1. 程式人生 > >C++基礎教程面向物件(學習筆記(83))

C++基礎教程面向物件(學習筆記(83))

R值參考

我們需要了解l值和r值,然後告訴你不要太擔心它們。這是C ++ 11之前的建議。但是理解C ++ 11中的移動語義需要重新審視該主題。所以我們現在就這樣做。

L值和r值

儘管名稱中包含“值”一詞,但l值和r值實際上不是值的屬性,而是表示式的屬性。

C ++中的每個表示式都有兩個屬性:一個型別(用於型別檢查)和一個值類別(用於某些型別的語法檢查,例如是否可以將表示式的結果賦值)。在C ++ 03及更早版本中,l值和r值是唯一可用的兩個值類別。

這些表示式是l值和r值的實際定義令人驚訝地複雜,因此我們將簡單地對主題進行簡化,這對於我們的目的來說基本上是足夠的。

將l值(也稱為定位器值)視為函式或物件(或計算為函式或物件的表示式)是最簡單的。所有l值都分配了記憶體地址。

當最初定義l值時,它們被定義為“適合在賦值表示式的左側的值”。但是,稍後,const關鍵字被新增到語言中,並且l值被分成兩個子類:可修改的l值(non-const)和不可修改的l值(const)。

想到一個r值是最簡單的作為“一切不是l值”。這尤其包括文字(例如5),臨時值(例如x + 1)和匿名物件(例如Fraction(5,2))。r值通常是針對它們的值進行評估,具有表示式範圍(它們在它們所在的表示式的末尾處死亡),並且不能分配給它們。這種非賦值規則是有意義的,因為賦值會對物件應用副作用。由於r值具有表示式範圍,如果我們要為r值賦值,那麼在我們有機會在下一個表示式中使用指定值之前,r值將超出範圍(這使得賦值無用)或者我們必須使用在表示式中多次應用副作用的變數(現在你應該知道它會導致未定義的行為!)。

為了支援移動語義,C ++ 11引入了3個新的值類別:pr值,x值和gl值。我們將在很大程度上忽略這些,因為理解它們對於有效地學習或使用移動語義是沒有必要的。如果您有興趣,cppreference.com有大量的表示式列表,這些表示式符合各種值類別的要求,以及有關它們的更多詳細資訊。

L值參考

在C ++ 11之前,C ++中只存在一種型別的引用,因此它被稱為“引用”。但是,在C ++ 11中,它有時被稱為l值引用。L值引用只能用可修改的l值初始化。

L值參考 可以初始化 可以修改
可修改的l值
不可修改的l值 沒有 沒有
R值 沒有 沒有
L值參考 可以初始化 可以修改
L值引用const 沒有
不可修改的l值 沒有
R值 沒有

對const物件的L值引用特別有用,因為它們允許我們將任何型別的引數(l值或r值)傳遞給函式,而無需複製引數。

R值參考

C ++ 11添加了一種稱為r值引用的新型別引用。r值引用是一個用r值(僅)初始化的引用。使用單個&符建立l值引用時,使用雙符號建立r值引用:

int x = 5;
int &lref = x; // l值引用用l值x初始化
int &&rref = 5; // 用r值5初始化的r值參考

無法使用l值初始化R值引用。

R值參考 可以初始化 可以修改
可修改的l值 沒有 沒有
不可修改的l值 沒有 沒有
R值
對const的R值引用 可以初始化 可以修改
可修改的l值 沒有 沒有
不可修改的l值 沒有 沒有
R值 沒有

R值引用有兩個有用的屬性。首先,r值引用將它們初始化的物件的生命週期延長到r值引用的生命週期(對const物件的l值引用也可以這樣做)。其次,非常量r值引用允許您修改r值!

我們來看看一些例子:

#include <iostream>
 
class Fraction
{
private:
	int m_numerator;
	int m_denominator;
 
public:
	Fraction(int numerator = 0, int denominator = 1) :
		m_numerator(numerator), m_denominator(denominator)
	{
	}
 
	friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
	{
		out << f1.m_numerator << "/" << f1.m_denominator;
		return out;
	}
};
 
int main()
{
	Fraction &&rref = Fraction(3, 5); // r值引用臨時分數
	std::cout << rref << '\n';
 
	return 0;
} //rref(和臨時分數)超出了範圍

該程式列印:

3/5
作為一個匿名物件,Fraction(3,5)通常會在定義它的表示式的末尾超出範圍。但是,由於我們用它初始化一個r值引用,它的持續時間會延長到塊的結尾。然後我們可以使用該r值引用來列印Fraction的值。

現在讓我們來看一個不太直觀的例子:

#include <iostream>
 
int main()
{
    int &&rref = 5; // 因為我們用文字初始化r值引用,所以在這裡建立一個值為5的臨時值
    rref = 10;
    std::cout << rref;
 
    return 0;
}

該程式列印:

10
雖然使用文字值初始化r值引用然後能夠更改該值可能看起來很奇怪,但在使用文字初始化r值時,會從文字構造臨時值,以便引用引用臨時值物件,而不是文字值。

R值參考不經常以上述任何一種方式使用。

R值引用作為函式引數

R值引用更常用作函式引數。當您希望對l值和r值引數具有不同的行為時,這對於函式過載最有用。

void fun(const int &lref) // l-value arguments will select this function
{
	std::cout << "l-value reference to const\n";
}
 
void fun(int &&rref) // r-value argument will select this function
{
	std::cout << "r-value reference\n";
}
 
int main()
{
	int x = 5;
	fun(x); // l-value argument calls l-value version of function
	fun(5); // r-value argument calls r-value version of function
 
	return 0;
}

這列印:

l-value reference to const
r-value reference

如您所見,當傳遞l值時,過載函式已解析為具有l值引用的版本。當傳遞r值時,過載函式解析為具有r值引用的版本(這被認為是比對const的l值引用更好的匹配)。

你為什麼要這樣做?我們將在下一課中更詳細地討論這個問題。不用說,它是移動語義的重要組成部分。

返回r值引用

你應該幾乎永遠不會返回一個r值引用,因為你幾乎永遠不會返回一個l值引用。在大多數情況下,當引用的物件超出函式末尾的範圍時,您最終將返回掛起引用。

Quiz Time:

1)說明以下哪個字母語句不能編譯:

int main()
{
	int x;
 
	// l值參考
	int &ref1 = x; // A
	int &ref2 = 5; // B
 
	const int &ref3 = x; // C
	const int &ref4 = 5; // D
 
	//r值參考
	int &&ref5 = x; // E
	int &&ref6 = 5; // F
 
	const int &&ref7 = x; // G
	const int &&ref8 = 5; // H
	
	return 0;
}

解決方案

B,E和G不會編譯。