天天看點

引用參數與引用傳回值引用參數與引用傳回值

轉自:http://www.cnblogs.com/bigshow/archive/2008/11/10/1330514.html

引用參數與引用傳回值

    經常看到這樣的聲明:T& func(T& t),這種聲明和T func(T t)有什麼差別?書上的解釋是為了提高效率,究竟是如何提高效率的呢?内部執行了什麼操作?本文通過8個小例子對引用參數和引用傳回進行了一次徹底的排查。

    首先看一下在類的成員函數中的引用參數和引用傳回值:

類定義class A

{

     public:

      int x;

      A(){}//構造函數

      A(const A& other)//拷貝構造函數

      {

            this->x = other.x;

            cout << "Copy" << endl;

      }

      ~A(){}//析構函數

      A& operator=(const A& other)//指派函數

      {

            this->x = other.x;

            cout << "Assign" << endl;

            return *this;

      }

      void func1(A a)

      {

      }

      void func2(A& a)

      {

      }

      A func3()

      {

            return *this;

      }

      A& func4()

      {

            return *this;

      }

};

    這個類很簡單,隻有一個成員變量x,并且定義了預設構造函數、拷貝構造函數、析構函數和指派函數。為了能夠更清楚地看到哪個拷貝構造函數與指派函數是否被調用,在這兩個函數中添加了一些輸出資訊。

    類中還定義了四個成員函數,下面分别分析這四個函數的執行情況。

    (1) 在main()函數中調用func1():

調用func1()int main()

{

      A a1, a2;

      a2.func1(a1);

      return 0;

}

func1()輸出結果Copy     為什麼會有這樣的輸出結果呢?這是由于func1()中傳遞的是值參數,是以在執行函數體之前會 先産生一個臨時對象,然後調用類的拷貝構造函數初始化這個臨時對象,進而輸出了"Copy"。 在函數内部操作的是這個臨時對象,對臨時對象所做的任何修改不會反映到函數的實參上。

    (2) 在main()函數中調用func2()以與func1()對比:

調用func2()int main()

{

      A a1, a2;

      a2.func2(a1);

      return 0;

}

func2()輸出結果

    結果什麼也沒有輸出。

    這是由于傳入的是一個引用參數,是以在函數内部不需要産生一個臨時對象來儲存對象資訊,是以不會調用拷貝構造函數。這就是引用參數的作用,減少一次對象的拷貝,提高了函數的效率。

    (3) 在main()函數中調用func3():

調用func3()int main()

{

      A a1, a2;

      a2 = a1.func3();

      return 0;

}

func3()輸出結果 Copy

Assign

    為什麼會輸出"Copy"呢?這是因為函數采用的是值傳回,是以為了儲存傳回值,需要先建立一個臨時對象,然後調用類的拷貝構造函數将*this的内容拷貝到這個臨時對象中,再将臨時對象傳回。最後通過指派函數将該臨時對象的内容指派給新對象。

    (4) 在main()函數中調用func4()以與func3()對比:

調用func4()int main()

{

 A a1, a2;

 a2 = a1.func4();

 return 0;

}

func4()輸出結果Assign

    隻調用了指派函數,這是引用函數采用的是引用傳回,是以直接傳回對象自身的引用*this,不需要建立臨時對象來儲存對象資訊,是以不會調用拷貝構造函數。最後通過指派函數直接将對象本身的内容指派給新對象。這就是引用傳回值的作用,減少了一次對象的拷貝,提高了函數的效率。

    總結一下:在類的成員函數中,使用引用參數和引用傳回值都不需要産生臨時對象,減少了一次對象的拷貝,提高了函數的效率。

    那麼,如果将參數作為傳回值傳回,并且用引用接收傳回值将會産生什麼效果呢?下面定義四個全局函數:

全局函數A& func5(A& a)

{

      return a;

}

A& func6(A a)

{

      return a;

}

A func7(A& a)

{

      return a;

}

A func8(A a)

{

      return a;

}

    (5) 在main()函數中調用func5():

調用func5()int main()

{

      A a1;

      a1.x = 1;

      A& a2 = func5(a1);

      a1.x++;

      cout << a1.x << endl;

      cout << a2.x << endl;

      return 0;

}

func5()輸出結果 2

2

    func5()采用了引用參數,并且以引用傳回值的方式傳回了該參數,是以a2是a1的一個引用,對a1的任何改變都會反映到a2上,是以a1、a2的成員變量x的值相同。

    (6) 在main()函數中調用func6():

調用func6()int main()

{

      A a1;

      a1.x = 1;

      A& a2 = func6(a1);

      a1.x++;

      cout << a1.x << endl;

      cout << a2.x << endl;

      return 0;

}

編譯的時候會報一個警告:

警告warning C4172: returning address of local variable or  temporary

func6()輸出結果Copy

2

4198610

    警告的意思就是傳回了一個局部變量的引用,這種用法實際上是錯誤的。局部變量在函數傳回前就會被釋放,是以實際上a2引用到的一塊不可知的記憶體,這從輸出的a2.x的值"4198610"也可以看出來。至于輸出"Copy",是因為采用的是值參數,上面已經讨論過,這裡不再贅述。

    (7) 在main()函數中調用func7():

調用func7()int main()

{

      A a1;

      a1.x = 1;

      const A& a2 = func7(a1);

      a1.x++;

      cout << a1.x << endl;

      cout << a2.x << endl;

      return 0;

}

func7()輸出結果Copy

2

1

    這是一種比較特殊的用法,由于func7()采用的是值傳回,是以在函數傳回前将會産生一個臨時對象,并執行一次拷貝構造函數。這樣相當于a2引用了一個臨時對象。前面曾經說過,臨時對象将會在函數傳回前被釋放,但是為什麼這裡輸出的結果是正常的呢?這是一種特殊情況,C++規定,如果有臨時對象有一個引用,那麼這個臨時對象的生存期将延長到和這個引用相同。這樣就可以解釋上面的輸出結果了:a2引用了一個臨時對象,而不是引用了a1,是以a1的任何改變不會影響到a2。

    注意:在VC編譯環境下,const A& a2 = func7(a1);這行語句前面可以不加"const",但是在g++或者其他版本的編譯器中不加"const"将會産生編譯錯誤。加上"const"更加符合C++标準的規定,因為臨時對象不可見,不允許通過該引用來改變臨時對象的内容。

    (8) 在main()函數中調用func8():

調用func8()int main()

{

      A a1;

      a1.x = 1;

      const A& a2 = func8(a1);

      a1.x++;

      cout << a1.x << endl;

      cout << a2.x << endl;

      return 0;

}

func8()輸出結果Copy

Copy

2

1

    通過以上的分析,對這個輸出結果也就很好了解了:由于采用的是值參數,是以在函數體執行前會調用一次拷貝構造函數;采用的是值傳回值,是以在函數傳回前又會調用一次拷貝構造函數,這就是前兩個"Copy"的由來。另外,a2引用的是一個臨時對象,而不是引用了a1,是以a1的任何改變不會影響到a2。

    總結一下:

    如果使用引用接收引用傳回值,則傳回的引用必須具有較長的生存期,不可以引用局部變量。

    如果使用引用接收值傳回值,則引用了一個臨時對象,該對象的生存期将延長到和這個引用相同。

PS:

原文:http://www.vchelp.net/wyy/paper/retconst.asp

第三點:

最後,回到你的問題上:

int f(void)

{

    int i=0;

    return i;

}

那麼調用 f( )=1; 是不行的,因為這裡傳回的是一個值,而不是一個變量。是以不能指派。

那麼傳回一個對象時為什麼能夠改變,看這裡的代碼:

class C1

{

public:

    C1(){iNo=0;};

    void Change(){iNo++;};

public:

    int iNo;

};

C1 f2(void)

{

    C1 c1;

    return c1;

}

int _tmain(int argc, _TCHAR* argv[])

{

    f2().Change(); //OK

}

為什麼能夠成功?因為相當于 

CA& c=f2();

c.Change();

這是在使用臨時變量的引用,而不是在使用變量的值。

第四點:

這并不是好的程式設計習慣,盡量不要這麼做。