原帖地址: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。
總結一下:
如果使用引用接收引用傳回值,則返回的引用必須具有較長的生存期,不可以引用局部變數。
如果使用引用接收值傳回值,則引用了一個臨時對象,該對象的生存期將延長到和這個引用相同。