搭配green judge解題系統
Special thanks to 台中女中sagit老師 <(_ _)>
上一章:大數運算
下一章:排序
回目錄:國立科學園區實驗中學C++程式語言自學講義
函式的英文是function
你可以把它想像成一塊有特定功能的程式
它不會被寫在main裡面
因為main本身也是個函式
話不多說
直接看這個例子
void say_hello()
{
cout<<"Hello"<<endl;
}
在上面的程式裡
我們 宣告 了一個函式,取名為say_hello
前面的 void 代表它並沒有要算出甚麼答案來(後面會有詳細解釋);
後面的 小括弧() ,是用來盛裝 參數 的,不過這個例子裡沒有參數;
底下大括弧裡包著的
cout<<"Hello"<<endl;
就是這個函式的 定義
簡而言之這個函式的功能呢
就是印出一句"Hello"而已
接下來只要把剛才那一塊函式放到main上面
我們就可以在main裡面 呼叫 它了
void say_hello()
{
cout<<"Hello"<<endl;
}
int main()
{
say_hello();
say_hello();
say_hello();
}
這樣一來,就會在小黑窗上看到三個 Hello
至於為什麼說它是「副函式」呢?
因為main本身也是一個函式!!
你可以看到main跟它的格式是一樣的
而main就是一個程式的「主函式」(它都叫main了當然是主角)
相較於它,其他的就是「副」函式囉
上一段有提到
小括弧() 是用來盛裝 參數 的
至於什麼是參數
可以看看以下的例子
void OAO(int x)
{
cout<<x<<endl;
}
int main()
{
OAO(123);
OAO(1000);
OAO(-1);
}
小括弧內的「參數」就是那個int x
程式裡面會用到的x就來自於小括弧裡的x
所以OAO這整個函式的功能就是
「拿一個數字x,並且把這個數字x給印出來」
在main裡面
首先我寫了OAO(123)
那麼OAO這個函式就被我「呼叫」了
並且我在呼叫的時候讓它的x先變成123
所以他就會幫我印出123這個數字
而接下來的OAO(1000)與OAO(-1)也是一樣的
所以你最後會在小黑窗上看到
123
1000
-1
在剛才的例子中,只用了一個 參數 x
但其實不管用幾個都可以
要用int或是long long或是double或是string或是各種型態都可以
以下示範兩個int參數
void add(int x, int y)
{
cout << x+y <<endl;
}
int main()
{
add(1,2);
add(4,5);
add(100,200);
}
add這個函式呢
他有兩個 參數
分別是 int x 與 int y
而這個函式的 定義 則是
把傳入的兩個參數x與y加起來後印出來
在main裡面 呼叫 函式的時候
就記得要傳兩個參數進小括弧裡
如果只傳一個 例如呼叫add(1)
或是傳了三個 例如呼叫add(1,2,3)
就會噴錯誤喔
這樣的程式執行起來
會在小黑窗上看到印出的
3
9
300
在剛才的例子中
我們函式最前面都是寫 void
它的意思就是說
「這個函式並沒有要回傳什麼答案回來」
你可以看到上面的例子就只是單純印出一些東西而已
接下來要介紹讓函式 回傳 答案
既然他要回傳答案
那就依照他要回傳答案的型態來把void取代掉
例如我想寫個函式,傳入x之後,讓函式把他加三之後的結果 回傳 給我
既然要回傳的是整數
那麼我這個函式的型態就是個整數
也就是函是的最一開始我要寫個int
int add3(int x)
{
int answer = x+3;
return answer;
}
int main()
{
int Q = 5;
int P = add3(Q);
cout << P << endl;
}
上面那個add3的函式
傳入一個x之後
他會用answer來儲存x+3之後的結果
並且把answer回傳過去
也就是要寫個return 加上答案
在main裡面呼叫add3這個函式時
將Q傳進去
並且把add3回傳的結果儲存在P裡面
由於Q是5
所以add3回傳的答案會是8
因此在cout<<P之後
我們會在小黑窗上看到一個數字8
當然也可以傳兩個以上的變數進去
不過回傳只能回傳一個
以下示範將兩數字相加的函式
int add(x, y)
{
int ans = x+y;
return ans;
}
int main()
{
int a, b;
cin >> a;
cin >> b;
cout << add(a, b);
}
它的本質上會和下面這個是一樣的
int main()
{
int a, b;
cin >> a;
cin >> b;
cout << a+b;
}
要練習使用函式
我們來複習很久以前寫過的題目:輸入H與W,輸出H*W
以前是直接在main裡面算答案
現在練習看看把運算的過程包到別的函式裡
讓main來呼叫它就好
【學生練習題】
函式裡面當然不是只能傻傻地做加減乘除
我們以往學過的條件判斷、迴圈等等
在函式內都能寫
簡而言之函式裡面一切都可以正常寫程式
【例題】
輸入一個數字N
如果N為1,那就印出1
否則,如果N是奇數,那就印出3N+1
否則,如果N是偶數,那就印出N/2
int f(int N)
{
if(N==1) return 1;
else if(N%2==1) return (3*N+1);
else return N/2;
}
int main()
{
int n;
cin >> n;
cout << f(n);
}
來試試用相同的f函式來解3N+1
【學生練習題】
總之
如果函式的功能是拿來算出答案的
就必須要在最前面寫上要算的答案的形態
該是int就是int
該是string就是string…等等
且函式的最後面要記得寫return 加上答案
如果沒有要算出答案
就在最開頭寫void就好
並且最後不需return答案
猜猜看,執行以下的程式時,會跑出甚麼
void to100(int x)
{
x=100;
}
int main()
{
int a=3;
to100(a);
cout << a << endl;
}
3?
或是100?
答案是,還是會跑出3喔
因為執行函式時
函式裡面的int x算是自己另外宣告的變數
所以就算把x變成100了
傳進去的數字也不會變成100
所以不用擔心變數傳進函式裡會被改變掉!!
前面的範例都是在main裡面呼叫函式
但其實函式裡面也可以呼叫函式喔
像這樣
void IPass()
{
cout<<"PASS";
}
void IFail()
{
cout<<"FAIL";
}
void sayResult(int score)
{
if(score>=60){
IPass();
}
else{
IFail();
}
}
int main()
{
int score;
cin >> score;
sayResult(score);
}
在main裡面呼叫了sayResult
而在sayResult裡面會呼叫IPass或是IFail
總而言之
函式不一定要被main呼叫
被別的函式呼叫也可以
那麼
在自己裡面呼叫自己可以嗎!?
像是這樣
void sayHi()
{
cout<<"Hi";
sayHi();
}
當然可以喔!!
雖然有點不直覺
但是一旦熟悉之後可以用它來達成很多神奇的事情
他就是 - - 接下來要介紹的 遞迴
呼叫副函式就像做夢一樣
又因為副函式的內容是定義好的
所以你可以把它想成套路固定的夢
每次作夢的時候帶一些物品進去(參數)
夢醒後會帶回一個答案(回傳)
而遞迴就像是夢中夢
你已經帶著物品到了夢中
在夢中的套路卻還要再做個夢
在程式中的寫法
就是讓函式裡面有「自己呼叫自己」的橋段
像是這樣
void sayhello()
{
cout << "hello" << endl;
sayhello();
}
這個函式印出一個hello之後
會再呼叫自己一次
就像是一個說哈囉的夢
在夢中說完哈囉還會躺上床再做一個一模一樣的夢中夢
在夢中夢說哈囉 然後在夢中夢躺床做夢中夢中夢…
你可能會懷疑
夢的套路中都有再做一個夢
那不就一直夢中夢下去不會醒來了嗎
對 沒錯
你把這段程式拿去執行
void sayhello()
{
cout << "hello" << endl;
sayhello();
}
int main()
{
sayhello();
}
會發現它一直印hello印個沒完
直到程式當掉為止
我們必須修改程式
讓它在某些條件下才要呼叫自己(夢中做夢)
這樣一來才有醒來的契機
至於要甚麼條件呢
這也是在程式裡面決定的
接下來要示範的程式是「限定夢中夢做到5層就結束」
所以它的行為應該要像是
夢->夢->夢->夢->夢->醒->醒->醒->醒->醒
那怎麼知道5次了沒呢
就用傳進來的 參數 來記錄吧
void sayhello(int x)
{
cout << "hello" << endl;
if(x<5) sayhello(x+1);
}
int main()
{
sayhello(1);
}
【例題】
輸入一個N
輸出N!
既然想算出N!
假設已經知道(N-1)!是多少了
我們就直接把那個(N-1)!拿去乘以N就是N!
為了算出(N-1)!
只需要呼叫自己一次就好
只要自己的函式內容是對的
就不需要擔心(N-1)!會不會被算錯了
就像每次睡覺(呼叫函式)時
不需要為了夢的內容怎麼運作而操心
只要帶正確的物品進去
並把它回傳的答案接住就好
這個夢要算出N!
那就在這個夢中再做個夢算出(N-1)!
把那個夢中夢的答案拿來乘以N就是我這個夢的答案
但若是N已經是1了
實在沒有必要再利用做夢來算出答案
直接回傳1就好
所以算N!的程式可以寫成這樣
int fac(int N)
{
if(N==1) return 1;
else return N*fac(N-1);
}
我不需要知道N!的本質是甚麼
我只需要知道N!和(N-1)!之間的關係就好
也就是算出(N-1)!之後再乘以N就是N!
遞迴也是這樣
我根本不需要知道整個運算怎麼變出來的
我只需要知道怎麼利用我呼叫自己以後回傳的結果就好
【例題】
費波那契數列的定義如下
第0項是0
第1項是1
之後每一項都是他前一項和前兩項的和
例如它的前10項為:0,1,1,2,3,5,8,13,21,34
它定義都告訴你了
那程式就照寫就好囉
int f(int n)
{
if(n==0) return 0;
else if(n==1) return 1;
else return f(n-1)+f(n-2);
}
遞迴的好處在這裡
例如f(5),我並不需要知道怎麼憑空生出f(5)的答案
我只需要知道它是f(3)+f(4)就好
至於f(3)和f(4)我也無法憑空生出來
反正呼叫自己來算就好
既然既然有把函式的定義寫對
就可以完全相信它回傳的答案會是對的!!
【例題】
輸入一個n
輸出在算出n的費波那契時,總共呼叫了幾次函式
例如n為0或1時,因為沒有夢中夢,所以答案都是1次
n為2時,需要分成n為0和n為1各算一次,再加上自己這次,所以是1+1+1=3
n為3時,需要分成n為1和n為2各算一次,再加上自己這次,所以是1+3+1=5
n若不是0或1,就會是n-1的答案加上n-2的答案,再加上這次的1
int Q(int n)
{
if(n==0) return 1;
else if(n==1) return 1;
else return Q(n-1)+Q(n-2)+1;
}
【學生練習題】
這題就是剛才介紹的f和Q兩個函式喔
圖片來自維基百科
這個玩具的玩法是這樣的
有三根柱子
第一根柱子放滿了圓盤
最小的在上面 最大的在下面
想要每次移動一個圓盤
來把所有的圓盤都移到第3根柱子上
其中移動的過程中每次只能拿一個
且絕對不能有大圓盤蓋在小圓盤上的情況發生
由於為了讓大圓盤不蓋在小圓盤上
會需要有第2根柱子來當成中繼站
例如可以把小圓盤先放在中繼站
把大圓盤放到目的地後
在把小圓盤疊到目的地上
這樣一來就可以移動這兩個大小圓盤
又可以不會讓大的蓋小的
搬的步驟由你來設計
但希望讓步驟最少
給你圓盤的總量
請你印出移動的步驟
例如當圓盤的總量是2時
印出
Ring 1 from 1 to 2
Ring 2 from 1 to 3
Ring 1 from 2 to 3
意思是
先把一號圓盤(最小的圓盤)搬到中繼站
再把二號圓盤(最大的圓盤)搬到目的地
最後再把一號圓盤從中繼站搬到目的地
雖然看起來很簡單
但n大起來時會變非常複雜喔
像是這個圖片是n為3時的解答
但它怎麼知道該這樣做呢?
當然是程式拉
(取自家象)
「將3個盤子從A移到C」
可以想成
「將上面2個盤子用『不知名神奇方法』從A移到B」
「將第3個盤子直接從A移到C」
「將上面2個盤子用『不知名神奇方法』從B移到C」
我不需要知道上面2個盤子是怎麼移的
我只需要相信它會神奇的幫我移好
所以我要做的事情是
還要考慮最邊界的條件:
當然如果盤子只有一個
那直接移到目的地就好
其中哪根柱子是起點、哪根是目的地、哪根是中繼站
也要從參數傳個仔細,來填填看吧
void hanoi(int n, int source, int destination, int temp)
{
if(n==1){
cout << "Ring 1 from " << source << " to " << destination << endl;
}
else{
hanoi(n-1, ___, ___, ___);
cout << "Ring " << n << " from " << source << " to " << destination << endl;
hanoi(n-1, ___, ___, ___);
}
}
int main()
{
int n;
cin >> n;
hanoi(n, ___, ___, ___);
}
【學生練習題】
盡量不要複製變填充題
自己寫更有意思哦!
上一章:大數運算
下一章:排序
回目錄:國立科學園區實驗中學C++程式語言自學講義