注意:
注意: 由這個角度去看的話, 就算一個函式內的程式碼片段在程式中只出現過一次, 還是值得寫成一個函式的。
注意: 以往常常是由於編譯器有函式名稱長度的限制, 所以名稱才必須很簡短。
等號右邊 getCharFromFile() 是在呼叫一個函式, CPU 除了會執行很多敘述之外, 還會計算一個數值出來, 以便指定給等號左邊的變數 c, 這個數值是在函式內計算出來並且用 return 敘述傳回呼叫函式的, 例如:
在定義函式時我們必須定義傳回值的型態, 這有兩種目的:
是一個自已定義的函式, 可以計算出傳入整數的平方, 並傳回此平方變數, 如果在主程式中用下面的方式呼叫:
當然一個函式也可以不傳回任何數值, 例如:
函式的參數和函式的傳回值是函式內部和呼叫此函式的程式兩端資料及結果傳遞的橋樑, 呼叫端程式要將資料交給函式處理必須籍由參數。 函式執行完畢後如果要將處理的資料交回去給呼叫端函式時, 也要透過參數或是傳回值兩者。
如下例計算陣列中數字的平均值及標準差函式:
函式參數的宣告方法和一般的變數一樣, 需要指明其型態, 需要設計其名稱, 這個名稱和函式內部的變數一樣屬於同一個命名空間, 對於基本型態 (char, short , int , long, float, double) 及指標型態參數而言, 這樣子的定義會保留適當數量的位元組來存放資料, 函式內可以把資料暫存在這個地方, 也可以取用放在這裡的資料, 在函式被呼叫、開始執行之前 CPU 會先計算相對應每一個參數的引數運算式, 將每一個運算式的數值存入相對應的參數變數中, 若參數為陣列或是指標時則引數運算式的結果應該是一個記憶體位址, 否則應該是一個整數或是浮點數。
對於陣列變數、 例如上面程式中的 double data[100] 而言, 只保留一個 double * 指標可以存放的空間, 並不配置 100 個 double 型態的變數。 這是函式參數和一般變數差異最大的地方。 data 是一個指標常數, 就儲存在上面配置的 double * 指標變數空間中。 此指標在函式呼叫時 CPU 由相對應的引數上求得一位址來初始化, 函式內部不能夠再去更改此指標, 函式內部可以透過此指標名稱以陣列的語法 data[i] 或是以指標的語法 *(data) 來存取呼叫此函式的程式內引數陣列的內容, 如果在函式內不允許修改該陣列內容的話, 函式參數宣告時必須宣告 const double data[100]。
請注意函式參數對於函式內部而言, 除了有很特殊的初始化方式之外, 就像是一個一般的變數一樣, CPU 可以暫存資料在裡面, 也可以讀取裡面存放的資料, 當它們參予運算式時, 會依其定義的型態選擇適當的運算子以及型態轉換。
函式參數的宣告對於呼叫端程式而言也具有很重要的意義。 例如:在 math 函式庫中 fabs() 函式的宣告是
因此對於
的動作, 將資料的型態作適當的轉換後才作交換的動作。
函式主體是一對大括號中的複合敘述 (compound statement) 所構成, 這個區塊的最前面可以定義區域變數 (Local variables), 在 ANSI C 及 K&R C 中才要求必須在所有可執行的敘述之前定義變數, C++ 中並沒有這樣的要求, TURBO C 中如果你在 Options/ Compiler / C++ Options 選擇 C++ always 則你用的是 C++ 編譯器, 並不嚴格要求如此, 如果你選擇 CPP extension 的話, 視你的程式檔案的附檔名, 若是 *.c 的話使用 ANSI C 的編譯器。 若是 *.cpp 則使用 C++ 編譯器。
變數定義時, 除了有加 static 的變數之外, 都是使用函式呼叫的堆疊來做為變數的儲存空間。 由於堆疊空間有限, 而且編譯器無法得知程式內演算法的運作狀況。 它無法幫你檢查是否在堆疊上有足夠空間來存放你定義的變數, 所以請小心使用。 除了區域變數之外, 也可以有函式原型的宣告, 但是不能有函式的定義。 (這是和 pascal 不一樣的地方,請留意!) 接在後面的就是可以執行的 C 敘述和下層的區塊 (block) 了。
那麼在呼叫一個函式的時候編譯器如何才能得知此函式到底要傳回什麼型態的資料、 到底該交付給它什麼型態的資料呢? 如想要呼叫的函式在呼叫之前先定義過了的話, 例如:
上面三種情況下, 編譯器在處理函式呼叫敘述時並沒有相關的型態資料, 如果是 ANSI C 或是 C++ 的話, 這種情況下編譯器會產生錯誤訊息, 如果是 K&R 的 C 編譯器的話會用預設的型態, 常常會導致不一致的錯誤 (例如函式本身以 ANSI C 編譯, 而函式呼叫時用 C 預設的型態, 則可能會有型態不一致的狀況發生, 比方說在 TURBC C 中如果你使用 math 函式庫中的 fabs() 或是 sqrt() 函式而沒有 #include <math.h> 的話, 會因為 C 中預設的回傳值型態是 int 而在執行時發現資料的錯誤。)
要保證正確的話, 請使用函式的原型宣告, 例如:
注意: 在函式原型中參數的名稱可以省略, 因為對於呼叫程式來說有關係的是參數的型態而已。
對於 K&R 的 C 來說為了怕這種問題太常發生, 用所謂的 type promotion rule 來做一點保護, 乾脆限定在堆疊上只有幾種長度的資料格式, 一是二個位元組, 二是四個位元組, 三是八個位元組。 只要是 char、short、或是 int 型態就一律使用二個位元組, long 型態用四個位元組,float 或 double 都用八個位元組, 當然資料格式也要同步地做一些轉換, 在這情況下嚴格地來說函式的參數並沒有所謂的 float 型態, 而只有 double 型態, 就算你定義 float 型態的參數如下例:
現在還存在的問題是類似像 printf() 函式的宣告及使用:
其它的一點都不用改, 效果是完完全全一樣的, 關於陣列的宣告與指標的關係請參考陣列進階。
很多人會有疑問說為什麼第一個大小不需要給,
第二個則一定要給?
簡單地說:
第二個大小 20,
是型態 int [20] 的一部分,
如果不指明的話,
param 就不是每一個元素是 "20個整數的陣列" 的陣列了,
第一個大小則可以不寫,
因為根本就不需要知道此陣列有幾個元素 (不需配置記憶體),
詳情請參考陣列進階。
那麼你知道
列印出來的資料代表什麼意義嗎?
是位址,
是 average() 這個函式載入在記憶體內的起始位址,
喔!
我們知道記憶體是一連串的位元沮,
每一個位元組有一個唯一的位址,
程式處理的資料如果放在記憶裡,
必須知道放在記憶體的什麼地方 (也就是位址),
CPU 才能一個指令一個指令地讀入指令暫存器 (IR) 中執行,
因此程式也有一個記憶體中的起始位址,
程式本身由此位址開始存放。
在 C 程式裡當你寫一個函式的位址,
再接著寫一對小括號的參數串列的話就是代表著函式的呼叫
(去那個位址執行存放在那個位址的指令),
例如:
如果你寫
的效果也是一樣的,
(不過還是別盡量蛇添足的好)。
我們也可以宣告一個指標變數來存放像 average 這樣子的函式位址,
例如:
注意:
上面的 pfn1, pfn2, pfn3[0] ... pfn3[19] 都是函式指標變數。
言歸正傳,指標變數到底有什麼用途?
由最簡單的例子說起,
我們用變數 x
而不用常數 3
最主要是不希望永遠只列印 3,
而是不管變數 x 的內容為何我們都將它列印出來。
同樣的道理,我們運用指標變數 xp:
對了,
是為了彈性,
同樣的程式碼,
可以因為變數內資料的不同而做出不同的事。
我們用
當然可以呼叫 average() 函式,
但是用
函式指標是由 C 到 C++ 物件化過程中很重要的一種機制,
運用它,
C++ 很容易地達到物件導向程式中的多型
(polymorphism, 動態繫結、old code call new code),
使得程式的擴展性大大提昇,
軟體的重用性大大增強,
說它是基石應該沒有太大偏差。
詳細內容請參考 C++ 書籍。
回
程式設計課程
首頁
函式指標:
當你定義一個函式如下:
你知道 numElems 代表一個數值,
data 代表一個位址,
i、sum 也都代表一個數值資料,
也就是說下面的 printf() 呼叫可以列印出這些個符號相對應的值或是資料:
printf("%d %x %d %lf\n",numElems, data , i, sum);
printf("%x\n", average);
int x[20];
average(10,x);
&average(10,x);
double (*pfn)(int ,int[]); //宣告式指標變數 pfn
pfn = average; //或 pfn =&average;
(*pfn)(10,x); //呼叫 average 函式傳入 10 及陣列 x
typedef double(*DFN)(int, int[]);
DFN pfn1, pfn2, pfn3[20];
int x;
printf("%d",x);
printf("%d",3);
最主要是不希望程式執行時 10 永遠存入變數 x 之中,
而是不管指標變數 xp 之內容為何變數的位址,
*xp = 10; 可以將 10 存入那一個變數之中。
average(10,x);
除了呼叫 average() 函式之外,
更可以視程式的邏輯、
pfn 函式指標變數的內容來呼叫不同的函式 (但是參數相同)。
範例請見 qsort() 函式之應用。
by Pei-yih Ting
E-mail: pyting@cs.ntou.edu.tw