2014年12月16日 星期二

C/C++ Functions ( C/C++ 函數說明 )

C 是一個程序導向的程式語言 (procedural-oriented programming language)

所以在設計上會以各種不同的函數 (function) 來達成特定目的

而 C++ 繼承了 C 的特性,再加上物件導向的觀念,因此也可以用 C-style 的方式來撰寫 C++ 程序 (不過這篇主要只會對 C 函式來介紹)

此篇,為函數做了些介紹 (在本文裡,將會交互地使用「函數」及「函式」二詞,意思相同)

I. 函數是什麼? 為什麼要寫函數?

函數,其實就是一些程式碼的集合,基於功能面上的區別而獨立成一個區塊

設計函數,可以讓工作更簡化 (或者說,可以讓一段程式碼重複的被利用 ←這個很重要喔

函數就像是 Doraemon 的道具,可以讓你完成某件任務,而且你可以一再的使用這個道具

或者說像是遊戲中主角的能力,你可以不斷的運用這個角色的能力來幫助你玩遊戲

因此,適時的運用函數可以很有效的協助你解題

它可以讓你把一個大問題拆解成數個小問題,分工的過程中也可協助除錯 (可拆成多個函式個別進行除錯)

II. 什麼樣的內容適合寫成函數?
  1. 基於重複利用的特性,如果有一項功能很常被使用到,它就適合被寫成函式
  2. 這項功能可以明確指出它在做什麼工作
    比如, sqrt 可能就是用來算平方根 (square root) 用的
  3. 各功能間的功能需要畫分清楚,函數內不必 "實作" 其它函數的內容 (可以使用其它函數)
    比如, sqrt 除了算平方根就不再做其它事 
  4. 這個嘛...因為可能的情況無法一一枚舉,就福至心靈想寫函式的時候就來寫囉

III. 函式的組成

要使用函數,就必須提供一些使用條件; 就像玩遊戲你要使用角色能力的話,也需要一些觸發條件

以下就一個簡單的函數來說明函式的組成,雖然這個例子很簡單,但基本上已包含了一個函式應該要具備的所有內容:

int add ( int a, int b )
{
    int sum;
    sum = a+b;
    return sum; 
//return 後若有任何程式碼都不會被執行
}

(請對應上方的顏色)
  1. 函式的回傳值(型態):一個函數可以有也可以沒有回傳值 (見下文說明)。這個地方是用來指定回傳值的型態為何 (此例為 int)
  2. 函數的名稱: 就像角色的能力也都會給予一個名稱,注意函數的名稱最好取個有意義的詞以利辨認。這裡是寫一個回傳兩數和的函數
  3. 小括號:小括號內放置的是此函數的參數 (見次點說明) 就算沒有參數也要有括號
  4. 參數型態參數名稱:一個函數若沒有參數,就只會有空的小括號;而參數須一一註明其型態及名稱,多個參數要用逗號來分隔。此例傳入兩個 int 參數,分別叫做 a、b。
  5. 函數主體:實作各種函式的內容
    這裡可以使用任何 code,包含 if、while、for,甚至呼叫其它程式(也可以呼叫自己本身,此時稱為遞迴)
  6. 回傳值:若有回傳值,則將 return 後面接著的內容傳回呼叫這個函式的地方。此例中回傳 sum 變數給呼叫函數的地方,函數也會立即結束。若回傳值為 void,則此項非必要,但若要函數提前結束時,可用 「return;」來強制函數結束。
  7. 若遇到 return 敍述,則此後的任何程式碼都不會被執行
    一個函式中可以寫多於一個以上的 return 敍述,但是一旦任一行 return 被執行,剩下的 code 都會立刻被捨棄。
    若是一個非 void 的函式沒有任何 return 敍述,在編譯時會出現警告訊息,而其函數的回傳內容無法確定 (undefined behavior)

IV. 函數做了什麼事?

首先,程式會複製一份參數傳進函式的內部以供使用。(實際上,C 裡面所有的程式都是用「複製」的方式來傳遞函數的,不過函數詳細的傳法就不在這篇的討論範圍了)請期待日後的「函數的傳遞方式」一文。

當 參數傳入完成之後,便會開始執行程式主體的內容(也就是被大括號包住的範圍的程式碼)。這個過程就像是在 main 函式裡的執行過程一樣,由上而下地執行,且可以使用 if、while 等區塊。(別忘了,main 也是函式喔,只是比較特別而已)這一塊,通常是函式的核心所在,在這裡會完成函式的主要工作。

最後的回傳值部份,是要告訴上一層呼叫的地方 (caller,相對地,函式是被呼叫的 callee) 那段呼叫部份的 code 會被取代成什麼東西。這個意思是,上層呼叫函式的地方會被取代成回傳的內容。

總結,函式一共會完成(最主要的)兩件事:
  1. 執行函式本體的內容
  2. 將呼叫函式的地方的 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. 怎麼使用函數?

基礎的使用方式如上一節所述的三個例子,直接打上函式的名稱並提供必要的參數(別忘了要用小括號括起來),此函式便會被呼叫。
再來說一些稍微高級一些的用法:
  1. 配合 ?: 來提供多樣化的參數傳遞
    ex. func( var % 2 == 0? 0 : 1 ) // 當 var 為偶數就傳 0 給 func 函式,不然就傳 1
  2. 一函數的回傳值作為另一函數的輸出
    ex. sum( abs( a ), abs( b ) ) // 將 a、b 的絕對值相加起來
  3. 將運算式的結果傳入函式
    ex. abs( 2-8 ) // 將 -6 傳入函式
  4. 多層的呼叫
    ex. printf( "%f", sum( abs( a ), abs( b ) ) ) // 有 printf、sum、abs 三層函式呼叫

最後,希望大家能更了解函式的使用方法囉~

沒有留言:

張貼留言