文章目錄
- 什么是方法?
- 方法就是一個代碼片段. 類似于 C 語言中的 "函式".
- 方法可以理解為是一個 功能,實作某種我們想要達到的效果,而且這個功能是可以被重復使用的,
- 方法存在的意義(不要背, 重在體會):
- 方法的語法格式
- 代碼實體1(求 1~n的和)
- 圖 1
- 如果你想讓方法(函式)回傳一個值,就是說帶回一個值回到主函式,再對其列印也行
- 程式實體2(程式實體1的改版)
- 圖2
- 既然講到函式(方法),也就會涉及函式堆疊幀問題,
- 函式堆疊幀圖(圖3),想了解 c方面的或者想對比一下的,可以看這篇文章[函式堆疊幀銷毀與創建(vs2013)- 修改版](https://blog.csdn.net/DarkAndGrey/article/details/119826033?spm=1001.2014.3001.5501)
- 注意事項
- 方法呼叫的執行程序
- 基本規則
- 代碼示例: 計算 1! + 2! + 3! + 4! + 5!
- 圖4
- 實參和形參的關系
- 舉個例子(交換a與b值)
- 我們先來用常規形式來寫
- 這里是交換實參
- 圖5
- 圖6
- 用方法來寫
- 圖7
- 圖8
- 圖9
- 沒有回傳值的方法
- 代碼示例
- 圖10
- 圖11
- 圖12
- 方法的多載
- 代碼示例
- 圖13
- 由此,我們需要 方法的多載:一個函式同時兼容多種引數的情況
- 代碼實體2
- 圖14
- 記住
- 另外切記
- 例如:
- 再來看一個錯誤示范
- 圖 15
- 圖16
- 補充一個知識點:
- 由上我可以總結出 方法多載的規則
- 方法遞回
- 遞回的概念
- 使用遞回之前,需要一個前提
- 代碼示例
- 圖17
- 那我該怎么去寫一個可以使用的遞回呢?
- 代碼實體2
- 圖 18
- 其實終止條件,相當于數學里的起始條件
- 舉個例子
- 代碼實作
- 圖19
- 再來看幾道例題
- 求 1~n之間的和
- 圖20
- 按照順序列印一個數字的每一位(例如1234 列印出 1 2 3 4)
- 圖21
- 遞回執行程序分析
- 繼續實踐
- 寫一個遞回方法,輸入一個非負整數,回傳組成它的數字之和. 例如,輸入 1729, 則應該回傳1+7+2+9,它的和是19
- 圖22
- 求斐波那契數列的第 N 項(遞回)
- 代碼如下
- 圖23
- 注意用 遞回的方法來求 斐波那契數,效率很低
- 圖 24
- 這里我推薦 迭代 方法(回圈)
- 求斐波那契數列的第 N 項(迭代/回圈)
- 圖 25
- 遞回小結
- 遞回是一種重要的編程解決問題的方式.
- 本文結束
什么是方法?
方法就是一個代碼片段. 類似于 C 語言中的 “函式”.
(其實方法就是函式另一個稱呼,只不過為了區分 C語言和Java,所以在Java中叫方法,C叫函式,唯一不同的就是兩者實作的方式)
?
方法可以理解為是一個 功能,實作某種我們想要達到的效果,而且這個功能是可以被重復使用的,
打個比方: 方法就好像是 制作面包的磨具,如果你想得到一個這樣形狀,或者說達到某種效果,你就去使用該磨具,(該函式能完成某一個 功能/任務/效果)
而且,你可以換材料,但你不能用它做其他的東西,這個磨具是專門正對面包的(函式實作的功能,是專門針對某一種情況)
也就是說你做面包要使用材料必須屬于面粉這一類的東西,無非即使質量的好壞
(函式接收的引數值是可以改變大小的,型別不能變,否則引數型別不匹配,是無法成功呼叫函式的功能)
?
方法存在的意義(不要背, 重在體會):
1. 是能夠模塊化的組織代碼(當代碼規模比較復雜的時候).
2. 做到代碼被重復使用, 一份代碼可以在多個位置使用.
3. 讓代碼更好理解更簡單.
4. 直接呼叫現有方法開發, 不必重復造輪子
?
方法的語法格式
public static 回傳值 方法名稱(形式引數串列){
函式體/方法體;
}
代碼實體1(求 1~n的和)
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);// 為輸入資料做準備
int n = scanner.nextInt();// 接收一個整型資料,將其賦給 n
sumadd(n);// 呼叫函式(方法)sumadd,并向 sumadd方法 傳參(n),
}
public static void sumadd(int n){// 這里接收引數,和C語言一樣,可以定義一個相同的變數名,來接收傳引數,但必須型別和引數個數要相同
int sum =0;// 定義一個求和變數,將其初始化
for(int i =0;i<=n;i++){
sum += i;// 將1~10的每個數值累加起來
}
System.out.println(sum);// 輸出累加的結果
}
}// 圖1
圖 1

不知道你們有沒有發現程式中 我們的 sumadd方法沒有寫在 main方法上面,而是下面,學過C的朋友可能會問 “不需要在前面宣告函式嗎?”
答案是不用,在Java中 是沒有函式宣告的概念,它就是要用的時候,直接去呼叫
另外在普及一個內容,方法的引數叫做形參,main方法的引數叫實參,形參相當于實參的一份拷貝,這種傳參方式被稱為 按值傳遞(傳值)
由此引申出一個知識點, 在Java中 是沒有 傳址 的概念,只有傳值,所以我們在呼叫方法的時候,只需要注重 形參 和 實參 的型別 和 個數 是否匹配(相同)
?
如果你想讓方法(函式)回傳一個值,就是說帶回一個值回到主函式,再對其列印也行
程式實體2(程式實體1的改版)
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);// 為輸入資料做準備
int n = scanner.nextInt();// 接收一個整型資料,將其賦給 n
int ret = sumadd(n);// 呼叫方法(函式)sumadd,并想 sumadd方法 傳參(n),
System.out.println(ret);
}
public static int sumadd(int n){// 這里接收引數,和C語言一樣,可以定義一個相同的變數名,來接收傳引數,但必須型別和引數個數要相同
int sum =0;// 定義一個求和變數,將其初始化
for(int i =0;i<=n;i++){
sum += i;// 將1~10的每個數值累加起來
}
return sum;
}
}// 圖2,注意圈起來的部分
圖2

有的人可能會說 我可不可以 不創建一個 與方法的回傳值相同型別的變數 來接收它的值,直接輸出行嗎?
答案是可以的,
寫法: System.out.println(sumadd(n));
因為 sumadd 是回傳值的,輸出陳述句,只是將它回傳來的值給列印了而已,
如果方法的回傳值為 void(無回傳),那么程式會報錯(輸出陳述句會說:我都準備好了,你就給我看這? 就這!)
?
既然講到函式(方法),也就會涉及函式堆疊幀問題,
每個函式,在被呼叫的時候,都會開辟堆疊幀(開辟屬于自身的記憶體空間)
函式堆疊幀圖(圖3),想了解 c方面的或者想對比一下的,可以看這篇文章函式堆疊幀銷毀與創建(vs2013)- 修改版

?
注意事項
1. public 和 static 兩個關鍵字在此處具有特定含義, 我們暫時不討論, 后面會詳細介紹.
2. 方法定義時, 引數可以沒有. 每個引數要指定型別
3. 方法定義時, 回傳值也可以沒有, 如果沒有回傳值, 則回傳值型別應寫成 void
4. 方法定義時的引數稱為 "形參", 方法呼叫時的引數稱為 "實參".
5. 方法的定義必須在類之中, 代碼書寫在呼叫位置的上方或者下方均可.
6. Java 中沒有 "函式宣告" 這樣的概念
?
方法呼叫的執行程序
基本規則
定義方法的時候, 不會執行方法的代碼. 只有呼叫的時候才會執行.
比如:
int b = sumadd();,它在執行該陳述句時,遇見呼叫方法的代碼,會先去執行呼叫方法,下面程式它暫時不會去關,
System.out.println(b); 呼叫方法的代碼執行完了之后,才會輪到該陳述句執行
當方法被呼叫的時候, 會將實參賦值給形參.(int a = 10; sumadd(a); public static void sumadd(int A); 這里的A,其實就是a值的一份拷貝 )
引數傳遞完畢后, 就會執行到方法體代碼.
當sumadd方法執行完畢之后(遇到 return 陳述句), 就執行完畢,,同時將其值帶回main方法中,并將其賦給變數b(型別匹配)繼續往下執行
并且一個方法可以被多次呼叫.
?
代碼示例: 計算 1! + 2! + 3! + 4! + 5!
public class UseOfMethods {
public static void main(String[] args) {
int n =5;
int sum = factorialSum(n);// 呼叫函式計算 5 的階乘和是多少,將其賦給sum
System.out.println(sum);
}
public static int factorialSum(int n){
int sum = 0;
int ret = 1;
for(int i = 1;i<=n;i++){
ret *= i;// 每個數字的階乘
sum+=ret;// 將每個數字的階乘累計相加
}
return sum;
}// 圖 4
}
圖4

使用方法的優點也就體現出來了,創建一個專門計算階乘和的函式,只要我的傳參沒問題,它就能幫我完成想要的效果,
注意 自定義方法名,要讓人看得懂是什么意思,見名知其用,看到名字知道它是用來干什么的,接下看代碼,往這個思想靠攏,大大提升代碼的閱讀性,
?
實參和形參的關系
舉個例子(交換a與b值)
我們先來用常規形式來寫
這里是交換實參
public class UseOfMethods {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("交換前:a = " + a + " b = " + b);
// 1
// int tmp =a;
// a =b;
// b = tmp;// 圖 5
//2.異或
a = a^b;
b = a^b;// b = a^b^b(b^b == 0) == 0 ^ a == a
a = b^a;// a = b^a == a(b) ^ a^b(a) == 0 ^ b == b
// 圖 6
// 兩種方法均可以交換兩者資料
System.out.println("交換后:a = " + a + " b = " + b);
}
}
圖5

圖6

?
用方法來寫
public class UseOfMethods {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("交換前:a = " + a + " b = " + b);
swap(a, b);
System.out.println("交換后:a = " + a + " b = " + b);
}
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
}
圖7

圖8

由圖 7,8.可知這樣寫不會發生任何改變,因為只是交換形參,也就是說 交換的是 swap方法的引數
并不是在交換 main方法的引數,這里的x,y,只是a和b的值的一份拷貝
有人肯定會說像C里面一樣,傳地址唄!
記住 在Java中,是無法取得堆疊上變數的地址的,也就是說取不到區域變數的地址( 圖 9)
圖9

如果要去做,只能把 a 和 b 的值,放到堆上(動態空間上),放在堆上的都是物件,
這道題留著 我們講 物件和類,或者陣列的時候,再進行講解,以我們現在的基礎還不足以解決該問題,
?
沒有回傳值的方法
方法的回傳值是可選的. 有些時候可以沒有的
代碼示例
public class UseOfMethods {
public static void main(String[] args) {
int a = 0;
int b = 1;
sum(a,b);
}
public static void sum(int x,int y){
System.out.println(x+y);
// 我在方法中就已經將我們想列印的結果 圖 10,所以不需要回傳值
//但是規定 無回傳,就不能 return 回傳一個值
// return x+y; 圖 11,都報錯了,就別談運行
// 但是可以這么寫
return ;// 虛晃一槍,你能奈我何?
// 圖 12
}
}
圖10

圖11

圖12

?
方法的多載
有些時候我們需要用一個函式同時兼容多種引數的情況, 我們就可以使用到方法多載
代碼示例
public class UseOfMethods {
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 12.5;
double b2 = 18.5;
double ret2 = add(a2, b2);//圖 13
System.out.println("ret2 = " + ret2);
}
public static int add(int x,int y){
return x+y;
}
}
圖13

?
由此,我們需要 方法的多載:一個函式同時兼容多種引數的情況
那我們怎么實作呢?
代碼實體2
public class UseOfMethods {
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 12.5;
double b2 = 18.5;
double b3 = 9.0;
double ret2 = add(a2, b2,b3);//圖 13
System.out.println("ret2 = " + ret2);
}
public static int add(int x,int y){
return x+y;
}
public static double add(double x,double y,double z){
return (x+y+z);
}
}// 圖 14
圖14

?
記住
方法多載的重點在于函式名要相同,引數個數或者型別要不同,(兩者中必有一者不同)
而回傳值 不重要,只要你接識訓傳的值的型別是相匹配的就行,要不然無法進行賦值,
另外切記
不要寫的一摸一樣(引數個數和型別都相同),這樣相當于 一個變數被重復定義,是會出錯的
例如:
public static int add(int x,int y){
return x+y;
}
public static int add(int x,int y){
return x+y;
}
?
再來看一個錯誤示范
public class UseOfMethods {
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 (double)(x+y);
}
}// 圖 15
圖 15

由圖我們可以看出這樣寫也是不行的,正如我前面所說的 回傳值,不是重點,引數型別和個數才是重點
只要你引數的型別 和 個數 一模一樣,無論你回傳值是什么,都算方法的重定義, 程式是會報錯的(方法的重定義這是我個人理解)
&ensp
圖16

由此我們可以看出 方法的多載 有多么強大,
我們可以根據 引數的型別和個數 來為我們的方法,加載 幾個新的方法,具有更多的功能
現在你知道了吧 方法的多載,有多爽!
?
補充一個知識點:
如果一個類的兩個方法(無論是在同一個類中宣告,還是都由一個類繼承的,或者一個宣告和一個繼承的【總的來說不一定是同一個類里的】)
具有相同的名稱,但簽名不是重寫等價的,則稱該方法名為多載, (了解即可)
?
由上我可以總結出 方法多載的規則
1. 方法名要相同
2. 方法的引數不同(引數個數或者型別,兩者選其一,或者都選,反正至少有一個因素是不同的)
3. 方法的回傳值型別不影響多載
4. 當兩個方法的名字相同, 引數也相同, 但是回傳值不同的時候, 不構成多載.
?
方法遞回
遞回的概念
一個方法在執行程序中呼叫自身, 就稱為 "遞回".
遞回相當于數學上的 "數學歸納法", 有一個起始條件, 然后有一個遞推公式
遞推公式是遞回的重點,推出它,遞回就很好寫,沒推出,就,,,嗯~~~
使用遞回之前,需要一個前提
1. 有一個趨近于終止的條件(停止遞回的條件)
2. 自己呼叫自己(讓它自己扇自己,扇疼了再停下來,不要問我為什么我舉這個例子!)
?
代碼示例
public class UseOfMethods {
public static void main(String[] args) {
func();
}
這樣寫,就不滿足 使用遞回所需的第一個條件 沒有一個終止遞回的條件
所以該程式會無限遞回死回圈,最終導致堆疊溢位(堆疊空間不是無限大,是有限,func方法一直呼叫自己下去,最終肯定是會 爆滿/溢位 的)
因為 每次呼叫 func方法時,都會為其在堆疊上空間開辟塊自己的空間
public static void func(){
func();
}
}// 圖17
圖17

?
那我該怎么去寫一個可以使用的遞回呢?
代碼實體2
public class UseOfMethods {
public static void main(String[] args) {
func(3);
}
public static void func(int n){
if(n==1){// 遞回的終止的條件,因為 n == 3.每次自己呼叫自己時,n都會減1,呼叫 3 次,就結束了
System.out.println(n);
return ;
}
func(n-1);
System.out.println(n);
}
}// 圖18
圖 18

?
其實終止條件,相當于數學里的起始條件
你這樣想 假設我們不知道 n 為多少,那我們怎么辦?先從初始情況分析唄
當 n =0時,怎么辦?
n =1時,怎么辦?
大于1又怎么辦?
en~,是不是有點懂了,當我們處理遞回時,我們可以有兩種思維
當我們去思考它的終止條件是什么? 你可能會被套娃,套成zz,
這時候,我們反過來想想 這個值的初始值是多少,滿足怎樣的條件?(就比如說要大于,小于或等于某個條件)
是不是打開新思路了?‘’
思考遞回的時候,橫向思考:根據遞推公式(個人理解遞推公式跟數學的通項公式有點像)去思考
代碼執行:縱向執行的(從上往下,一條條執行)
在坐的某些人,經常會自己代入某個數字,然后,拿著數字和代碼一個個展開,很燒腦的
雖然我很喜歡這樣,但是我只是在我掌握遞回規律(遞推公式)之后,對代碼執行程序的好奇,去展開很小很小的范圍,比如1、2、3之類的
太復雜了,對不起,我不玩了,,,
這點不值得提倡,我們需要的是掌握其遞回規律,才是重中之重,
?
舉個例子
求 n的階乘(n==5)
1! == 1 //這就是我們的起始條件,也是我們的終止條件
2! == 2*1 == 2 * 1!
3! == 3*2*1 == 3 * 2!
4! == 4*3*2*1 == 4 * 3!
5! == 5*4*3*2*1 == 5 * 4!
你發現了 求 5!的值,它是 5 * (5-1)!
而 4! == 4 * (4-1)!
3! == 3 * (3-1)!
2! == 2 * (2-1)!
以此類推 直到遇到 1! 時,他的回傳值里不再帶其他階乘,此時 1!是不是很適合作為我們遞回的終止條件
由此我們發現了 遞推公式為 n * (n-1)!
5! == 5 * 4!== 5 * 4 * 3! == 5 * 4 * 3 * 2! == 5 * 3 * 2 * 1(這里只是幫助你們理解,別學,上面的遞推公式才是你們該學的)
代碼實作
import java.util.Scanner;
public class UseOfMethods{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(factorial(n));
sc.close();//關閉輸入
}
public static int factorial(int n){
if(1==n){
return 1;
}
return n*factorial(n-1);// n * (n-1)!
// (n-1)! == factorial(n-1); 即 factorial == !
}
}// 圖19,不懂沒關系,我幫你搬下來,對著看
1! == 1 //這就是我們的起始條件,也是我們的終止條件
2! == 2*1 == 2 * 1!
3! == 3*2*1 == 3 * 2!
4! == 4*3*2*1 == 4 * 3!
5! == 5*4*3*2*1 == 5 * 4!
你發現了 求 5!的值,它是 5 * (5-1)!
而 4! == 4 * (4-1)!
3! == 3 * (3-1)!
2! == 2 * (2-1)!
5! == 5 * 4!== 5 * 4 * 3! == 5 * 4 * 3 * 2! == 5 * 3 * 2 * 1
程式運行 跟我們剛才藏寶殿是一樣的,一層一層的搶,從最里面的開始搶
也就從我們終止條件開始
n == 1; return 1;// 這里的回傳值 是回傳到 呼叫它的上一層級(藏寶殿核心是第一層嗎,搶完了,肯定去搶第二程啊)
n == 2 return 2* factorial(2-1) == 2 * 1 == 2
n == 3; return 3 * 2 == 6
n == 4; return 4 * 6 == 24
n == 5; return 5 * ==
考驗你們的時候,在下面評論,看你們到底有沒有看懂學會
遞回的字面意思
遞回 n * factorial(n-1)l n-1 就是它傳過去的值,稱為遞, factorial(n-1)回傳的值,稱為歸
結合稱為: 遞回,
你賺的錢遲早是有一天會花完的,,,
趕快跟著我一起卷,
圖19

?
再來看幾道例題
求 1~n之間的和
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(sumadd(n));// 圖 20
sc.colse()// 關閉輸入
}
public static int sumadd(int n){
if(n==1){
return 1;
}
return n + sumadd(n-1);
}
}// 自己琢磨,不懂在下方評論,一起探討,(n 比 n-1 多了1,看似廢話,其實是真理)
圖 20
圖20

?
按照順序列印一個數字的每一位(例如1234 列印出 1 2 3 4)
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
print(n);
sc.close();//關閉輸入
}
public static void print(int n){
if(n<10){
System.out.println(n);
return;
}
print(n/10);// 在呼叫自身是,將 n/10 的值 作為下個自身函式的形參
// 最終肯定有 剩一位數的時候,一位數(0~9)肯定是小于10的,別問為什么,問就不會!
// 這個也就是我們終止條件
// 假設我們輸入的是 1234 , 在呼叫自身是,將 n/10 的值 作為下個自身函式的形參,那么 剩余的最后一位就是 1.所以從1開始列印
// 其次是2 (12%10)、 3(123%10)、4(1234%10)
System.out.println(n%10);
}// 圖 21
}
圖21

?
遞回執行程序分析
遞回的程式的執行程序不太容易理解, 要想理解清楚遞回, 必須先理解清楚 "方法的執行程序", 尤其是 "方法執行結束之后, 回到呼叫位置繼續往下執行“
&ensp;
繼續實踐
寫一個遞回方法,輸入一個非負整數,回傳組成它的數字之和. 例如,輸入 1729, 則應該回傳1+7+2+9,它的和是19
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(sum(n));
sc.close();
}
public static int sum(int n){
if(n<10){
return n;
}
return n%10+sum(n/10);
}
}// 圖 22 ,這題跟上面那題幾乎一樣,就不講了,自己琢磨,
圖22

?
求斐波那契數列的第 N 項(遞回)
1 1 2 3 5 8 13......
從第三位開始,等于自身前兩位數相加
代碼如下
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(fibonacci(n));
}
public static int fibonacci(int n){
if(n<3){
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
}
}// 圖23
圖23

?
注意用 遞回的方法來求 斐波那契數,效率很低
圖 24

一個數字重復出現,求 第 n位的斐波那契函式,n越大重復的數字越大
所以我不推薦使用遞回去處理斐波那契數,因為面試中,使用該方法求解,你絕對GG
?
這里我推薦 迭代 方法(回圈)
求斐波那契數列的第 N 項(迭代/回圈)
1 1 2 3 5 8 13.....
從第三位開始,等于自身前兩位數相加
import java.util.Scanner;
public class UseOfMethods {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
fibonacci(n);
sc.close();
}
public static void fibonacci(int n){
if(n<3) {
System.out.println(1);
}else {
int a =1;
int b =1;
int c = 0;
for(int i=3;i<=n;i++){//從第三位開始,等于自身前兩位數相加
c = a+b;
a=b;
b=c;
// 1 1 2 3 5
// a b c
// 1 1 2 3 5
// a b c
// 1 1 2 3 5
// a b c
}
System.out.println(c);
}
}
}// 圖 25,只是計算簡單加法,效率要高很多
圖 25

?
遞回小結
遞回是一種重要的編程解決問題的方式.
有些問題天然就是使用遞回方式定義的(例如斐波那契數列, 二叉樹等), 此時使用遞回來解就很容易.
有些問題使用遞回和使用非遞回(回圈)都可以解決. 那么此時更推薦使用回圈, 相比于遞回, 非遞回程式更加高效
本文結束
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/333782.html
標籤:java
