每次調用函數時,都會重新創建該函數所有的形參,此時所傳遞的實參將會初始化對應的形參。
形參的初始化與變量的初始化一樣:如果形參具有非引用類型,則復制實參的值,如果形參為引用類型,則它只是實參的別名。
傳值參數
普通的非引用類型的參數通過復制對應的實參實現初始化。當用實參副本初始化形參時,函數並沒有訪問調用所傳遞的實參本身,因此不會修改實參的值。
// return the greatest common divisor
int gcd(int v1, int v2) { while (v2) { int temp = v2; v2 = v1 % v2; v1 = temp; } return v1; }
while 循環體雖然修改了 v1 與 v2 的值,但這些變化僅限於局部參數,而對調用 gcd 函數使用的實參沒有任何影響。於是,如果有函數調用:
gcd(i, j)
則 i 與 j 的值不受 gcd 內執行的賦值操作的影響。
非引用形參表示對應實參的局部副本。對這類形參的修改僅僅改變了局部副本的值。一旦函數執行結束,這些局部變量的值也就沒有了。
指針形參
函數的形參可以是指針,此時將復制實參指針。與其他非引用類型的形參一樣,該類形參的任何改變也僅作用於局部副本。如果函數將新指針賦給形參,主調函數使用的實參指針的值沒有改變。
事實上被復制的指針只影響對指針的賦值。如果函數形參是非 const 類型的指針,則函數可通過指針實現賦值,修改指針所指向對象的值:
void reset(int *ip) { *ip = 0; // changes the value of the object to which ip points
ip = 0; // changes only the local value of ip; the argument
is unchanged }
調用 reset 后,實參依然保持原來的值,但它所指向的對象的值將變為 0。
如果保護指針指向的值,則形參需定義為指向 const 對象的指針:
void use_ptr(const int *p) { // use_ptr may read but not write to *p
}
指針形參是指向 const 類型還是非 const 類型,將影響函數調用所使用的實參。我們既可以用 int* 也可以用 const int* 類型的實參調用 use_ptr 函數;但僅能將 int* 類型的實參傳遞給 reset 函數。這個差別來源於指針的初始化規則。可以將指向 const 對象的指針初始化為指向非 const對象,但不可以讓指向非 const 對象的指針向 const 對象。
值傳遞的局限性
值傳遞並不是在所有的情況下都適合,不適宜值傳遞的情況包括:
當需要在函數中修改實參的值時。
當需要以大型對象作為實參傳遞時。對實際的應用而言,復制對象所付出的時間和存儲空間代價往往過在。
當沒有辦法實現對象的值傳遞時。
傳引用參數
// incorrect version of swap: The arguments are not changed!
void swap(int v1, int v2) { int tmp = v2; v2 = v1; // assigns new value to local copy of the argument
v1 = tmp; } // local objects v1 and v2 no longer exist
這個例子期望改變實參本身的值。但對於上述的函數定義,swap 無法影響實參本身。執行 swap 時,只交換了其實參的局部副本,而傳遞 swap 的實參並沒有修改。
為了使 swap 函數以期望的方式工作,交換實參的值,需要將形參定義為引用類型:
// ok: swap acts on references to its arguments
void swap(int &v1, int &v2) { int tmp = v2; v2 = v1; v1 = tmp; }
與所有引用一樣,引用形參直接關聯到其所綁定的對象,而並非這些對象的副本。定義引用時,必須用與該引用綁定的對象初始化該引用。引用形參完全以相同的方式工作。每次調用函數,引用形參被創建並與相應實參關聯。
使用引用形參返回額外的信息
引用形參的另一種用法是向主調函數返回額外的結果。函數只能返回單個值,但有些時候,函數有不止一個的內容需要返回。
利用引用避免復制
在向函數傳遞大型對象時,需要使用引用形參,這是引用形參適用的另一種情況。雖然復制實參對於內置數據類型的對象或者規模較小的類類型對象來說沒有什么問題,但是對於大部分的類類型或者大型數組,它的效率(通常)太低了;使用引用形參,函數可以直接訪問實參對象,而無須復制它。
編寫一個比較兩個 string 對象長度的函數作為例子。這個函數需要訪問每個 string 對象的 size,但不必修改這些對象。由於 string 對象可能相當長,所以我們希望避免復制操作。使用 const 引用就可避免復制:
// compare the length of two strings
bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
其每一個形參都是 const string 類型的引用。因為形參是引用,所以不復制實參。又因為形參是 const 引用,所以 isShorter 函數不能使用該引用來修改實參。
如果使用引用形參的唯一目的是避免復制實參,則應將形參定義為 const 引用。
