這個問題牽涉到得首先是左值(L-value)和右值(R-value)的概念;
1. 我查資料的時候發現很多地方都引用一句話:"通俗的講,左值就是能夠出現在指派符号左面的東西,而右值就是那些可以出現在指派符号右面的東西了。"我覺得這句話在剛開始了解的時候是什麼用都沒有的一句廢話.因為我們都不知道哪些東西應該放在指派符号的左邊,哪些東西又應該放在指派符号的右邊這樣說是沒有意義的.
2.接着我找到個比較靠譜的定義:左值是指具有對應的可由使用者通路的存儲單元,并且能由使用者改變其值的量。如一個變量就是一個左值,因為它對應着一個存儲單元,并可由程式設計者通過變量名通路和改變其值。
(下面的第三點是c++primer中的)
3.變量和文字常量都有存儲區,并且有相關的類型。差別在于變量是可尋址的(addressable)對于每一個變量都有兩個值與其相聯:
1).它的資料值,存儲在某個記憶體位址中。有時這個值也被稱為對象的右值(rvalue,讀做are-value).我們也可認為右值的意思是被讀取的值(read value)。 文字常量和變量都可 被用作右值。
2).它的位址值——即存儲資料值的那塊記憶體的位址。它有時被稱為變量的左值(lvalue,讀作ell-value)。我們也可認為左值的意思是位置值location value文字常量不能被用作左值
有些變量既可以當左值右可以當右值。
-
左值(Lvalue) →→ Location
表示記憶體中可以尋址,可以給它指派(const類型的變量例外)
-
右值Rvalue) →→ Read
表示可以知道它的值(例如常數)
一個典型的例子
a++ : 先使用a的值,再給a加1,作為 右值
// a++的實作
int temp = a;
a = a + 1;
return temp;
- ++a : 先加再用,作為 左值
a = a + 1;
return a;
左值符号 &
右值符号 &&
在C++中,臨時對象不能作為左值,但是可以作為常量引用,
const &
。
C++ 11中的std::move可将左值引用轉化成右值引用。
左值和右值
左值:指表達式結束後依然存在的持久對象,可以取位址,具名變量或對象
右值:表達式結束後就不再存在的臨時對象,不可以取位址,沒有名字。
比如
int a = b + c;
,a 就是一個左值,可以對a取位址,而
b+c
就是一個右值,對表達式
b+c
取位址會報錯。C++11中右值又由兩個概念組成:将亡值和純右值。
純右值和将亡值
在C++98中,右值是純右值,純右值指的是臨時變量值、不跟對象關聯的字面量值。包括非引用的函數傳回值、表達式等,比如 2、‘ch’、int func()等。将亡值是C++11新增的、與右值引用相關的表達式。
- 純右值:非引用傳回的臨時變量( int func(void) )、運算表達式産生的臨時變量(b+c)、原始字面量(2)、lambda表達式等。
- 将亡值:将要被移動的對象、T&&函數傳回值、std::move傳回值和轉換為T&&的類型的轉換函數的傳回值。
将亡值可以了解為通過“盜取”其他變量記憶體空間的方式擷取到的值。在確定其他變量不再被使用、或即将被銷毀時,通過“盜取”的方式可以避免記憶體空間的釋放和配置設定,能夠延長變量值的生命期。
右值引用和左值引用
介紹
右值引用就是對一個右值進行引用的類型,标記為 T&&。因為右值不具名,是以引用的形式找到它,用引用來表示,右值引用也是引用的引用(我目前是這麼想的)。
左值引用就是對一個左值進行引用的類型。
引用本身不擁有所綁定對象的記憶體,隻是該對象的一個别名,左值引用就是有名變量的别名,右值引用是不具名變量的别名。是以無論左值引用還是右值引用都必須立即進行初始化。
通過右值引用,這個将亡的右值又“重獲新生”,它的生命周期與右值引用類型變量的生命周期一樣,隻要這個右值引用類型的變量還活着,那麼這個右值臨時量就會一直活着,這是一重要特性,可利用這一點會一些性能優化,避免臨時對象的拷貝構造和析構。
左值引用包括常量左值引用和非常量左值引用。非常量左值引用隻能接受左值,不能接受右值;常量左值引用是一個“萬能”的引用類型,可以接受左值(常量左值、非常量左值)、右值。不過常量左值所引用的右值在它的“餘生”中隻能是隻讀的。
int &a = 2; // 非常量左值引用 綁定到 右值,編譯失敗
int b = 2; // b 是非常量左值
const int &c = b; // 常量左值引用 綁定到 非常量左值,編譯通過
const int d = 2; // d 是常量左值
const int &e = d; // 常量左值引用 綁定到 常量左值,編譯通過
const int &f =2; // 常量左值引用 綁定到 右值,編譯通過
右值引用通常不能綁定到任何的左值,要想綁定一個左值到右值引用,通常需要std::move()将左值強制轉換為右值。比如:
int a;
int &&r1 = a; // 編譯失敗
int &&r2 = std::move(a); // 編譯通過
右值引用特點
右值引用獨立于左值和右值。意思是右值引用類型的變量可能是左值也可能是右值。比如:
int&& val1 = x;
var1類型為右值引用,但var1本身是左值,因為具名變量都是左值。
T&& 并不一定表示右值,它綁定的類型是未定的,既可能是左值又可能是右值。
template<typename T>
void f(T&& param){}
f(10); //param是右值
int x = 10;
f(x); //param是左值
T&& param,表示param實際上是一個未定的引用類型,稱為universal references,可以認為它是一種未定的引用類型,它必須被初始化,它是左值還是右值引用取決于它的初始化,如果&&被一個左值初始化,它就是一個左值;如果它被一個右值初始化,它就是一個右值。
上面例子,當參數為右值10的時候,根據universal references的特點,param 被一個右值初始化,那麼 param 就是右值;當參數為左值 x 時,param 被一個左值引用初始化,那麼 param 就是一個左值。
隻有發生自動類型推斷時(如函數模闆的類型自動推導,或auto關鍵字),T&& 才是一個universal references。比如
template<typename T>
void func(T&& param) //T需要推導,&&是一個universal references
template<typename T>
class Test {
Test(Test&& rhs);
};
上面的例子中,param是universal references,rhs 是 Test&& 右值引用,因為模版函數 func 發生了類型推斷,而 Test&& 并沒有發生類型推導,因為 Test&& 是确定的類型了.
再看一個例子:
template<typename T>
void func(const T&& param);
上面的例子中param不是 universal references,它其實是一個右值引用。universal references 僅僅在 T&& 下發生,任何一點附加條件都會使之失效,是以上面被 const 修飾之後就成了右值引用了。
引用折疊規則
經過類型推導的 T&& 類型,相比右值引用(&&)會發生類型變化,這種變化被稱為引用折疊或崩塌。規則:
- 所有的右值引用疊加到右值引用上仍然還是一個右值引用;
- 所有的其他引用類型之間的疊加都将變成左值引用。
總之是,所有的右值引用疊加到右值引用上仍然是一個右值引用,其他引用折疊都是左值引用。
T& &、T& &&和T&& & 都折疊成類型 T&
T&& &&折疊成 T&&
右值函數模版參數類型推導
template<typename T>
void foo(T&&);
1. 當傳給foo函數的參數是一個左值時,例如:
int i = 29;
foo(i);//i為左值引用
此時,T的類型為int的左值引用:int&,參數類型為int& &&,(即T&&),結合上面的引用折疊規則,最終參數的類型為int的左值引用:int&。
2. 當傳給foo函數的參數是一個右值時,例如:
foo(29);
此時,T的類型為int,參數類型為int&&,(即T&&)。
還在了解ing,感覺好複雜,也不會用呢,先到這。
還有一點,忘了加上。
編譯器會把已命名的右值引用視為左值,而将未命名的右值引用視為右值。
#include <iostream>
#include <string>
#include <stack>
#include <vector>
#include <algorithm>
#include <cstdio>
using namespace std;
void PrintValue(int & i)
{
cout << "lvalue : " << i << endl;
}
void PrintValue(int&& i)
{
cout << "rvalue : " << i << endl;
}
void Forward(int&& i)
{
PrintValue(i);
}
int main()
{
int i = 0;
PrintValue(i);
PrintValue(1);
Forward(2);
return 0;
}
輸出結果:
Forwaid函數收到一個右值,在轉發給PrintfValue時又變成了左值,在Forward中調用PrintfValue時,右值i變成了一個命名的對象,編譯器會把它當作左值處理。
左值引用, 即&i, 是一種對象類型的引用; 右值引用, 即&&i, 是一種對象值的引用;
std::move()可以把左值引用, 轉換為右值引用;
左值引用是固定的引用, 右值引用是易變的引用, 隻能引用字面值(literals)或臨時對象(temporary object);
右值引用主要應用在移動構造器(move constructor)和移動-指派操作符(move-assignment operator)上面;
代碼如下
/*
* cppprimer.cpp
*
* Created on: 2013.11.7
* Author: Caroline
*/
#include <iostream>
#include <utility>
int main (void) {
int i = 42;
int &lr = i;
int &&rr = i*42;
const int &lr1 = i*42;
int &&rr1 = 42;
int &&rr2 = std::move(lr);
std::cout << "i = " << i << std::endl;
std::cout << "lr = " << lr << std::endl;
std::cout << "rr = " << rr << std::endl;
std::cout << "lr1 = " << lr1 <<std::endl;
std::cout << "rr1 = " << rr1 << std::endl;
std::cout << "rr2 = " << rr2 << std::endl;
}