講師: 李根逸 (Ken-Yi Lee), E-mail: feis.tw@gmail.com
函式
【第五講】
!153
課程⼤大綱
!154
函式宣告 [P.155]
函式呼叫 [P.156]
C 標準函式庫 [P.157]
數學函式庫 [P.158]
函式定義 [P.160]
定義數學函式 [P.163]
定義模組化函式 [P.166]
變數可視範圍 [P.167]
在 for 的⼩小括號內宣告變數 [P.170]
產⽣生亂數 [P.173]
產⽣生夠亂的亂數 [P.175]
函式遞迴 [P.176]
函式宣告
函式的概念來⾃自於數學。可以把每個函式想成是⼀一個 程式模組,包含⼀一段程式⽚片段,具有某種設計好的功 能。
!
函式宣告:
!
!
⼀一個函式可以接受零到多個參數輸⼊入,並回傳⼀一個值 在函式宣告時,參數名稱不是必要可以省略:
輸⼊入參數
函式
回傳值回傳值的資料型態 函式名稱(參數資料型態 [參數名稱], ...);
double sqrt(double x);
!155
double sqrt(double);
函式呼叫
函式呼叫語法:
函式名稱([引數值], ...) 範例:
呼叫函式時,會將函式需要的引數值算出後再執⾏行函 式。函式執⾏行完後則會回傳⼀一個回傳值型態的值 (暫 時變數)
!
!
在程式碼呼叫函式之前需要宣告或定義函式
!156
sqrt(4.0)
double s = sqrt(2.0*v);
-> double s = sqrt(2.0*2.0);
-> double s = sqrt(4.0);
-> double s = 2.0;
double v = 2.0;
C 標準函式庫
C 語⾔言有內建已經寫好的的函式庫,例如 sqrt (求 平⽅方根) 這類的數學函式 :
!
!
!
呼叫 sqrt 需引⽤用 <math.h> (內含 sqrt 宣告)
除此之外還有 printf、scanf 和 system 這種⾮非數 學函式 :
printf
“Hello world” 11
4.0
sqrt
2.0!157
sqrt(4.0) == 2.0
printf(“Hello world”) == 11
<math.h> 數學函式庫
常⾒見的數學函式 說明
double sqrt(double x ) 回傳 x 的平⽅方根 double exp(double x) 回傳 e 的 x 次⽅方
double log(double x) 回傳 x 的⾃自然對數 (底為 e) double fabs(double x) 回傳 x 的絕對值
double ceil(double x) 回傳不⼩小於 x 的最⼩小整數 double floor(double x) 回傳不太於 x 的最⼤大整數 double pow(double x, double y) 回傳 x 的 y 次⽅方
double fmod(double x, double y) 回傳 x 除以 y 的浮點餘數 double sin(double x) 回傳 x 的正弦值
double cos(double x) 回傳 x 的餘弦值 double tan(double x) 回傳 x 的正切值
[查詢函式] Windows: MSDN 網站, Linux 或 Unix-like: man 指令
!158
【補充】 C 的標準函式庫
<assert.h> 協助偵錯函式
<ctype.h> 字元處理
<errno.h> 錯誤處理函式
<float.h> 浮點數相關定義
<limits.h> 資料型態屬性
<locale.h> 地區相關
<math.h> 數學
<setjmp.h> 在函式間跳躍
!159
<signal.h> 訊號 (signal) 處理
<stdarg.h> 使⽤用不固定個數函式參 數
<stddef.h> 標準定義
<stdio.h> 輸⼊入輸出
<stdlib.h> ⼀一般函式
<string.h> 字串處理
<time.h> 時間計算
函式定義
函式定義語法 :
!
!
同⼀一函式只能定義⼀一次,且不可在函式内定義函式
範例 : max2(int,int) 是⼀一個會回傳兩整數最⼤大值 的函式
回傳值的資料型態 函式名稱(參數資料型態 參數名稱, ...) { /* 程式碼內容 */
return 回傳值; }
int max2(int x, int y) { if (x >= y) {
return x;
} else {
return y;
} } main 也是⼀一個函式,會在程式⼀一開始執⾏行時被呼叫
!160
/* 求兩整數最⼤大值 */
#include <stdio.h>
#include <stdlib.h>
int max2(int x, int y) { if (x >= y) {
return x;
} else {
return y;
} }
int main() { int a, b;
printf("請輸⼊入⼀一個整數:");
scanf("%d", &a);
printf("請輸⼊入另⼀一個整數: ");
scanf("%d", &b);
printf("其中⽐比較⼤大的整數是 %d\n", max2(a, b));
system("pause");
return 0;
}
將 定 義 放 置 在 呼 叫 前 可以省去宣告的動作 函式定義
(函式的真正內容所在)
函式呼叫 (執⾏行指定的函式)
!161
/* 求兩整數最⼤大值 */
#include <stdio.h>
#include <stdlib.h>
int max2(int, int);
int main() { int a, b;
printf("請輸⼊入⼀一個整數:");
scanf("%d", &a);
printf("請輸⼊入另⼀一個整數: ");
scanf("%d", &b);
printf("其中⽐比較⼤大的整數是 %d\n", max2(a, b));
system("pause");
return 0;
}
int max2(int x, int y) { if (x >= y) {
return x;
} else {
return y;
} }
函式宣告
(跟變數⼀一樣,在呼叫函 式之前我們需要先宣告)
函式呼叫 (執⾏行指定的函式)
函式定義
(函式的真正實作所在)
將定義放置在呼叫前後 都可以,但是在呼叫前
⼀一定要宣告或定義
函式呼叫時會先將參數 的值算出後再傳⼊入函式
!162
【思考】為什麼要分成宣告跟定義?
!163
【範例】定義數學函式
試寫⼀一名為 myabs 的函式,傳⼊入⼀一個整數後回傳該 整數的絕對值
!
!
!
!
試寫⼀一名為 mypow 的函式,傳⼊入底數 x 與指數 y 兩個浮點數 (double) 後,回傳 x 的 y 次⽅方是多 少 ?
【範例】 mypow.cpp
【範例】 myabs.cpp
【範例】⽐比⼤大⼩小
試寫⼀一函式 max3 讓使⽤用者輸⼊入三個數字,並回傳 三個數字中最⼤大的
提⽰示 : 可以利⽤用 max2 來幫你完成!
!164
【範例】max3.cpp
main
max3
傳⼊入引數
回傳值
max2
max2
!165 max3(3, 5, 4)
int max3(int a, int b, int c) { return max2(max2(a, b), c);
}
max2(3, 5)
max2(5, 4)
3, 5, 4
5
3, 5
5, 4 5
5
可以利⽤用呼叫其他函式來完成
⼀一個函式是 C 語⾔言的重要⼿手法
【範例】定義模組化函式
修改範例,將『畫 N 個星星』模組化
這裡我們寫⼀一個函式
void draw_stars(int len) 來
畫 len 個星星Please enter an integer: 3
* **
***
Please enter an integer: 5
* **
*** ****
*****
void 是個特殊的資料型 態,代表虛無的意思,
這裡⽤用在不需要回傳值 的函式。當回傳值型態 是 void 時,return 後不接回傳值。
!166
【範例】triangle.cpp
變數可視範圍 (scope)
我們可以依照變數宣告在程式碼的位置分為兩種變數:
全域變數 (global variable)
宣告在函式外。因為容易造成名稱污染,請避免使⽤用全域變數
區域變數 (local variable)
宣告在區塊内 (成對的左右⼤大括號)
⼀一個變數可以被某段程式碼使⽤用要同時滿⾜足兩個條件:
該變數是全域變數或宣告該變數的區塊包含這段程式碼 這段程式碼位於該變數宣告以後
變數⽣生命週期
:
全域變數:程式開始時配置記憶體,程式結束時釋放 區域變數:宣告時配置記憶體,離開區塊時釋放
!167
#include <stdio.h>
#include <stdlib.h>
int i = 5;
void p(){
/* int i = -1; */
i = i + 1;
printf("%d\n", i);
return;
}
int main(){
printf("%d\n", i);
int i = 6;
i = i + 1;
p();
printf("%d\n", i);
system("pause");
return 0;
}
這程式的執⾏行結果是 ?
!168
#include <stdio.h>
#include <stdlib.h>
int main(){
int i = 3;
printf("%d\n", i);
if (i == 3) { i = i + 1;
int i = 6;
printf("%d\n", i);
i = i + 1;
}
if (i == 3) {
printf("%d\n", i);
}
system("pause");
return 0;
}
這程式的執⾏行結果是 ?
!169
在迴圈前宣告的變數,在迴圈結束後⼀一樣可以存取:
!
!
!
在 C99 或 C++ 我們可以在 for ⼩小括號的初始式內 宣告變數,此時的變數在迴圈執⾏行過程中指的是同⼀一 個,⽽而離開迴圈後該變數就離開可視範圍:
!170
int i = 1;
while (i <= 10) {
printf(“%d\n”, i);
i++;
} printf(“%d\n”, i);
for (int i = 1; i <= 10; i++) { int j = i;
printf(“%d\n”, j);
}
printf(“%d\n”, i); /* 不合法 */
在 for 的⼩小括號内宣告變數
int i;
for (i = 1; i <= 10; i++) { printf(“%d\n”, i);
} printf(“%d”, i);
這個特性可以讓 for 計數⽤用的 變數不會污染到後⾯面的程式碼
!171
【補充】靜態修飾字 (static)
除了之前所提的全域變數和區域變數外,我們在變數 前可以加上 static 修飾字:
加在全域變數前:
該變數的⽣生命週期不變
該變數的可視範圍由全程式變成該檔案本⾝身
加在區域變數前:
該變數的⽣生命週期與全域變數相同,於整個程式開始時配置,結束 時釋放
該變數從程式開始到結束只會初始化⼀一次 該變數的可視範圍不變
【範例】triangle.cpp
【補充】函式的引數預設值
我們在定義函式的參數時可以給予預設值:
例如 :
!
當呼叫 max3(3, 4) 時,就會如同呼叫 max3(3, 4, 0)
注意的是,在有多個參數時,前⾯面的參數如果設定有預設值的話,
後⾯面的參數也⼀一定要有:
這個是不合法的:
!172
int max3(int a, int b, int c = 0) { ... }
int max3(int a, int b = 0, int c) { ... }
產⽣生亂數
C 標準函式庫中包含了⼀一個能產⽣生整數亂數的函式 :
int
rand(): 回傳⼀一⻑⾧長整數型態的亂數 [宣告於 stdlib.h]每次呼叫 rand() 函式都會得到隨機的回傳值
#include <stdio.h>
#include <stdlib.h>
int main() {
for (int i = 1; i <= 5; i++) {
printf(“產⽣生的亂數: %d\n”, rand());
}
system(“pause”);
return 0;
}
!173
【範例】擲骰⼦子
試寫⼀一程式讓電腦亂數產⽣生⼗十個⼀一到六之間的數字,
模擬擲骰⼦子的結果
提⽰示: rand() 的回傳值的範圍是 [0, RAND_MAX],我 們可以利⽤用求餘數 (%) 運算⼦子來求取某固定範圍的整 數亂數
!174
【範例】dice.cpp
產⽣生夠亂的亂數
電腦產⽣生亂數是利⽤用內定的⼀一個亂數表,因此我們必 須利⽤用 <stdlib.h> 內的 srand 函式適當的選取第
⼀一個開始的亂數種,否則亂數出現的順序將是固定可 預測的
可以⽤用 srand(time(0)) 指令初始化亂數表
void srand(unsigned int seed):⽤用 seed 初始化亂數表 time_t time(time_t *):回傳⺫⽬目前時間 [宣告於 time.h]
int main(){
srand(time(0));
printf(“A random number: %d\n”, rand());
system(“pause”);
return 0;
}
!175
函式遞迴
遞迴是函式應⽤用上最迷⼈人⽽而容易困惑的⼀一種觀念 函式遞迴指的是在函式定義内⼜又呼叫同名函式 :
在 test 函式定義內⼜又呼叫 test,試想這樣會產⽣生怎 樣的結果 ?
void test() { test();
return;
}
int main() { test();
return 0;
}
!176
main
test test test
1: void test() { 2: test();
3: return;
4: }
5: 6: int main() { 7: test();
8: return 0;
9: }
!177 test() test() test() test()
注意這裡每個 test 都是分別存在的
【範例】數數字
[1]試想⼀一下下列程式碼會產⽣生怎樣的結果?
#include <stdio.h>
#include <stdlib.h>
void countdown(int count) { printf("%d\n", count);
if (count != 0) {
countdown(count-1);
}
return;
}
int main() {
countdown(2);
system("pause");
return 0;
}
!178
【範例】countdown_1.cpp
!179
main
2
1
0
countdown
countdown
countdown(2)
countdown(1)
countdown
countdown(0)
int main() {
!180
countdown(2);
return 0;
} void countdown(int count=2) { printf("%d\n", count);
if (count != 0) {
countdown(count-1);
}
return;
} void countdown(int count=1) { printf("%d\n", count);
if (count != 0) {
countdown(count-1);
}
return;
} void countdown(int count=0) { printf("%d\n", count);
if (count != 0) {
countdown(count-1);
}
return;
}
輸出:
2 1 0
【範例】數數字
[2]試想⼀一下下列程式碼會產⽣生怎樣的結果?
#include <stdio.h>
#include <stdlib.h>
void countdown(int count) { if (count != 0) {
countdown(count-1);
}
printf("%d\n", count);
return;
}
int main() {
countdown(2);
system("pause");
return 0;
}
!181
【範例】countdown_2.cpp
int main() {
!182
countdown(2);
return 0;
} void countdown(int count=2) { if (count != 0) {
countdown(count-1);
}
printf("%d\n", count);
return;
} void countdown(int count=1) { if (count != 0) {
countdown(count-1);
}
printf("%d\n", count);
return;
} void countdown(int count=0) { if (count != 0) {
countdown(count-1);
}
printf("%d\n", count);
return;
}
輸出:
0 1 2
【範例】求數字和
試寫⼀一 sum 的函式去計算 1+2+...+N 的值
sum(N) == N + sum(N-1) 且 sum(1) == 1
!
!
!
!
!
!
可以⽤用迴圈 (while 或 for) 來寫嗎 ?
int sum(int N) { if (N == 1) { return 1;
} else {
return N + sum(N-1);
} }
!183
【範例】sum.cpp
【補充】C++ 的函式重載
在 C 語⾔言裡⾯面,同樣名字的函式,只能有⼀一個
在 C++ 語⾔言裡⾯面,同樣名字的函式,只要參數個數 或 型 別 不 同 就 可 以 有 很 多 個 , 稱 為 函 式 重 載 (function overloading)
!184
void print(int v) { printf(“%d\n”, v); } void print(double v) { printf(“%f\n”, v); }
print(3); // 呼叫 print(int)
print(3.0); // 呼叫 print(double)
習題 [1]
[E0501] 試寫⼀一函式 max4 讓使⽤用者輸⼊入四個整 數,並回傳四個數字中最⼤大的
請試著寫出上⾯面兩例在執⾏行時呼叫函式的歷程?
[E0502] 試寫⼀一個 round 函式,傳回輸⼊入參數四 捨五⼊入⾄至整數位後的值
int round(float num) { ... }
數學函式庫 (math.h) 中有⼀一 floor 函式可以將輸⼊入 參數無條件捨去
[E0503] 試寫⼀一個程式讓電腦擲 6000 次骰⼦子,並 統計⼀一到六各個數字分別出現了幾次
!185
習題 [2]
[E0504] 試寫⼀一程式,產⽣生⼀一 0 ⾄至 9 之間整數亂 數,讓使⽤用者猜測該數字為何,直到使⽤用者猜對。
[E0505] 試寫⼀一程式,輸⼊入⼀一元⼆二次⽅方程式的三個 參數 (a, b, c),求該⼀一元⼆二次⽅方程式的實數解
無實數解時請印出警告訊息 ax2+bx+c = 0
<math.h> 内有 sqrt 函式可以⽤用來平⽅方根值
!186
習題 [3]
(遞迴)
[E0506] 試⽤用遞迴函式寫⼀一程式,讓使⽤用者輸⼊入⼀一 正整數 N 後顯⽰示 N! 的值
N! = 1 * 2 * 3 * ... * N = (N-1)! * N
[E0507] Fibonacci 數列 :
0, 1, 1, 2, 3, 5, 8, 13, 21 ...
規則 :
數列中的前兩個數是 0 和 1
數列中的其他的數是前兩個數字的和
試寫⼀一遞迴程式去計算第 N 個的 Fibonacci 數是多少