目次
【1】ちょっとした例
毎年、秋の採用期間中に、学校は重要な取り組みを 1 つ行います。それは、キャンパスの採用活動をフォローアップし、キャンパスの採用活動で遭遇するさまざまな問題を全員が解決できるよう支援し、全員が積極的に粘り強く探し続けて満足のいく内定を獲得できるように奨励することです。しかし、一般に誰もが同様の問題に遭遇します。
数日後、多くの生徒が同じような質問をし、先生はまた同じことをすることになりましたが、後になって、きっと遭遇したことがある生徒も多くて恥ずかしくて質問できなかったのではないかと思い、先生が質問を書きました。学校フォーラムに送信されました:
次に、投稿のリンクをグループに送信し、「最近の筆記試験に合格しなかった学生はここを参照してください。https: //www.xxx.com 学生はリンクをクリックすると投稿の内容を確認できます」と伝えます。 。
このプロセスから次のことがわかります。
-
教師は生徒のよくある問題を投稿に整理することで、繰り返しの作業を減らし、クラスメートからのより多くの問題を解決する時間を確保します。
-
学生がリンクをクリックすると、投稿に入って読むことができ、問題が解決されます。
-
生徒は、教師と同じことを何度も繰り返すことなく、リンクをクリックしてランダムに読むことができます。
プログラミングでも同様で、特定の関数コードが頻繁に使用される可能性があり、それがすべての場所で再実装されると、次のようになります。
-
手続きが面倒になる。
-
開発効率が低く、繰り返し作業が多くなります。
-
変更が必要な場合は、このコードが使用されているすべての場所を変更する必要があります。
-
再利用には向きません。
したがって、プログラミングでは、頻繁に使用されるコードを「ポスト」(メソッド) にカプセル化し、必要に応じてそれらをリンク (つまり、メソッド名 - メソッドのエントリ アドレス) として直接使用することもでき、面倒なプロセスを何度も回避できます。
【2】メソッドの概念と使い方
【2.1】メソッドとは何ですか?
メソッドとはコードの断片であり、C 言語の「関数」に似ています。メソッドの意味 (暗記するのではなく、理解することに集中してください):
-
コードをモジュール形式で編成できます (コード サイズが比較的複雑な場合)。
-
コードを再利用し、1 つのコードを複数の場所で使用できるようにしてください。
-
コードを理解しやすく、シンプルにします。
-
車輪の再発明を行わずに、既存の開発メソッドを直接呼び出します。
例: ここで、カレンダーを開発したいと考えています。カレンダーでは、年が閏年であるかどうかを判断する必要があることがよくあります。次のコードが提供されています。
import java.util.Scanner;
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
while(scan.hasNextInt()) {
int year = scan.nextInt();
// 判断year是不是闰年.
if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
System.out.println("是闰年");
else
System.out.println("不是闰年");
}
}
メソッドはどのように定義すればよいでしょうか?
【2.2】メソッド定義
[メソッドの構文形式]
// 方法定义
修饰符 返回值类型 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
【コード例】閏年かどうかを検出する関数を実装します。
public static boolean Method(int year){
// 判断year是不是闰年.
if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
return true;
else
return false;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while(scan.hasNextInt()) {
int year = scan.nextInt();
if(Method(year) == true){
System.out.println("是闰年");
} else {
System.out.println("不是闰年");
}
}
}
【コード例】 2つの整数を加算するメソッドを実装します。
public static int Add(int x, int y){
return x + y;
}
【予防】
-
修飾子: この段階では、パブリック静的固定マッチングが直接使用されます。
-
戻り値の型: メソッドに戻り値がある場合、戻り値の型は返されるエンティティの型と一致する必要があり、戻り値がない場合は void として記述する必要があります。
-
メソッド名: キャメルケースを使用して名前が付けられます。
-
パラメータ一覧:メソッドにパラメータがない場合は()内に何も記述しません。パラメータがある場合はパラメータの種類を指定する必要があります。複数のパラメータはカンマで区切ります。
-
メソッド本体: メソッド内で実行されるステートメント。
-
Java では、メソッドはクラス内に記述する必要があります。
-
Java ではメソッドをネストできません。
-
Javaにはメソッド宣言がありません。
【2.3】メソッド呼び出しの実行処理
【メソッド呼び出し処理】
メソッドを呼び出します--- >パラメータを渡します--- >メソッドのアドレスを見つけます--- >呼び出されたメソッドのメソッド本体を実行します--- >呼び出されたメソッドは終了して戻ります--- >メインの呼び出しメソッドに戻ります実行を継続します。
【予防】
-
メソッドが定義されている場合、メソッドのコードは実行されず、呼び出されたときにのみ実行されます。
-
メソッドは複数回呼び出すことができます。
【コード例】 2つの整数の足し算を計算する
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("第一次调用方法之前");
int ret = add(a, b);
System.out.println("第一次调用方法之后");
System.out.println("ret = " + ret);
System.out.println("第二次调用方法之前");
ret = add(30, 50);
System.out.println("第二次调用方法之后");
System.out.println("ret = " + ret);
}
public static int add(int x, int y) {
System.out.println("调用方法中 x = " + x + " y = " + y);
return x + y;
}
// 执行结果
一次调用方法之前
调用方法中 x = 10 y = 20
第一次调用方法之后
ret = 30
第二次调用方法之前
调用方法中 x = 30 y = 50
第二次调用方法之后
ret = 80
【コード例】 1! + 2! + 3! + 4! + 5! を計算します。
public class TestMethod {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += fac(i);
} S
ystem.out.println("sum = " + sum);
}
public static int fac(int n) {
System.out.println("计算 n 的阶乘中n! = " + n
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
} r
eturn result;
}
} /
/ 执行结果
计算 n 的阶乘中 n! = 1
计算 n 的阶乘中 n! = 2
计算 n 的阶乘中 n! = 3
计算 n 的阶乘中 n! = 4
计算 n 的阶乘中 n! = 5
sum = 153
【2.4】実パラメータと仮パラメータの関係(重要)
Java のメソッドの仮パラメータは、sum 関数の独立変数 n に相当し、sum 関数が呼び出されたときに渡される値を受け取るために使用されます。仮パラメータの名前は任意に選択でき、メソッドには影響しません。仮パラメータは、メソッドの呼び出し時に渡される値を保存するためにメソッドを定義するときに使用する必要がある単なる変数です。
【コード例】例えば
public static int getSum(int N){ // N是形参
return (1+N)*N / 2;
}
getSum(10); // 10是实参,在方法调用时,形参N用来保存10
getSum(100); // 100是实参,在方法调用时,形参N用来保存100
【コード例】例えば
public static int add(int a, int b){
return a + b;
}
add(2, 3); // 2和3是实参,在调用时传给形参a和b
[注意] Javaでは実パラメータの値は常に仮パラメータにコピーされますが、仮パラメータと実パラメータは本質的には2つの実体です。
[コード例] 2 つの整数変数を交換する
public static void main(String[] args) {
int a = 10;
int b = 20;
swap(a, b);
System.out.println("main: a = " + a + " b = " + b);
}
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
System.out.println("swap: x = " + x + " y = " + y);
}
// 运行结果
swap: x = 20 y = 10
main: a = 10 b = 20
スワップ関数が交換された後、仮パラメータ x と y の値が変更されていることがわかりますが、main メソッドの a と b の値は交換前と同じままです。 、交換は成功しませんでした。
実パラメータ a と b は main メソッドの 2 つの変数であり、それらの空間は main メソッドのスタック (特別なメモリ空間) 内にありますが、仮パラメータ x と y は swap メソッドの 2 つの変数であり、その空間はスワップ メソッドの実行時にスタック内で x と y が構成されるため、実パラメータ a と b と仮パラメータ x と y は相関関係のない 2 つの変数であり、スワップ メソッドが呼び出されるときの値は、実パラメータ a と b は単純にコピーされ、そのコピーの 1 つは仮パラメータ x と y に渡されるため、仮パラメータ x と y に対する操作は実パラメータ a と b には影響しません。
[注意]基本型の場合、仮パラメータは実パラメータのコピー、つまり値による呼び出しと同等です。
int a = 10;
int b = 20;
int x = a;
int y = b;
int tmp = x;
x = y;
y = tmp;
x と y の変更は a と b に影響を与えないことがわかります。
[解決策]参照型パラメータ(この問題を解決するには配列など)を渡します。
このコードの実行プロセスについては、後で配列について学ぶときに詳しく説明します。
public static void main(String[] args) {
int[] arr = {10, 20};
swap(arr);
System.out.println("arr[0] = " + arr[0] + " arr[1] = " + arr[1]);
}
public static void swap(int[] arr) {
int tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
}
// 运行结果
arr[0] = 20 arr[1]
【1.5】戻り値のないメソッド
メソッドの戻り値はオプションです。値が存在しない場合もあります。そうでない場合は、戻り値の型を void として記述する必要があります。
【コード例】
<span style="background-color:#ffffff"><span style="color:#7a7a7a">class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
print(a, b);
}
public static void print(int x, int y) {
System.out.println("x = " + x + " y = " + y);
}
}
</span></span>
【2】関数のオーバーロード
【2.1】メソッドのオーバーロードが必要な理由
public class TestMethod {
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = add(a2, b2);
System.out.println("ret2 = " + ret2);
}
public static int add(int x, int y) {
return x + y;
}
}
// 编译出错
Test.java:13: 错误: 不兼容的类型: 从double转换到int可能会有损失
double ret2 = add(a2, b2);
^
パラメータの型が一致しないため、既存の add メソッドを直接使用することはできません。
比較的単純かつ大まかな解決策は次のとおりです。
public class TestMethod {
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = addInt(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = addDouble(a2, b2);
System.out.println("ret2 = " + ret2);
}
public static int addInt(int x, int y) {
return x + y;
}
public static double addDouble(double x, double y) {
return x + y;
}
}
上記のコードは確かに問題を解決できますが、不親切な点は、さまざまなメソッド名を提供する必要があり、名前付けが本質的に頭の痛い問題であることです。すべての名前を add として指定できますか?
【2.2】メソッドのオーバーロードの概念
自然言語では「いい人」などの「多義性」という現象がよく起こります。
自然言語では、単語に複数の意味がある場合、それは過負荷であると言われ、特定の意味を特定のシナリオと組み合わせる必要があります。Java ではメソッドをオーバーロードすることもできます。
Java では、複数のメソッドの名前が同じでパラメータ リストが異なる場合、それらはオーバーロードされていると言われます。
public class TestMethod {
public static void main(String[] args) {
add(1, 2); // 调用add(int, int)
add(1.5, 2.5); // 调用add(double, double)
add(1.5, 2.5, 3.5); // 调用add(double, double, double)
}
public static int add(int x, int y) {
return x + y;
}
public static double add(double x, double y) {
return x + y;
}
public static double add(double x, double y, double z) {
return x + y + z;
}
}
【知らせ】
-
メソッド名は同じである必要があります。
-
パラメータ リストは異なっている必要があります (パラメータの数、パラメータのタイプ、およびタイプの順序が異なる必要があります)。
-
戻り値の型が同じかどうかは関係ありません。
// 注意:两个方法如果仅仅只是因为返回值类型不同,是不能构成重载的
public class TestMethod {
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
}
public static int add(int x, int y) {
return x + y;
}
public static double add(int x, int y) {
return x + y;
}
}
// 编译出错
Test.java:13: 错误: 已在类 Test中定义了方法 add(int,int)
public static double add(int x, int y) {
^
1 个错误
-
コンパイラはコードをコンパイルするときに、実際のパラメータの型を推定し、推定の結果に基づいて呼び出すメソッドを決定します。
【2.3】メソッドシグネチャ
同じ名前を持つ 2 つの識別子を同じスコープ内に定義することはできません。例: 同じ名前の 2 つの変数を 1 つのメソッド内で定義することはできません。では、なぜ同じ名前のメソッドを 1 つのクラス内で定義できるのでしょうか?
メソッド シグネチャは、コンパイラによってコンパイルおよび変更された後のメソッドの最終的な名前です。具体的なメソッドは、メソッドの完全なパス名 + パラメータ リスト + 戻り値の型であり、これがメソッドの完全な名前を構成します。
public class TestMethod {
public static int add(int x, int y){
return x + y;
}
public static double add(double x, double y){
return x + y;
}
public static void main(String[] args) {
add(1,2);
add(1.5, 2.5);
}
}
上記のコードをコンパイルした後、JDK に付属の javap 逆アセンブリ ツールを使用して、特定の操作を表示します。
-
まずプロジェクトをコンパイルして .class バイトコード ファイルを生成します。
-
コンソールで、表示する .class が存在するディレクトリを入力します。
-
「javap -v バイトコード ファイル名」と入力します。
メソッド シグネチャ内のいくつかの特殊記号の説明:
特殊文字 | データの種類 |
---|---|
V | 空所 |
Z | ブール値 |
B | バイト |
C | チャー |
S | 短い |
私 | 整数 |
J | 長さ |
F | 浮く |
D | ダブル |
[ | 配列 ([ で始まり、他の特殊文字と組み合わせて、対応するデータ型の配列を表します。複数の [ は複数の次元配列を表します) |
L | 参照型。L で始まり;で終わり、中央は参照型の完全なクラス名です。 |
【3】再帰
昔々、山があって、その山にお寺がありました。お寺には老僧がいて、若い僧侶に物語を語りました。その物語は次のとおりです。「昔々、山がありました。」 . 山の上に寺院がありました。寺院には、若い僧侶に物語を語る老僧がいました。それは次のとおりです。「むかしむかし、山がありました。そして、山にお寺がありました。 ..」「昔々、あるところに山がありました...」
上の 2 つのストーリーには共通の特徴があります:ストーリー自体が含まれているということです。この種の考え方は、数学やプログラミングで非常に役に立ちます。なぜなら、遭遇する問題を直接解決するのは簡単ではない場合もありますが、元の問題を分解できることがわかるからです。サブ問題に分割された後、サブ問題は元の問題と同じ解法を持ち、サブ問題が解決された後、元の問題が解決されます。
【3.1】再帰の概念
実行中に自分自身を呼び出すメソッドを「再帰的」といいます。再帰は数学における「数学的帰納法」に相当し、開始条件があり、その後に再帰的な式が存在します。
例えばNさんにお願いします!
開始条件: N = 1 の場合、N! は 1 です。この開始条件は再帰の終了条件と同等です。
再帰式: N! を見つけます。直接見つけるのは簡単ではありませんが、問題を N! => N * (N-1)! に変換できます。
再帰に必要な条件:
-
元の問題をサブ問題に分割します。注意: サブ問題は、元の問題の解決策と一致している必要があります。
-
再帰的な終了。
【コード例】 N の階乗を再帰的に求めます。
// 非递归
public static int factorial(int n){
int ret = 1;
for(int i = 1; i <= n; i++){
ret *= i;
}
return ret;
}
public static void main(String[] args
int n = 5;
System.out.println(factorial(n));
}
// 递归
public static int factorial(int n) {
if(n < 1){
return 1;
} else {
return n * factorial(n - 1);
}
}
public static void main(String[] args) {
int n = 5;
System.out.println(factorial(n));
}
【3.2】再帰的実行プロセス分析
public static void main(String[] args) {
int n = 5;
int ret = factor(n);
System.out.println("ret = " + ret);
}
public static int factor(int n) {
System.out.println("函数开始, n = " + n);
if (n == 1) {
System.out.println("函数结束, n = 1 ret = 1");
return 1;
}
int ret = n * factor(n - 1);
System.out.println("函数结束, n = " + n + " ret = " + ret);
return ret;
}
// 执行结果
函数开始, n = 5
函数开始, n = 4
函数开始, n = 3
函数开始, n = 2
函数开始, n = 1
函数结束, n = 1 ret = 1
函数结束, n = 2 ret = 2
函数结束, n = 3 ret = 6
函数结束, n = 4 ret = 24
函数结束, n = 5 ret = 120
ret = 120
【実行プロセス図】
-
「コールスタック」について。
-
メソッドが呼び出されるとき、現在の呼び出し関係を記述する「スタック」メモリ空間が存在します。これはコール スタックと呼ばれます。
-
各メソッド呼び出しは「スタック フレーム」と呼ばれ、各スタック フレームには呼び出しのパラメータや実行を継続するためにどこに戻るかなどの情報が含まれています。
-
後で、IDEA を使用してコール スタックの内容を簡単に確認できます。
【3.3】再帰的実践
[練習問題 1]数値の各桁を順番に出力します (たとえば、1234 は 1 2 3 4 を出力します)。
[図]
public static void print(int n){
if(n == 0){
return;
}
print(n / 10);
System.out.print(n % 10);
}
public static void main(String[] args) {
int n = 1234;
print(n);
}
[練習問題 2]数値の各桁を順番に出力します (たとえば、1234 は 1 2 3 4 を出力します (変形すると合計 1234))。
[図]
public static int print(int n){
if(n < 10){
return n;
}
return (n % 10) + print(n / 10);
}
public static void main(String[] args) {
int n = 1234;
System.out.println(print(n));
}
[練習問題 3] 1 + 2 + 3 + ... + 10 を再帰的に求めます
[図]
public static int func_1(int n){
if (n == 1){
return 1;
}
return n + func_1(n - 1);
}
public static void main(String[] args){
System.out.println(func_1(10));
}
[練習問題 4] フィボナッチ数列の N 項を求めます。
// 非递归
public static int fib(int n){
int f1 = 1;
int f2 = 1;
int f3 = 0;
for(int i = 3; i <= n; i++){
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
return f3;
}
public static void main(String[] args) {
System.out.println(fib(5));
System.out.println(fib(10));
System.out.println(fib(20));
System.out.println(fib(30));
}
// 递归
public static int fib(int n){
if(n <= 2){
return 1;
} else {
return fib(n - 1) + fib(n - 2);
}
}
public static void main(String[] args) {
System.out.println(fib(5));
System.out.println(fib(10));
System.out.println(fib(20));
System.out.println(fib(30));
}
【4】ハノイ塔問題
【問題の背景】
ハノイ塔問題は古典的な問題です。ハノイタワーとしても知られるハノイタワーは、インドの古代伝説に由来しています。ブラフマー神が世界を創造したとき、3本のダイヤモンドの柱を作り、1本の柱には64枚の金の円盤を下から上に大きさの順に積み上げました。ブラフマーはバラモンに、別の柱の上にある円盤を下から大きい順に並べ替えるよう命じました。また、小さな円盤では常に円盤を拡大することはできず、3 つの柱の間で一度に 1 枚の円盤しか移動できないことも規定されています。操作方法を尋ねますか?
この図を見ると、3 枚のプレートを移動するのに 7 ステップかかることが簡単にわかりますが、これが最小ステップ数であるかどうかについては、考えていただけます。
ルールを見つけると、1、2、3、4 枚のプレートを移動するには、それぞれ、、、1、3、7、15 のステップが必要になります。n 個のプレートを移動するには 2^n−1 ステップが必要であることを推測するのは難しくありません。
【イラスト1】
【イラスト2】
【イラスト3】
上の図から次のことがわかります。
-
プレート:2^1-1 1回移動
-
2 つのプレート: 2^2-1 を 3 回移動します
-
3 枚のプレート: 2^3-1 を 7 回移動します
-
64 枚のプレート: 2^64-1 を移動
【問題解決】
/**
* 汉诺塔问题
* @param n
* @param pos1 初始位置
* @param pos2 中转位置
* @param pos3 目标位置
*/
public static void towerOfHanoi(int n, char pos1, char pos2, char pos3) {
if(n == 1){
towerOfHanoiMove(pos1, pos3);
return;
}
towerOfHanoi(n - 1, pos1, pos3, pos2);
towerOfHanoiMove(pos1, pos3);
towerOfHanoi(n - 1, pos2, pos1, pos3);
}
/**
* 移动打印函数
* @param pos1 初始位置
* @param pos2 目标位置
*/
public static void towerOfHanoiMove(char pos1, char pos2){
System.out.println(pos1 + "->" + pos2 + " ");
return;
}
/**
* 入口函数
* @param args
*/
public static void main(String[] args) {
towerOfHanoi(3, 'A', 'B', 'C');
}