☛ 傳值方式 ( 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[?] 指到正確的陣列位置。