最近一段時間寫C++代碼,總是被其中的左值/右值以及std::move等東西困擾,打算用文章記錄下我學(xué)習(xí)這部分知識的過程,做個總結(jié),給自己以后做參考;這邊文章首先介紹下C++中的左值/右值以及左值引用和右值引用。
左值與右值
直觀來說,在一個表達(dá)式中,出現(xiàn)在等號左邊的表達(dá)式是左值,出現(xiàn)在等號右邊的表達(dá)式是右值;注意,這里強調(diào)是表達(dá)式而不是變量。左值既可以出現(xiàn)在等號的左邊也可以出現(xiàn)在等號的右邊,而右值一定只能出現(xiàn)在等號的右邊。一個表達(dá)式如果不是左值,一定是右值。
本質(zhì)上說,左值是可以被尋址的值,即左值一定對應(yīng)內(nèi)存中的一塊區(qū)域,而右值既可以是內(nèi)存中的值,也可以是寄存器中的一個臨時變量。一個表達(dá)式被用作左值時,使用的是它的地址,而被用作右值時,使用的是它的內(nèi)容。
如何準(zhǔn)確的判斷一個表達(dá)式是不是左值呢?
- 如果這個表達(dá)式可以出現(xiàn)在等號的左邊,一定是左值;
- 如果可以對一個表達(dá)式使用&符號去地址,它一定是左值;
- 如果它“有名字”,則一定是左值;
int a = b+c ; // a是左值,因為我們可以對a取地址 &a; b+c是右值,因為我們不能對它取地址 &(b+c)編譯會報錯
在cpp11中,右值又可以進(jìn)一步分為純右值(pure rvalue)和將亡值(expiring value);其中純右值指的是臨時變量或者不和變量關(guān)聯(lián)的字面量;其中臨時變量包括非引用類型的函數(shù)返回值,比如 int f()的返回值,和表達(dá)式的結(jié)果,比如(a+b)結(jié)果就是一個臨時變量;字面量比如10,“abc”這些。將亡值是cpp11新增的,適合右值引用相關(guān)的概念,包括返回右值引用T&&的函數(shù)的返回值,std::move的返回值。
左值引用和右值引用
引用
引用在使用上是變量的一個別名,對引用變量的任何操作都可以理解為對原變量的操作;引用和指針的主要區(qū)別如下:
- 引用不能為空,引用任何時候都要指向一塊合法的內(nèi)存;指針可以為空;
- 引用一旦初始化完成,就不能再指向另一個變量;指針可以;
- 引用必須在創(chuàng)建的時候初始化,指針可以在任何時候初始化;
那么什么是左值引用和右值引用呢?
左值引用就是對一個左值進(jìn)行引用的類型,右值引用就是對一個右值進(jìn)行引用的類型。左值引用和右值引用都屬于引用類型,都必須在聲明時對其進(jìn)行初始化,其原因是引用類型本身并不持有所引用變量的內(nèi)存,所以必須在初始化時制定要綁定的變量;左值引用是對具名變量的引用,右值引用是對不具名變量(匿名變量)的引用。
形如 const T&的常量左值引用是個萬金油的引用類型,其可以引用非常量左值,常量左值和右值。而形如T&的非常量左值引用只能接受非常量左值對其進(jìn)行初始化。
int &a = 2; # 非常量左值引用綁定到右值,編譯失敗
int b = 2; # 非常量左值變量
const int &c = b; # 常量左值引用綁定到非常量左值,編譯通過
const int d = 2; # 常量左值
const int &e = c; # 常量左值引用綁定到常量左值,編譯通過
const int &b =2; # 常量左值引用綁定到右值,編程通過
右值引用通常不能綁定任何左值,如果要綁定左值,需要使用std::move()將左值轉(zhuǎn)換為右值;
int a;
int&& r1 = a; //非法,a是左值;
int&& r2 = std::move(a); //合法,通過std::move()將左值轉(zhuǎn)換為右值;
下面來看一個例子:
void foo(int& a) {
cout << "lvalue reference " << a << endl;
}
void foo(int&& a ) {
cout << "rvalue reference " << a << endl;
}
int main() {
int a = 0;
foo(a);
foo(1);
}
輸出:
lvalue reference 0
rvalue reference 1
上面的demo重載了foo函數(shù),使其既可以接受左值引用,也可以接受右值引用。從結(jié)果可以看出,變量是作為左值處理的,而字面常量(臨時對象)是作為右值處理的。
但是如果臨時對象通過一個接受右值的函數(shù)傳遞給另一個函數(shù),就會變成左值,因為這個變量在傳遞的過程中變成了具名變量:
void foo(int& a) {
cout << "lvalue reference " << a << endl;
}
void foo(int&& a ) {
cout << "rvalue reference " << a << endl;
}
void f(int&& a) {
foo(a);
}
int main() {
f(1);
}
輸出:
lvalue reference 1