1. 程式人生 > >C++ 引用作為函式返回值

C++ 引用作為函式返回值

(1)以引用返回函式值,定義函式時需要在函式名前加 &

(2)用引用返回一個函式值的最大好處是,在記憶體中不產生被返回值的副本。

引用作為返回值,必須遵守以下規則:

  • (1)不能返回區域性變數的引用。主要原因是區域性變數會在函式返回後被銷燬,因此被返回的引用就成為了"無所指"的引用,程式會進入未知狀態。
#include <iostream>
using namespace std;

int& test1()
{
	int n = 5;
	return n;
}
 
int main()
{
	int i = test1();
	cout << i << endl;  

	return 0;
}

g++ 5.4.0 編譯報警告,執行報段錯誤!

VS2015編譯報:warning C4172: 返回區域性變數或臨時變數的地址: n

但可正常執行輸出

因為區域性變數n賦值給i時還沒有被新資料覆蓋。

由此可看出g++更加嚴謹,不能返回區域性變數的引用!

補充:

#include <iostream>
using namespace std;

int& test1()
{
	int n = 5;
	return n;
}

int main()
{
	// int i = test1();   //  因為有拷貝操作,g++5.4.0執行將會報段錯誤
	int &i = test1();  //  因為無拷貝操作,g++5.4.0執行將不會報錯
	// cout << i << endl; // g++5.4.0執行將會報段錯誤
	return 0;
}

再舉1例:

#include <iostream>
using namespace std;

int& test1()
{
	int n = 5;
	return n;
}


void test2()
{
	int b = 8;
}


  
int main()
{

	int &i = test1();
	cout << i << endl;  // 輸出 5

	int &b = test1();
	 
	cout << b << endl;  // 輸出 5
	test2();
	cout << b << endl;  // 輸出 8

	int c = test1();
	test2();
	cout << c << endl;  // 輸出 5

	return 0;

}

VS2015編譯報警告:warning C4172: 返回區域性變數或臨時變數的地址: n

但可正常執行輸出:

 

i實際為test1 中區域性變數的別名, 當test1退出時,i 指向的記憶體中值未被覆蓋,直接輸出為5;
輸出第二個b 之前,因為呼叫了test2, 實際指向的記憶體空間已被覆蓋為8,故輸出為8;
c是一個新的變數,其值為5, 故輸出為5;

g++ 5.4.0編譯警告,執行報段錯誤:

  •  (2)不能返回函式內部new分配的記憶體的引用。雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函式內部new分配記憶體的引用),又面臨其它尷尬局面。例如,被函式返回的引用只是作為一 個臨時變量出現,而沒有被賦予一個實際的變數,那麼這個引用所指向的空間(由new分配)就無法釋放,造成memory leak。
  •  (3)可以返回類成員的引用,但最好是const。主要原因是當物件的屬性是與某種業務規則(business rule)相關聯的時候,其賦值常常與某些其它屬性或者物件的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它物件可以獲得該屬性的非常 量引用(或指標),那麼對該屬性的單純賦值就會破壞業務規則的完整性。

1. 用返回值方式呼叫函式

#include <iostream>
using namespace std;

int g_a; // 定義全域性變數g_a

int test() {
	g_a = 168;
	return g_a;
}

int main()
{
	int i = test();
	
	cout << i << endl; 
	return 0;
}

返回全域性變數g_a的值時,C++會在記憶體中建立臨時變數並將g_a的值拷貝給該臨時變數。當返回到主函式main後,賦值語句 i = test()會把臨時變數的值再拷貝給變數i 。

2. 用函式的返回值初始化引用變數

#include <iostream>
using namespace std;

int g_a; // 定義全域性變數g_a

int test() {
	g_a = 168;
	return g_a;
}

int main()
{
	int &i = test();
	
	cout << i << endl; 
	return 0;
}

編譯報錯:error C2440: “初始化”: 無法從“int”轉換為“int &”

注:(有些編譯器可以成功編譯,但會給出一個warning)

分析:這種情況下,函式test()是以值方式返回,返回時,首先拷貝g_a的值給臨時變數。返回到主函式後,用臨時變數來初始化引用變數i,使得i成為該臨時變數的別名。由於臨時變數的作用域短暫(在C++標準中,臨時變數或物件的生命週期在一個完整的語句表示式結束後便宣告結束,也就是在語句int &i = test();之後) ,所以i面臨無效的危險,很有可能以後的值是個無法確定的值。哪怕修改為如下程式碼:

#include <iostream>
using namespace std;

int g_a; // 定義全域性變數g_a

int test() {
	g_a = 168;
	return g_a;
}

int main()
{
	const int &i = test();

	cout << i << endl; 
	return 0;
}

 雖然編譯執行通過,但該問題依然存在!

如果真的希望用函式的返回值來初始化一個引用變數,應當先建立一個變數,將函式的返回值賦給這個變數,然後再用該變數來初始化引用:

int b = test();
int &i = b;

3. 用返回引用的方式呼叫函式

#include <iostream>
using namespace std;

int g_a; // 定義全域性變數g_a

int& test() {
	g_a = 168;
	return g_a;
}

int main()
{
	int i = test();

	cout << i << endl; 
	return 0;
}

這種情況下,函式test()的返回值不產生副本,而是直接將變數g_a返回給主函式,即主函式的賦值語句中的左值是直接從變數g_a中拷貝而來(也就是說i只是變數g_a的一個拷貝而非別名) ,這樣就避免了臨時變數的產生。尤其當變數g_a是一個使用者自定義的類的物件時,這樣還避免了呼叫類中的拷貝建構函式在記憶體中建立臨時物件的過程,提高了程式的時間和空間的使用效率。

4. 用函式返回的引用作為新引用變數的初始化值

#include <iostream>
using namespace std;

int g_a; // 定義全域性變數g_a

int& test() {
	g_a = 168;
	return g_a;
}

int main()
{
	int &i = test();

	cout << i << endl; 
	return 0;
}

這種情況下,函式test()的返回值不產生副本,而是直接將變數g_a返回給主函式。在主函式中,引用變數i用該返回值初始化,也就是說此時i成為變數g_a的別名。由於g_a是全域性變數,所以在i的有效期內g_a始終保持有效,故這種做法是安全的。

5. 可以用函式返回的引用作為賦值表示式中的左值

當函式返回一個引用時,則返回一個指向返回值的隱式指標。這樣,函式就可以放在賦值語句的左邊。例如,請看下面這個簡單的程式:

#include <iostream>
using namespace std;

double vals[] = { 10.1, 12.6, 33.1, 24.1, 50.0 };

double& setValues(int i)
{
	return vals[i];   // 返回第 i 個元素的引用
}

// 要呼叫上面定義函式的主函式
int main()
{

	cout << "改變前的值" << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << "vals[" << i << "] = ";
		cout << vals[i] << endl;
	}

	setValues(1) = 20.23; // 改變第 2 個元素
	setValues(3) = 70.8;  // 改變第 4 個元素

	cout << "改變後的值" << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << "vals[" << i << "] = ";
		cout << vals[i] << endl;
	}
	return 0;
}

輸出:

 

參考連結:http://www.runoob.com/cplusplus/returning-values-by-reference.html