目錄
概念:
理解:
?左值引用,右值引用
?左值引用能否給右值取別名?
右值引用能否給左值取別名?
引用的意義是什么?
左值和右值對自定義類型有什么區別嗎?
move的妙用!
沒有優化的左值傳輸:
?臨時變量是必然產生的!
?右值引用本身是左值!
主要原因:
右值引用的底層 和左值引用的底層:?
右值能否被改變?
概念:
傳統的C++語法中就有引用的語法,而C++11中新增了的右值引用語法特性,所以從現在開始我們 之前學習的引用就叫做左值引用。無論左值引用還是右值引用,都是給對象取別名。
什么是左值?什么是左值引用?
左值是一個表示數據的表達式(如變量名或解引用的指針),我們可以獲取它的地址+可以對它賦 值,左值可以出現賦值符號的左邊,右值不能出現在賦值符號左邊。
定義時const修飾符后的左 值,不能給他賦值,但是可以取它的地址。
左值引用就是給左值的引用,給左值取別名。
int main()
{// 以下的p、b、c、*p都是左值
int* p = new int(0);int b = 1;const int c = 2;// 以下幾個都是常見的右值
10;x + y;fmin(x, y);return 0;}
理解:
在以往較為淺薄的理解中,左值是可以進行修改的,右值是無法進行修改的
例如,圖中的a是變量可以進行修改是左值,但10是常數不能進行修改是右值,但不完全是這樣的結果:又如下圖所示,a在第一行代碼中是左值,在第二行代碼中又充當了右值,所以左值和右值不能如此輕易的劃分。
又如:加上了const的c是不允許被修改的,所以c是左值還是右值?答案是:c是左值!
因此,最后我們可以總結出一個結論,?可以取地址的是左值,不能取地址的是右值。
同時我們也需要注意,左值代表的不一定是一個數值,也可也是一個表達式。
例如:
*p
*p 是一個左值vector<int> v(10,1);
v[1];
v[1] 是一個左值
凡是能取到地址的都算是左值!?
而右值就是取不到地址的,同樣右值也可以是一個表達式,且同時,臨時變量、匿名對象、臨時對象統統都是右值!
都是右值?
?左值引用,右值引用
引用就是取別名!左值引用就是給左值取別名,右值引用取別名!
int main()
{// 以下的p、b、c、*p都是左值
int* p = new int(0);int b = 1;const int c = 2;// 以下幾個是對上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;double x = 1.1, y = 2.2;
// 以下幾個都是常見的右值
10;x + y;fmin(x, y);
// 以下幾個都是對右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);
// 這里編譯會報錯:error C2106: “=”: 左操作數必須為左值10 = 1;x + y = 1;fmin(x, y) = 1;return 0;
}
?左值引用能否給右值取別名?
答案是不可以!但是加上const可以!
右值引用能否給左值取別名?
答案也是不可以!但是需要把左值加上move進行使用后才行!
所以左右值引用 可以對左右值進行取別名,但是要有條件!
引用的意義是什么?
- 減少拷貝!提高效率!傳值、傳參、傳引用返回 都可以使用引用,這是左值引用的常見場景!
- 但是左值引用沒有徹底解決返回值的問題,右值引用就是修補這一塊內容。
- 因為如果是一個臨時對象,或者局部對象,則不能使用引用返回!因為出了作用域地址就會被銷毀,所以沒有用!而這種問題也不能直接使用右值引用進行返回,右值引用返回不是這樣用的!?
例如:
在主函數調用的時候,會在返回的時候進行臨時變量的構造然后再加上拷貝構造把數值從臨時變量中進行拷貝導賦值變量中,然后C++11后面優化成了一個直接獲取!
?這里無論是加左值引用還是右值引用都沒有效果且會報錯,因為str是局部變量被銷毀了!地址不存在了!因為出作用域了!
為了解決這種問題,增加一個右值引用的構造函數,而被右值引用修飾的構造叫做移動構造!
有了移動構造,編譯器會進行選擇,如果返回的時候是左值,那么編譯器就會去選擇拷貝構造,如果是右值,那么就會 選擇移動構造
左值和右值對自定義類型有什么區別嗎?
自定義類型的右值基本都是匿名對象、傳值返回的臨時對象。
然后右值又有細分,分為純右值和將亡值,基本上內置類型的右值都是純右值,而自定義類型的右值基本都是將亡值。
對于局部變量來說,它的左值只能是老實的開辟空間然后被利用完被拷貝完后銷毀空間,
而對于局部變量來說,它的右值(將亡值),它會利用空間! 將拷貝數值的空間和臨時變量的空間進行交換!
?有了移動構造后,在傳值時,會強行把左值變成右值,強行使用了move (可以不在返回時加上move 編譯器會做處理!)/可以看出str雖然是左值,但是和將亡值沒什么區別
?然后會調用兩次移動構造,但是編譯器直接轉移資源,一步到位!以前需要進行拷貝,但是右值的移動構造,將被賦值的數值強行和右值進行交換!
移動構造為什么叫移動構造,是因為它移動將亡值對象的資源!!!!把將亡值的資源轉移到被賦值調用的數據上!
嚴格來說,移動構造是延長了將亡值 所占據的資源數據的生命周期,而不是延長了將亡值所處在空間地址的生命周期。因為是轉移資源,所以移動構造的代價是極低的!
move的妙用!
可以看出s1的數據被移動到了s3 這就是move的功能!??
沒有優化的左值傳輸:
沒有優化的傳輸其實就是仙拷貝構造在賦值拷貝,就如下圖中的寫法,導致編譯器無法進行優化!
?優化:進行優化的左值傳輸!一次構造和一次拷貝構造,最后被編譯器優化成了一次拷貝構造!
?臨時變量是必然產生的!
?臨時對象是必然產生的,這個臨時對象是在寄存器的,但是寄存器非常的小,所以當對象數據過大時,會把臨時對象放到兩個數據的棧幀中:如下圖所示的綠色框框就是處在main和to_string兩個棧幀之間!
這種編譯器無法進行優化的,可以使用move進行轉化成移動構造函數,將其變成一次移動構造,一次移動賦值!
str在傳輸時會給一個臨時對象,然后把臨時對象的數據(空)的數據和 傳輸的數據進行數據之間的交換,然后在臨時賦值的時候,因為在賦值之前進行初始化,所以會把初始化的那個空間和臨時對象內部的數據再一次進行交換,然后完成移動賦值!
?右值引用本身是左值!
右值和右值引用,求s1是右值還是左值?左值!已知std::string(“11111111”)是右值
但是右值引用是左值!有地址!右值引用本身是左值!
主要原因:
右值有一個特點,本身是不能改變的,因為如果使用了別名,那如果別名也不能改變,那如何做資源轉移?
可以看出右值的資源轉移主要是因為swap如轉移的過程中數值不能進行改變,也就是右值引用的數據不能改變,那么將無法進行資源的轉移!所以也就可以得出,右值引用是左值!
右值引用的底層 和左值引用的底層:?
右值引用和左值引用的底層都是指針,都是取匿名空間的地址!所以右值真的沒有地址嗎?那右值存哪里?總要存儲吧!所以右值是有地址的,只是不能拿取這個地址罷了!取不到這個地址罷了!?
右值能否被改變?
s5是可以左值引用s1的!但是s6是不能直接左值引用的!但是可以使用強制轉化:
可以資源轉移到s7!所以右值能否改變呢!可以的!如果不用強轉呢?
先給一個右值引用,然后在左值引用,本質就是右值引用底層是指針,且底層還是有空間存儲的!
和上面的s5和s1的操作一樣!類似于交換!