C++左值和右值(詳解版)

2020-07-16 10:04:41
前面講過,參照是一個變數,它參照其他變數的記憶體位置。例如,來看以下程式碼:

int x = 34;
int &lRef = x;

在該程式碼中,識別符號 IRef 就是一個參照。在宣告中,參照是通過 & 符號來指示的,它出現在型別與變數的識別符號之間,這種型別的參照稱為左值參照

可以將左值看作是一個關聯了名稱的記憶體位置,允許程式的其他部分來存取它。在這裡,我們將 "名稱" 解釋為任何可用於存取記憶體位置的表示式。所以,如果 arr 是一個陣列,那麼 arr[1] 和 *(arr+1) 都將被視為相同記憶體位置的“名稱”。

相對而言,右值則是一個臨時值,它不能被程式的其他部分存取。為了說明這些概念,請看以下程式段:
int square(int a)
{
    return a * a;
}
int main()
{
    int x = 0; // 1
    x = 12; // 2
    cout << x << endl; // 3
    x = square(5); // 4
    cout << x << endl; // 5
    return 0;
}
在該程式中,x 是一個左值,這是因為 x 代表一個記憶體位置,它可以被程式的其他部分存取,例如上面註釋的第 2、3、4 和 5 行。

而表示式 square(5) 卻是一個右值,因為它代表了一個由編譯器建立的臨時記憶體位置,以儲存由函數返回的值。該記憶體位置僅被存取一次,也就是在第 4 行賦值語句的右側。在此之後,它就會立即被刪除,再也不能被存取了。

對於包含右值的記憶體位置來說,其本質就是:它雖然沒有名稱,但是可以從程式的其他部分存取到它。

C++11 引入了右值參照的概念,以表示一個本應沒有名稱的臨時物件。右值參照的宣告與左值參照類似,但是它使用的是 2 個 & 符號(&&),以下程式碼使用了右值參照列印了兩次 5 的平方:
int && rRef = square(5);
cout << rRef << endl;
cout << rRef << endl;
有意思的是,宣告一個右值參照,給一個臨時記憶體位置分配一個名稱,這使得程式的其他部分存取該記憶體位置成為了可能,並且可以將這個臨時位置變成一個左值。

右值參照不能約束到左值上,所以,以下程式碼將無法編譯:

int x = 0;
int && rRefX = x;

再來看以下初始化語句:

int && rRef1 = square(5);

在初始化完成之後,這個包含值 square(5) 的記憶體位置有了一個名稱,即 rRef1,所以 rRef1 本身變成了一個左值。這意味著後面的這個初始化語句將不會編譯:

int && rRef2 = rRef1;

究其原因,就是右側的 rRef1 不再是一個右值。綜上所述,臨時物件最多可以有一個左值參照指向它。如果函數有一個臨時物件的左值參照,則可以確認,程式的其他部分都不能存取相同的物件。