☛ 傳值方式 ( Pass by value )
C++ 預設的參數傳遞方法是傳值 ( Pass by value ),也就是在函式呼叫時,將實際參數的值 copy 給形式參數,如此可保證原實際參數的內容不會被函式所更動:
int funct(int a, int b) { a=100; b=200; return 0; } void main() { int RetVal; int i=5,j=6; RetVal=funct(i, j); //將 i,j 的值 copy 到 funct() 中的 a,b cout<< i << j; //i 仍為 5, j 仍為 6 .... }
由於形式參數所佔的空間是在呼叫函式時由堆疊中配置的,所以在函式結束后,這些形式參數便不復存在了。 當下次再呼叫函式時,也許所配置到的又是另一塊堆疊中的空間。
☛ 傳址方式 ( Pass by address )
除了陣列外,其他型別均可作為傳值的參數。 當參數的 size 很大時,傳值的方式無論在 copy 參數的時間或堆疊的佔用上都會造成相當大的負擔,這時候就應該以傳址方式 ( Pass by address ) 來傳遞參數:
struct St { char a[200]; int i[200]; } int funct(St *); void main() { St s; ...... funct(&s); //將結構體 s 的位址以傳值方式傳入 .... } funct(St *st) //st 須宣告為指位器型別 { st->a[0]='a'; //取用結構體內的成員 .... }
事實上,傳址方式就是傳值方式的一種,只不過所傳的是資料的位址而非內容;然而,使用傳址方式卻可以在函式中經由指位算符 ( * ) 而更改到實際參數的內容。 在許多狀況下會需要這樣的功能:
void swap(int *, int *); void main() { int i=1,j=5; swap(&i, &j); //互換內容 cout << i <<", "<< j; //印出 5, 1 } void swap(int *a, int *b) { int t; t=*a; //經由 *a, *b 來更改 i, j 的內容 *a=*b; *b=t; }
如果使用傳址呼叫,但又不希望實際參數的內容在函式執行期間遭到有意或無意的更改,則可在參數型別的前面加上 const:
funct(const St *st);
則在 funct 中,任何企圖更改 st 所指變數的內容之動作均會造成 Compile-Time error。 C++ 最大的好處之一,就是會盡量為我們找出各種可能潛伏的 Bug,以增加程式的強固性。
函式的傳回值也可以是一個位址,這原理就和參數的傳址呼叫是一樣的。 一個常用的函式庫字串拷貝函式 strcpy() 就是最好的例子,其在 string.h 中的宣告如下:
char *strcpy(char *dest, const char *src);
這個函式會把 src 字串的內容 copy 到 dest 字元陣列中,最後並傳回 dest 字串的位址,在第二個參數前加上 const 表示 src 的值不可被 strcpy() 所更改。 下面是使用範例:
#include <iostream.h> #include <string.h> void main() { char d[40], *s="Flag Publishing Corp."; cout << strcpy(d, s); }
執行結果:
Flag Publishing Corp.
雖然也可以用 cout << d; 來印出結果,但經由傳回值的方式則可使程式較為簡潔,同時也容易串接於運算式中:
cout << strcpy(d, strcpy(e, s)); //s → e → d → cout
☛ 傳參考方式 ( Pass by reference )
傳址方式雖然好用,但在很多狀況下使用起來卻很不自然,每次呼叫時必須用 & 來取址,在函式中又得用 * 來依址取值,所以很容易因為疏忽而造成 bug。
至於傳參考的方式 ( Pass by reference ) 則可以讓實際參數和形式參數成為同一個變數,只不過所使用的名稱和地點不同而已。 也就是說,形式參數會成為實際參數的別名:
void main() { int i=2; ..... funct(i); //i 和 a 代表同一個變數 ..... } void funct(int& a) //a 須宣告為參考型別 { a=5; }
這樣的用法就自然多了,以前面的 swap() 為例:
void swap(int&, int& ); void main() { int i=1, j=5; swap(i,j); ..... } void swap(int& a, int& b) //a 是 i 的別名,b 是 j 的別名 { int t; t=a; //經由 a,b 來更改 i,j 的內容 a=b; b=t; }
當參數的size很大時,傳參考也是一個很好的辦法,同時我們也可以用 const 來防止資料無意間被更改:
class BigData { ...... } funct(const BigData& bd); void main() { BigData a; .... funct(a); }
由於函式的傳回值只能傳回一個資料,當我們希望傳回多個資料,或是資料的 size 很大時,則可用傳址或傳參考的方式來達成。 這兩種方式中又以傳參考較為自然,但它有個缺點就是容易造成假像,使人誤以為是傳值呼叫 ( 因為它們在呼叫函式時的寫法是一樣的 ),結果參數的內容被更改了而不自知;而傳址呼叫的參數由於必須以 & 取址后再傳,所以不易造成這種困擾。
☛ 傳遞多維陣列的參數
陣列本身是不能作為參數來傳遞的,但是它的位址值卻可以:
int a[20]; funct(a); //亦可寫成 funct(&a[0]); ← 實際參數 .... funct(int *a) //亦可寫成 funct(int a[]); ← 形式參數 { //或 funct(int a[20]); ...... }
注意,即使在形式參數中指明瞭陣列的大小 ( 如 int a[20] ),C++ 還是不會為我們檢查陣列範圍的,所以一般都是自行設法來防範超過陣列範圍的存取:
funct(int a[], int size) //連陣列大小一起傳入 { ...... }
至於多維陣列則比較麻煩一點,因為除了第一維度外,我們必須指明其餘的每一個維度之大小:
funct(int a[][15]); //或寫成 funct(int (*a)[15]) { cout << a[1][2]; //印出自 a 算起第 1*15+2 個元素 a++; //a 往後移了 15 個整數 } void main() { int b[3][15]; funct(b); }
上例中,a 是一個指向二維陣列的指位器,當我們用 a 來對陣列做運算時,就必須知道該陣列第二維度的大小,如此才能讓 a[?] 指到正確的陣列位置。