所以在設計上會以各種不同的函數 (function) 來達成特定目的
而 C++ 繼承了 C 的特性,再加上物件導向的觀念,因此也可以用 C-style 的方式來撰寫 C++ 程序 (不過這篇主要只會對 C 函式來介紹)
此篇,為函數做了些介紹 (在本文裡,將會交互地使用「函數」及「函式」二詞,意思相同)
I. 函數是什麼? 為什麼要寫函數?
函數,其實就是一些程式碼的集合,基於功能面上的區別而獨立成一個區塊
設計函數,可以讓工作更簡化 (或者說,可以讓一段程式碼重複的被利用 ←這個很重要喔
函數就像是 Doraemon 的道具,可以讓你完成某件任務,而且你可以一再的使用這個道具
或者說像是遊戲中主角的能力,你可以不斷的運用這個角色的能力來幫助你玩遊戲
因此,適時的運用函數可以很有效的協助你解題
它可以讓你把一個大問題拆解成數個小問題,分工的過程中也可協助除錯 (可拆成多個函式個別進行除錯)
II. 什麼樣的內容適合寫成函數?
- 基於重複利用的特性,如果有一項功能很常被使用到,它就適合被寫成函式
- 這項功能可以明確指出它在做什麼工作
比如, sqrt 可能就是用來算平方根 (square root) 用的 - 各功能間的功能需要畫分清楚,函數內不必 "實作" 其它函數的內容 (可以使用其它函數)
比如, sqrt 除了算平方根就不再做其它事 這個嘛...因為可能的情況無法一一枚舉,就福至心靈想寫函式的時候就來寫囉
III. 函式的組成
要使用函數,就必須提供一些使用條件; 就像玩遊戲你要使用角色能力的話,也需要一些觸發條件
以下就一個簡單的函數來說明函式的組成,雖然這個例子很簡單,但基本上已包含了一個函式應該要具備的所有內容:
int add ( int a, int b ) { int sum; sum = a+b; return sum; //return 後若有任何程式碼都不會被執行 }
(請對應上方的顏色)
- 函式的回傳值(型態):一個函數可以有也可以沒有回傳值 (見下文說明)。這個地方是用來指定回傳值的型態為何 (此例為 int)
- 函數的名稱: 就像角色的能力也都會給予一個名稱,注意函數的名稱最好取個有意義的詞以利辨認。這裡是寫一個回傳兩數和的函數
- 小括號:小括號內放置的是此函數的參數 (見次點說明) 就算沒有參數也要有括號
- 參數型態及參數名稱:一個函數若沒有參數,就只會有空的小括號;而參數須一一註明其型態及名稱,多個參數要用逗號來分隔。此例傳入兩個 int 參數,分別叫做 a、b。
- 函數主體:實作各種函式的內容
這裡可以使用任何 code,包含 if、while、for,甚至呼叫其它程式(也可以呼叫自己本身,此時稱為遞迴) - 回傳值:若有回傳值,則將 return 後面接著的內容傳回呼叫這個函式的地方。此例中回傳 sum 變數給呼叫函數的地方,函數也會立即結束。若回傳值為 void,則此項非必要,但若要函數提前結束時,可用 「return;」來強制函數結束。
- 若遇到 return 敍述,則此後的任何程式碼都不會被執行
一個函式中可以寫多於一個以上的 return 敍述,但是一旦任一行 return 被執行,剩下的 code 都會立刻被捨棄。
若是一個非 void 的函式沒有任何 return 敍述,在編譯時會出現警告訊息,而其函數的回傳內容無法確定 (undefined behavior)
IV. 函數做了什麼事?
首先,程式會複製一份參數傳進函式的內部以供使用。(實際上,C 裡面所有的程式都是用「複製」的方式來傳遞函數的,不過函數詳細的傳法就不在這篇的討論範圍了)請期待日後的「函數的傳遞方式」一文。
當 參數傳入完成之後,便會開始執行程式主體的內容(也就是被大括號包住的範圍的程式碼)。這個過程就像是在 main 函式裡的執行過程一樣,由上而下地執行,且可以使用 if、while 等區塊。(別忘了,main 也是函式喔,只是比較特別而已)這一塊,通常是函式的核心所在,在這裡會完成函式的主要工作。
最後的回傳值部份,是要告訴上一層呼叫的地方 (caller,相對地,函式是被呼叫的 callee) 那段呼叫部份的 code 會被取代成什麼東西。這個意思是,上層呼叫函式的地方會被取代成回傳的內容。
總結,函式一共會完成(最主要的)兩件事:
- 執行函式本體的內容
- 將呼叫函式的地方的 code 取代為此函數的回傳值
V. 函數的例子
1. 一個接收兩個整數作為參數,並回傳一個整數的函式
int add ( int a, int b ) { int sum; sum = a+b; return sum; }
例:printf( "%d", sum( 2, 3 ) );
首先會執行 sum 函式的本體,然後由於此函式回傳值為 5,所以 printf 裡的 sum( 2, 3 ) 被取代為 5,於是此行變為 printf( "%d", 5 );
2. 一個不接受任何參數並回傳一個浮點數的函式 (也可以寫成double(void))
double PI() { return 3.1415926; }
例:double circle_area = r * r * PI(); → double circle_area = r * r * 3.1415926;
3. 一個不回傳任何東西,但會接受一個字元陣列做為參數的函數(這裡的 void 不可省略)
void modify_str_head( char str[] ) { str[ 0 ] = 'a'; return; str[ 1 ] = 'b'; //這行不會被執行 }
例:modify_str_head( str ); // str is char array
4. 不回傳任何東西,也不接受參數的函式
void init() { /* Do something here. */ }
通常預處理全域變數資料用
VI. 怎麼使用函數?
基礎的使用方式如上一節所述的三個例子,直接打上函式的名稱並提供必要的參數(別忘了要用小括號括起來),此函式便會被呼叫。
再來說一些稍微高級一些的用法:
- 配合 ?: 來提供多樣化的參數傳遞
ex. func( var % 2 == 0? 0 : 1 ) // 當 var 為偶數就傳 0 給 func 函式,不然就傳 1 - 一函數的回傳值作為另一函數的輸出
ex. sum( abs( a ), abs( b ) ) // 將 a、b 的絕對值相加起來 - 將運算式的結果傳入函式
ex. abs( 2-8 ) // 將 -6 傳入函式 - 多層的呼叫
ex. printf( "%f", sum( abs( a ), abs( b ) ) ) // 有 printf、sum、abs 三層函式呼叫
最後,希望大家能更了解函式的使用方法囉~
沒有留言:
張貼留言