C 左值與右值

2021-09-13 18:37:01 字數 3009 閱讀 3590

左值(lvalue)和右值(rvalue)是 c/c++ 中乙個比較晦澀基礎的概念,不少寫了很久c/c++的人甚至沒有聽過這個名字,但這個概念到了 c++11 後卻變得十分重要,它們是理解 move/forward 等新語義的基礎。

左值與右值這兩概念是從 c 中傳承而來的,在 c 中,左值指的是既能夠出現在等號左邊也能出現在等號右邊的變數(或表示式),右值指的則是只能出現在等號右邊的變數(或表示式).

int a;

int b;

a = 3;

b = 4;

a = b;

b = a;

// 以下寫法不合法。

= a;

a+b = 4;

在 c 語言中,通常來說有名字的變數就是左值(如上面例子中的 a, b),而由運算操作(加減乘除,函式呼叫返回值等)所產生的中間結果(沒有名字)就是右值,如上的 3 + 4, a + b 等。我們暫且可以認為:左值就是在程式中能夠尋值的東西,右值就是沒法取到它的位址的東西(不完全準確),但如上概念到了 c++ 中,就變得稍有不同。具體來說,在 c++ 中,每乙個表示式都會產生乙個左值,或者右值,相應的,該表示式也就被稱作「左值表示式", "右值表示式"。對於基本資料型別來說(primitive types),左值右值的概念和 c 沒有太多不同,不同的地方在於自定義的型別,而且這種不同比較容易讓人混淆:

1) 對於基礎型別,右值是不可被修改的(non-modifiable),也不可被 const, volatile 所修飾(cv-qualitification ignored)

2) 對於自定義的型別(user-defined types),右值卻允許通過它的成員函式進行修改。

對於 1),這和 c 是一致的,2) 卻是 c++ 中所獨有, 因此,如果你看到 c++ 中如下的寫法,千萬不要驚訝:

class cs

~cs()

cs& operator=(const cs& other)

int get_i() const

void change(int i)

private:

int i_;

};cs get_cs()

int main()

這個特性看起來多少有些奇怪,因為通常來說,自定義型別應該設計得和內建型別盡量一樣(所謂 value type,value semantic),但允許成員函式改變右值這個特性卻有意無意使得自定義型別特殊化了。對此,我們其實可以這樣想,也許會好理解點:自定義型別允許有成員函式,而通過右值呼叫成員函式是被允許的,但成員函式有可能不是 const 型別,因此通過呼叫右值的成員函式,也就可能會修改了該右值,done!

關於右值,在 c++11 以前有乙個十分值得關注的語言的特性:右值能被 const 型別的引用所指向,所以如下**是合法的。

const cs& ref = get_cs();
而且準確地說,右值只能被 const 型別的 reference 所指向,非 const 的引用則是非法的:

// error 

cs& ref = get_cs();

當乙個右值被 const 引用指向時,它的生命週期就被延長了,這個用法我在前面一篇部落格裡講到過它的相關應用。其中暗藏的邏輯其實就是:右值不能當成左值使用(但左值可以當成右值使用)。另外值得注意的是,對於前面提到的右值的兩個特性:

1) 允許呼叫成員函式。

2) 只能被 const reference 指向。

它們導致了一些比較有意思的結果,比如:

void func(cs& c)

//error

func(get_cs());

//正確

func(get_cs() = get_cs());

其中: func(get_cs() = get_cs()); 能夠被正常編譯執行的原因就在於,cs 的成員函式 operator=() 返回的是 cs&!不允許非 const reference 引用 rvalue 並不是完美的,它事實上也引起了一些問題,比如說拷貝建構函式的介面不一致了,這是什麼意思呢?

class cs

;// 另一種寫法

class cs2

;

上面兩種寫法的不同之處就在於引數,乙個是 const reference,乙個是非 const。對於自定義型別的引數,通常來說,如果函式不需要修改傳進來的引數,我們往往就按 const reference 的寫法,但對於 copy constructor 來說,它經常是需要修改引數的值,比如 auto_ptr。

// 類似auto_ptr

class auto_ptr

private:

void* ptr_;

};

所以,對於 auto_ptr 來說,它的 copy constructor 的引數型別是 non const reference。有些情況下,這種寫法應該被鼓勵,畢竟 non const reference 比 const reference 更能靈活應對各種情況,從而保持一致的介面型別,當然也有代價,引數的語義表達不準確了。除此更大的問題是如果拷貝建構函式寫成這樣子,卻又對 rvalue 的使用帶來了極大的不變,如前面所講的例子,rvalue 不能被 non const reference 所引用,所以像 auto_ptr 的這樣的類的 copy constructor 就不能接受 rvalue.

// 錯誤

auto_ptr p(get_ptr());

// operator=() 同理,錯誤。

auto_ptr p = get_ptr();

這也是 auto_ptr 很不好用的原因之一,為了解決這個問題,c++11 中引入了一種新的引用型別,該種引用是專門用來指向 rvalue 的,有了這種新型別,對 lvalue 和 rvalue 的引用就能明確區分開來了。因為有了這種新的型別,接著就引出了 c++11 中新的語義,move(), forward() 等。

摘自:

c 左值與右值

在學c 過程中,左值與右值的概念是一道繞不過去的坎,因此就想寫一些自己的理解。左值 lvalue 與右值 rvalue 是c 語法中的術語 terms 第一次看到這兩個詞的時候,很自然就認為在乙個表示式中,在等號左邊的就是左值,在等號右邊的就是右值。如果你們第一次看到這兩個詞也這麼認為的話,那我要恭...

C 左值與右值

乙個很常見的誤區就是左值時等號左邊的值,右值時等號右邊的值 含義 左值是指表示式結束後依然存在的持久化物件 右值是指表示式結束時就不再存在的臨時物件 左值是儲存單元內的值,即是有實際儲存位址的 右值則不是儲存單元內的值,比如它可能是暫存器內的值也可能是立即數。下圖取自乙個知乎的回答 看幾個例子 in...

C 左值與右值

左值 lvalue 是有識別符號 可以取位址的表示式,最常見的情況有 變數 函式或資料成員的名字返回左值引用的表示式,如 x x 1 cout 字串字面量如 hello world 在函式呼叫時,左值可以繫結到左值引用的引數,如 t 乙個常量只能繫結到常左值引用,如 const t 反之,純右值 p...