1. 程式人生 > >C++Primer:函式(引數傳遞:引用形參)

C++Primer:函式(引數傳遞:引用形參)

1.問題的引入

考慮下面不適宜複製實參的例子,該函式希望交換兩個實參的值:

<span style="font-size:18px;">void swap(int v1, int v2)
{
      int temp = v2;
      v2 = v1;
      v1 = temp;
}
int main()
{
      int i = 10;
      int j = 20;
      cout << "Before swap():\ti: "<< i << "\tj: " << j << endl;
      swap(i, j);
      cout << "After swap():\ti: "<< i << "\tj: " << j << endl;
      return 0;
}</span>

編譯並執行程式,產生如下輸出結果:
Before swap(): i: 10  j: 20
After swap():    i: 10  j: 20

這個例子期望改變實參本身的值。但是對於上述的函式定義,swap無法影響實參本身。執行swap時,只交換了實參的區域性副本,而傳遞swap的實參並沒有修改。

為了使swap函式以期望的方式進行工作,交換實參的值,需要將形參定義為引用型別:

<span style="font-size:18px;">// ok: swap acts on references to its arguments
void swap(int &v1, int &v2)
{
int tmp = v2;
v2 = v1;
v1 = tmp;
}</span>
與所有的引用一樣,引用形參直接關聯所繫結的實參,他是實參的一個別名,而不是這些物件所謂的副本。定義引用時,必須用與該引用繫結的物件初始化該引用。引用形參完全以相同的方式進行工作。每次呼叫該函式時,引用形參被建立並與相應的實參關聯。

此時,呼叫該函式時,swap(i,j);

形參v1只是物件i的另一個名字,而v2則是物件j的另外一個名字。對v1的任何修改實際上也是對i的修改。所以此時可以完成兩個值的交換任務。

從C語言背景轉到C++的我們習慣通過傳遞指標來實現對實參的訪問。在C++中,使用引用形參更安全和更自然。

2.使用引用形參返回額外的資訊

通過對上例的討論,可以理解如何利用引用形參讓函式修改實參的值

。引用形參的另一種用法是向主調函式返回額外的結果。函式只能返回單個值,但有些時候,函式有不止一個的內容需要返回。例如,定義一個 find_val 函式。在一個整型 vector 物件的元素中搜索某個特定值。如果找到滿足要求的元素,則返回指向該元素的迭代器;否則返回一個迭代器,指向該 vector 物件的 end 操作返回的元素。此外,如果該值出現了不止一次,我們還希望函式可以返回其出現的次數。在這種情況下,返回的迭代器應該指向具有要尋找的值的第一個元素。如何定義既返回一個迭代器又返回出現次數的函式?可以定義一種包含一個迭代器和一個計數器的新型別。而更簡便的解決方案是給 find_val 傳遞一個額外的引用實參,用於返回出現次數的統計結果:

<span style="font-size:18px;">// returns an iterator that refers to the first occurrence of value
// the reference parameter occurs containn a second return value
vector<int>::const_iterator find_val(
          vector<int>::const_iterator beg, 
          vector<int>::const_iterator end, 
         <span style="color:#cc0000;"><strong> int value</strong></span>, // the value we want
          <strong><span style="color:#ff0000;">vector<int>::size_type &occurs</span></strong>) // number of times it occurs
{
     // res_iter will hold first occurrence, if any
          vector<int>::const_iterator res_iter = end;
          <span style="color:#ff0000;"><strong>occurs = 0;</strong></span> // set occurrence count parameter ///引用,即別名,我們可以在主函式體中查到
          for ( ; beg != end; ++beg)
               if (*beg == value) {// remember first occurrence of value
               if (res_iter == end)
                  { res_iter = beg;}
               ++occurs; // increment occurrence count
          }
          return res_iter; // count returned implicitly in occurs
}</span>

it = find_val(ivec.begin(), ivec.end(), 42, ctr);

呼叫 find_val 時,需傳遞四個實參:一對標誌 vector 物件中要搜尋的元素範圍的迭代器,所查詢的值,以及用於儲存出現次數的size_type 型別物件。假設 ivec 是 vector<int>型別的物件,it 是一個適當型別的迭代器,而 ctr 則是 size_type 型別的變數。

呼叫後,ctr 的值將是 42 出現的次數,如果 42 在 ivec 中出現了,則 it將指向其第一次出現的位置;否則,it 的值為 ivec.end(),而 ctr 則為 0

3. 利用const引用避免複製(const int&)

向函式傳遞大型物件時,需要使用引用形參,這是引用形參適用的另一種情況。雖然複製實參對於內建資料型別的物件或者規模較小的類型別物件來說沒有什麼問題,但是對於大部分的類型別或者大型陣列,它的效率太低了;此外,也會注意某些類型別是無法複製的。使用引用形參,函式可以直接訪問實參物件,而無須複製它。

舉個例子: 我們需要編寫這樣一個功能:我們要比較兩個string物件長度。這個函式需要訪問每個string物件的大小,但是沒有必要修改這些物件。由於string物件可能相當長,所以應該避免採用複製操作。此時,採用const 引用就非常合適。
<span style="font-size:18px;">// compare the length of two strings
bool isShorter(<strong><span style="color:#ff0000;">const string&</span></strong> s1, <span style="color:#ff0000;"><strong>const string&</strong></span> s2)
{
      return s1.size() < s2.size();
}</span>
注意:如果使用引用形參的唯一目的是避免複製實參,則應將形參定義為const引用

4.更靈活的指向const的引用

如果函式具有普通的非const引用形參,則顯然不能通過從const物件進行呼叫。畢竟,此時函式可以修改傳遞進來的物件,這樣就違背了實參的const特性。但比較容易忽略的是,呼叫這樣的函式(非const引用形參)時,傳遞一個右值或具有需要轉換的物件同樣是不允許的
<span style="font-size:18px;">// function takes a non-const reference parameter
int incr(int &val)
{
       return ++val;
}
int main()
{
      short v1 = 0;
      const int v2 = 42;
      int v3 = incr(v1); // error: v1 is not an int
      v3 = incr(v2); // error: v2 is const
      <strong><span style="color:#ff0000;">v3 = incr(0); // error: literals are not lvalues</span></strong>
      v3 = incr(v1 + v2); // error: addition doesn't yield an lvalue
      int v4 = incr(v3); // ok: v3 is a non const object type int
}</span>
問題的關鍵在於:非const引用形參只能與完全同類型的非const物件關聯!!! 舉一個例子:
<span style="font-size:18px;">// returns index of first occurrence of c in s or s.size() if c isn't in s
// Note: s doesn't change, so it should be a reference to const
string::size_type find_char(<span style="color:#ff0000;"><strong>string &s</strong></span>, char c)
{
       string::size_type i = 0;
       while (i != s.size() && s[i] != c)
               ++i; // not found, look at next character
       return i;
}</span>

if (find_char("Hello World", 'o')) // ...

雖然字串字面值可以轉換為string物件,但是上述的呼叫仍然會導致編譯失敗。儘管函式並沒有修改這個形參的值,但是這樣的定義帶來的問題是不能通過字串字面值呼叫函式

應該說,更鼓勵將不加以修改的實參定義為const引用,因為將形參定義為非const引用,將毫不必要地限制了這些函式的使用(比如字面值字串查詢某一元素)!

注意:應當將不需要修改的引用形參定義為const引用。普通的非const引用形參在使用時非常不靈活。非const形參既不允許const物件初始化,也不能用字面值或產生右值的表示式實參初始化,大大地限制了函式功能。

5.傳遞指向指標的引用

假設想編寫一個與前面交換兩個整數的 swap 類似的函式,實現兩個指標的交換。已知需用 * 定義指標,用 & 定義引用。現在,問題在於如何將這兩個操作符結合起來以獲得指向指標的引用。這裡給出一個例子:

<span style="font-size:18px;">// swap values of two pointers to int
void ptrswap(int* &v1, int* &v2)
{
       int *tmp = v2;
       v2 = v1;
       v1 = tmp;
}</span>
形參: int* &v1;
應該從右至左理解:v1是一個引用(別名),與指向int型物件的指標相關聯。也就是說,v1只是傳遞金ptrswap函式的任意指標的別名。
<span style="font-size:18px;">int main()
{
    int i = 10;
    int j = 20;
    int *pi = &i; // pi points to i
    int *pj = &j; // pj points to j
    cout << "Before ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;
    ptrswap(pi, pj); // now pi points to j; pj points to i
    cout << "After ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;
    return 0;
}</span>
解釋:形參實參關係可以理解為 int*  &v1 (pi);  實際上,傳進來了兩個指標,最終完成的是指標的交換!

即指標的值被交換了。在呼叫 ptrswap 時,pi 指向 i,而 pj 則指向 j。在 ptrswap 函式中, 指標被交換, 使得呼叫 ptrswap 結束後, pi 指向了原來 pj所指向的物件。換句話說,現在 pi 指向 j,而 pj 則指向了 i。