C語言-函數與遞迴

簡介

當程式發展越來越大時,數千數萬行的main函數是不實際的

所以我們將重複的細節做切割,將其模組化,讓main函數可以重複使用

模組化的好處就是容易除錯且較有彈性,對於程式未來的發展是有益的

函數定義

函數分為回傳值、函數名稱、參數列與函數主體,前三項被合稱為函數原型(Function prototype)

宣告語法如下:

1
回傳值 函數名稱(參數列);

例如:

1
int max(int,int);

如果函數不回傳任何值的話,回傳值則寫void,如果不傳入任何參數,參數列保持空白就行

函數原型事先定義了該函數只接受什麼,會回傳什麼,如果在呼叫過程中不符合函數原型

編譯器將會告知有錯誤產生,例如:

1
max(num1,ch); // ch是字元

接著就可以根據函數原型實作函數本體了,函數本體的語法如下:

1
2
3
回傳值 函數名稱(參數列){
函數主體
}

例如:

1
2
3
int max(int num1,int num2){
return (num1 > num2? num1: num2);
}

以下是呼叫函數的範例程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int max(int,int);
int main(void) {
int num1 = 100,num2 = 200;
printf("最大值為:%d\n",max(num1,num2));
return 0;
}
int max(int num1,int num2){
return (num1 > num2? num1: num2);
}

執行結果

最大值為:200

函數是可以互相呼叫的,範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
void fun1();
void fun2();
int main(void) {
fun1();
return 0;
}
void fun1(){
printf("hello ");
fun2();
}
void fun2(){
printf("world\n");
}

執行結果

hello world

變數可視範圍與生命週期

變數可視範圍指的是能夠使用變數的區塊

可視範圍分為全域變數、區域變數跟區塊變數

變數生命週期指的是變數有效的時間

全域變數是指宣告在(main)函數以外的變數,當下整段程式都看得到該變數的存在並呼叫使用

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
const double PI = 3.14159;
double area(double);
int main(void) {
double radius;
printf("請輸入半徑:");
scanf("%lf",&radius);
printf("圓面積:%.2f^2 * %f = %f\n",radius,PI,area(radius));
return 0;
}
double area(double radius){
return radius*radius*PI;
}

執行結果

請輸入半徑:3
圓面積:3.00^2 * 3.141590 = 28.274310

所有變數都設為全域變數是不應該的,這樣會發生變數名稱管理問題,造成維護困難

全域變數的生命週期開始於程式開始,結束於程式結束

區域變數指的是在(main)函數內或函數的參數列的變數

只有該函數可以使用,其他地方無法存取,範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
void fun1();
void fun2();
int x = 1;
int main(void) {
int x = 2;
printf("這邊看到的是區域變數\t%d\n",x);
fun1();
fun2();
return 0;
}
void fun1(){
x*=10;
printf("這邊看到的是全域變數\t%d\n",x);
}
void fun2(){
int x = 3;
printf("這邊看到的是區域變數\t%d\n",x);
}

執行結果

這邊看到的是區域變數    2
這邊看到的是全域變數    10
這邊看到的是區域變數    3

區域變數的生命週期開始於函數開頭,結束於函數結尾

區塊變數指的是在while、if-else等在大括號內的變數,範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
void fun1();
void fun2();
int x = 100;
int main(void) {
int x = 200;
int i;
printf("這邊看到的是區域變數\t%d\n",x);
fun1();
fun2();
for(i = 1; i <= 5; i++){
int x = i*2;
printf("這邊看到的是區塊變數\t%d\n",x);
}
return 0;
}
void fun1(){
x*=10;
printf("這邊看到的是全域變數\t%d\n",x);
}
void fun2(){
int x = 300;
printf("這邊看到的是區域變數\t%d\n",x);
}

執行結果

這邊看到的是區域變數    200
這邊看到的是全域變數    1000
這邊看到的是區域變數    300
這邊看到的是區塊變數    2
這邊看到的是區塊變數    4
這邊看到的是區塊變數    6
這邊看到的是區塊變數    8
這邊看到的是區塊變數    10

這邊要記住的是當可視範圍大的變數與可視範圍小的變數發生同名時

範圍小的變數會暫時覆蓋範圍大的變數,稱之為變數覆蓋

遞迴

函數除了可以呼叫別的函數,也可以呼叫自己,稱為遞迴

以下是Fibonacci數列作為使用遞迴例子

Fibonacci數列的第定義為

$$ Fibonacci(N) =
\begin{cases}
1, & \mbox{if }N\mbox{ = 1 or }N\mbox{ = 2}\\
fibonacci(N-1) + fibonacci(N-2), & \mbox{if }N \ge 3
\end{cases} $$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int fibonacci(int);
int main(void) {
int N = 0;
printf("請輸入第幾項:");
scanf("%d",&N);
printf("fibonacci數列第%d項為:%d",N,fibonacci(N));
return 0;
}
int fibonacci(int N){
if(N == 1 || N ==2) return 1; //終止條件
else return fibonacci(N-1) + fibonacci(N-2);
}

執行結果

請輸入第幾項:7
fibonacci數列第7項為:13

遞迴看起來似乎可以當作迴圈來用,但迴圈跟遞迴哪個好並沒有一定的答案

迴圈寫起來較複雜,但沒有記憶體堆疊的問題

遞迴較容易理解,但有執行效率上的問題

選用哪個都要看需求如何再決定

關於堆疊在資料結構會提到

參考

  1. C語言
  2. 函式簡介
  3. 引數傳遞、傳回值
  4. 變數、函式可視範圍(static 與 extern)
  5. 遞迴(Recursion)