理解C 中的左值和右值

2021-09-25 17:03:12 字數 4023 閱讀 9277

理解c++中的左值和右值

attention:this blog is a translation of ,which is posted by @internalpoiners.

一、前言

一直以來,我都對c++中左值(lvalue)和右值(lvalue)的概念模糊不清。我認為是時候好好理解他們了,因為這些概念隨著c++語言的進化變得越來越重要。

二、左值和右值——乙個友好的定義

首先,讓我們避開那些正式的定義。在c++中,乙個左值是指向乙個指定記憶體的東西。另一方面,右值就是不指向任何地方的東西。通常來說,右值是暫時和短命的,而左值則活的很久,因為他們以變數的形式(variable)存在。我們可以將左值看作為容器(container)而將右值看做容器中的事物。如果容器消失了,容器中的事物也就自然就無法存在了。

讓我們現在來看一些例子:

int x = 666; //ok

在這裡,666是乙個右值。乙個數字(從技術角度來說他是乙個字面常量(literal constant))沒有指定的記憶體位址,當然在程式執行時一些臨時的暫存器除外。在該例中,666被賦值(assign)給x,x是乙個變數。乙個變數有著具體(specific)的記憶體位置,所以他是乙個左值。c++中宣告乙個賦值(assignment)需要乙個左值作為它的左運算元(left operand):這完全合法。

對於左值x,你可以做像這樣的操作:

int* y = &x; //ok

在這裡我通過取位址操作符&獲取了x的記憶體位址並且把它放進了y。&操作符需要乙個左值並且產生了乙個右值,這也是另乙個完全合法的操作:在賦值操作符的左邊我們有乙個左值(乙個變數),在右邊我們使用取位址操作符產生的右值。

然而,我們不能這樣寫:

int y;

666 = y; //error!

error: lvalue required as left operand of assignment
賦值的左運算元需要乙個左值,這裡我們使用了乙個右值666。

我們也不能這樣做:

int* y = &666;// error~

error: lvalue required as unary '&' operand`
&操作符需要乙個左值作為運算元,因為只有乙個左值才擁有位址。

三、返回左值和右值的函式

我們知道乙個賦值的左運算元必須是乙個左值,因此下面的這個函式肯定會丟擲錯誤:lvalue required as left operand of assignment

int setvalue()

// … somewhere in main() …

setvalue() = 3; // error!

錯誤原因很清楚:setvalue()返回了乙個右值(乙個臨時值6),他不能作為乙個賦值的左運算元。現在,我們看看如果函式返回乙個左值,這樣的賦值會發生什麼變化。看下面的**片段(snippet):

int global = 100;

int& setglobal()

// … somewhere in main() …

setglobal() = 400; // ok

該程式可以執行,因為在這裡setglobal()返回乙個引用(reference),跟之前的setvalue()不同。乙個引用是指向乙個已經存在的記憶體位置(global變數)的東西,因此它是乙個左值,所以它能被賦值。注意這裡的&:它不是取位址操作符,他定義了返回的型別(乙個引用)。

可以從函式返回左值看上去有些隱晦,它在你做一些高階的程式設計例如實現一些操作符的過載(implementing overload operators)時會很有作用,這些知識會在未來的章節中講述。

四、左值到右值的轉換

乙個左值可以被轉換(convert)為右值,這完全合法且經常發生。讓我們先用+操作符作為乙個例子,根據c++的規範(specification),它使用兩個右值作為引數並返回乙個右值(譯者按:可以將操作符理解為乙個函式)。

讓我們看下面的**片段:

int x = 1;

int y = 3;

int z = x + y; // ok

等一下,x和y是左值,但是加法操作符需要右值作為引數:發生了什麼?答案很簡單:x和y經歷了乙個隱式(implicit)的左值到右值(lvalue-to-rvalue)的轉換。許多其他的操作符也有同樣的轉換——減法、加法、除法等等。

五、左值引用

相反呢?乙個右值可以被轉化為左值嗎?不可以,它不是技術所限,而是c++程式語言就是那樣設計的。

在c++中,當你做這樣的事:

int y = 10;

int& yref = y;

yref++; // y is now 11

這裡將yref宣告為型別int&:乙個對y的引用,它被稱作左值引用(lvalue reference)。現在你可以開心地通過該引用改變y的值了。

我們知道,乙個引用必須只想乙個具體的記憶體位置中的乙個已經存在的物件,即乙個左值。這裡y確實存在,所以**執行完美。

現在,如果我縮短整個過程,嘗試將10直接賦值給我的引用,並且沒有任何物件持有該引用,將會發生什麼?

int& yref = 10; // will it work?

在右邊我們有乙個臨時值,乙個需要被儲存在乙個左值中的右值。在左邊我們有乙個引用(乙個左值),他應該指向乙個已經存在的物件。但是10 是乙個數字常量(numeric constant),也就是乙個左值,將它賦給引用與引用所表述的精神衝突。

如果你仔細想想,那就是被禁止的從右值到左值的轉換。乙個volitile的數字常量(右值)如果想要被引用,需要先變成乙個左值。如果那被允許,你就可以通過它的引用來改變量字常量的值。相當沒有意義,不是嗎?更重要的是,一旦這些值不再存在這些引用該指向**呢?

下面的**片段同樣會發生錯誤,原因跟剛才的一樣:

void fnc(int& x)

int main()

我將乙個臨時值10傳入了乙個需要引用作為引數的函式中,產生了將右值轉換為左值的錯誤。這裡有乙個解決方法(workaround),創造乙個臨時的變數來儲存右值,然後將變數傳入函式中(就像注釋中寫的那樣)。將乙個數字傳入乙個函式確實不太方便。

六、常量左值引用

error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
gcc認為引用不是const的,即乙個常量。根據c++規範,你可以將乙個const的左值繫結到乙個右值上,所以下面的**可以成功執行:

const int& ref = 10; // ok!

當然,下面的也是:

void fnc(const int& x)

int main()

背後的道理是相當直接的,字面常量10是volatile的並且會很快失效(expire),所以給他乙個引用是沒什麼意義的。如果我們讓引用本身變成常量引用,那樣的話該引用指向的值就不能被改變了。現在右值被修改的問題被很好地解決了。同樣,這不是乙個技術限制,而是c ++人員為避免愚蠢麻煩所作的選擇。

應用:c++中經常通過常量引用來將值傳入函式中,這避免了不必要的臨時物件的建立和拷貝。

編譯器會為你建立乙個隱藏的變數(即乙個左值)來儲存初始的字面常量,然後將隱藏的變數繫結到你的引用上去。那跟我之前的一組**片段中手動完成的是一碼事,例如:

// the following…

const int& ref = 10;

// … would translate to:

int __internal_unique_name = 10;

const int& ref = __internal_unique_name;

現在你的引用指向了真實存在的事物(知道它走出作用域外)並且你可以正常使用它,出克改變他指向的值。

const int& ref = 10;

std::cout << ref << 「\n」; // ok!

std::cout << ++ref << 「\n」; // error: increment of read-only reference 『ref』

七、結論

C 中的左值和右值

1.概念變數和文字常量都有儲存區,並且有相關的型別,區別在於變數是可定址的 對於每個變數,都有2個值與其相關聯 1 資料值,儲存在某個記憶體位址中,也稱右值 rvalue 右值是被讀取的值 read value 文字常量和變數都可被用於右值。2 位址值,即儲存資料值的那塊記憶體位址,也稱左值 lva...

c 中的左值和右值

左值 lvalue 右值 rvalue 是 c c 中乙個比較晦澀的概念,有的人可能甚至沒有聽過,但這個概念到了 c 11 後卻變得十分重要,它們是理解move forward 等新語義的基礎。左值與右值這兩概念是從c中傳承而來的,在c中,左值指的是能夠出現在等號左邊及右邊的變數 表示式 右值則指的...

C 中的左值和右值

左值 lvalue 應該被解釋被location value,即可被定址的值 右值 rvalue 應該被解釋為read value,即唯讀的值,而不能被定址 這樣理解應該更為準確一點 通俗的講,左值就是能夠出現在賦值符號左面的東西,而右值就是那些可以出現在賦值符號右面的東西了。舉個很簡單的例子 a ...