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

Attention:this blog is a translation of https://www.internalpointers.com/post/understanding-meaning-lvalues-and-rvalues-c ,which is posted by @internalpoiners.

一耗跛、前言

一直以來毒嫡,我都對(duì)C++中左值(lvalue)和右值(lvalue)的概念模糊不清搁吓。我認(rèn)為是時(shí)候好好理解他們了,因?yàn)檫@些概念隨著C++語言的進(jìn)化變得越來越重要。

二、左值和右值——一個(gè)友好的定義

首先,讓我們避開那些正式的定義昔期。在C++中,一個(gè)左值是指向一個(gè)指定內(nèi)存的東西肄渗。另一方面镇眷,右值就是不指向任何地方的東西咬最。通常來說翎嫡,右值是暫時(shí)和短命的,而左值則活的很久永乌,因?yàn)樗麄円宰兞康男问剑╲ariable)存在惑申。我們可以將左值看作為容器(container)而將右值看做容器中的事物。如果容器消失了翅雏,容器中的事物也就自然就無法存在了圈驼。
讓我們現(xiàn)在來看一些例子:

int x = 666; //ok

在這里,666是一個(gè)右值望几。一個(gè)數(shù)字(從技術(shù)角度來說他是一個(gè)字面常量(literal constant))沒有指定的內(nèi)存地址绩脆,當(dāng)然在程序運(yùn)行時(shí)一些臨時(shí)的寄存器除外。在該例中橄抹,666被賦值(assign)給x靴迫,x是一個(gè)變量。一個(gè)變量有著具體(specific)的內(nèi)存位置楼誓,所以他是一個(gè)左值玉锌。C++中聲明一個(gè)賦值(assignment)需要一個(gè)左值作為它的左操作數(shù)(left operand):這完全合法。
對(duì)于左值x疟羹,你可以做像這樣的操作:

int* y = &x;  //ok

在這里我通過取地址操作符&獲取了x的內(nèi)存地址并且把它放進(jìn)了y主守。&操作符需要一個(gè)左值并且產(chǎn)生了一個(gè)右值,這也是另一個(gè)完全合法的操作:在賦值操作符的左邊我們有一個(gè)左值(一個(gè)變量)榄融,在右邊我們使用取地址操作符產(chǎn)生的右值参淫。
然而,我們不能這樣寫:

int y;
666 = y; //error!

可能上面的結(jié)論是顯而易見的愧杯,但是從技術(shù)上來說是因?yàn)?code>666是一個(gè)字面常量也就是一個(gè)右值涎才,它沒有一個(gè)具體的內(nèi)存位置(memory location),所以我們會(huì)把y分配到一個(gè)不存在的地方民效。
下面是GCC給出的變異錯(cuò)誤提示:

error: lvalue required as left operand of assignment

賦值的左操作數(shù)需要一個(gè)左值憔维,這里我們使用了一個(gè)右值666涛救。
我們也不能這樣做:

int* y = &666;//   error~

GCC給出了以下錯(cuò)誤提示:

error: lvalue required as unary '&' operand`

&操作符需要一個(gè)左值作為操作數(shù),因?yàn)橹挥幸粋€(gè)左值才擁有地址业扒。

三检吆、返回左值和右值的函數(shù)

我們知道一個(gè)賦值的左操作數(shù)必須是一個(gè)左值,因此下面的這個(gè)函數(shù)肯定會(huì)拋出錯(cuò)誤:lvalue required as left operand of assignment

int setValue()
{
    return 6;
}

// ... somewhere in main() ...

setValue() = 3; // error!

錯(cuò)誤原因很清楚:setValue()返回了一個(gè)右值(一個(gè)臨時(shí)值6)程储,他不能作為一個(gè)賦值的左操作數(shù)〔渑妫現(xiàn)在,我們看看如果函數(shù)返回一個(gè)左值章鲤,這樣的賦值會(huì)發(fā)生什么變化摊灭。看下面的代碼片段(snippet):

int global = 100;

int& setGlobal()
{
    return global;    
}

// ... somewhere in main() ...

setGlobal() = 400; // OK

該程序可以運(yùn)行败徊,因?yàn)樵谶@里setGlobal()返回一個(gè)引用(reference)帚呼,跟之前的setValue()不同。一個(gè)引用是指向一個(gè)已經(jīng)存在的內(nèi)存位置(global變量)的東西皱蹦,因此它是一個(gè)左值煤杀,所以它能被賦值。注意這里的&:它不是取地址操作符沪哺,他定義了返回的類型(一個(gè)引用)沈自。
可以從函數(shù)返回左值看上去有些隱晦,它在你做一些進(jìn)階的編程例如實(shí)現(xiàn)一些操作符的重載(implementing overload operators)時(shí)會(huì)很有作用辜妓,這些知識(shí)會(huì)在未來的章節(jié)中講述枯途。

四、左值到右值的轉(zhuǎn)換

一個(gè)左值可以被轉(zhuǎn)換(convert)為右值籍滴,這完全合法且經(jīng)常發(fā)生酪夷。讓我們先用+操作符作為一個(gè)例子,根據(jù)C++的規(guī)范(specification)异逐,它使用兩個(gè)右值作為參數(shù)并返回一個(gè)右值(譯者按:可以將操作符理解為一個(gè)函數(shù))捶索。
讓我們看下面的代碼片段:

int x = 1;
int y = 3;
int z = x + y;   // ok

等一下,xy是左值灰瞻,但是加法操作符需要右值作為參數(shù):發(fā)生了什么腥例?答案很簡(jiǎn)單:xy經(jīng)歷了一個(gè)隱式(implicit)的左值到右值(lvalue-to-rvalue)的轉(zhuǎn)換。許多其他的操作符也有同樣的轉(zhuǎn)換——減法酝润、加法燎竖、除法等等。

五要销、左值引用

相反呢构回?一個(gè)右值可以被轉(zhuǎn)化為左值嗎?不可以,它不是技術(shù)所限纤掸,而是C++編程語言就是那樣設(shè)計(jì)的脐供。
在C++中,當(dāng)你做這樣的事:

int y = 10;
int& yref = y;
yref++;        // y is now 11

這里將yref聲明為類型int&:一個(gè)對(duì)y的引用借跪,它被稱作左值引用(lvalue reference)≌海現(xiàn)在你可以開心地通過該引用改變y的值了。
我們知道掏愁,一個(gè)引用必須只想一個(gè)具體的內(nèi)存位置中的一個(gè)已經(jīng)存在的對(duì)象歇由,即一個(gè)左值。這里y確實(shí)存在果港,所以代碼運(yùn)行完美沦泌。
現(xiàn)在,如果我縮短整個(gè)過程辛掠,嘗試將10直接賦值給我的引用谢谦,并且沒有任何對(duì)象持有該引用,將會(huì)發(fā)生什么公浪?

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

在右邊我們有一個(gè)臨時(shí)值他宛,一個(gè)需要被存儲(chǔ)在一個(gè)左值中的右值。在左邊我們有一個(gè)引用(一個(gè)左值)欠气,他應(yīng)該指向一個(gè)已經(jīng)存在的對(duì)象。但是10 是一個(gè)數(shù)字常量(numeric constant)镜撩,也就是一個(gè)左值预柒,將它賦給引用與引用所表述的精神沖突。
如果你仔細(xì)想想袁梗,那就是被禁止的從右值到左值的轉(zhuǎn)換宜鸯。一個(gè)volitile的數(shù)字常量(右值)如果想要被引用,需要先變成一個(gè)左值遮怜。如果那被允許淋袖,你就可以通過它的引用來改變數(shù)字常量的值。相當(dāng)沒有意義锯梁,不是嗎即碗?更重要的是,一旦這些值不再存在這些引用該指向哪里呢陌凳?
下面的代碼片段同樣會(huì)發(fā)生錯(cuò)誤剥懒,原因跟剛才的一樣:

void fnc(int& x)
{
}

int main()
{
    fnc(10);  // Nope!
    // This works instead:
    // int x = 10;
    // fnc(x);
}

我將一個(gè)臨時(shí)值10傳入了一個(gè)需要引用作為參數(shù)的函數(shù)中,產(chǎn)生了將右值轉(zhuǎn)換為左值的錯(cuò)誤合敦。這里有一個(gè)解決方法(workaround)初橘,創(chuàng)造一個(gè)臨時(shí)的變量來存儲(chǔ)右值,然后將變量傳入函數(shù)中(就像注釋中寫的那樣)。將一個(gè)數(shù)字傳入一個(gè)函數(shù)確實(shí)不太方便保檐。

六耕蝉、常量左值引用

先看看GCC對(duì)于之前兩個(gè)代碼片段給出的錯(cuò)誤提示:

error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

GCC認(rèn)為引用不是const的,即一個(gè)常量夜只。根據(jù)C++規(guī)范赔硫,你可以將一個(gè)const的左值綁定到一個(gè)右值上,所以下面的代碼可以成功運(yùn)行:

const int& ref = 10;  // OK!

當(dāng)然盐肃,下面的也是:

void fnc(const int& x)
{
}

int main()
{
    fnc(10);  // OK!
}

背后的道理是相當(dāng)直接的爪膊,字面常量10volatile的并且會(huì)很快失效(expire),所以給他一個(gè)引用是沒什么意義的砸王。如果我們讓引用本身變成常量引用推盛,那樣的話該引用指向的值就不能被改變了。現(xiàn)在右值被修改的問題被很好地解決了谦铃。同樣耘成,這不是一個(gè)技術(shù)限制,而是C ++人員為避免愚蠢麻煩所作的選擇驹闰。
應(yīng)用:C++中經(jīng)常通過常量引用來將值傳入函數(shù)中瘪菌,這避免了不必要的臨時(shí)對(duì)象的創(chuàng)建和拷貝。
編譯器會(huì)為你創(chuàng)建一個(gè)隱藏的變量(即一個(gè)左值)來存儲(chǔ)初始的字面常量嘹朗,然后將隱藏的變量綁定到你的引用上去师妙。那跟我之前的一組代碼片段中手動(dòng)完成的是一碼事,例如:

// the following...
const int& ref = 10;

// ... would translate to:
int __internal_unique_name = 10;
const int& ref = __internal_unique_name;

現(xiàn)在你的引用指向了真實(shí)存在的事物(知道它走出作用域外)并且你可以正常使用它屹培,出克改變他指向的值默穴。

const int& ref = 10;
std::cout << ref << "\n";   // OK!
std::cout << ++ref << "\n"; // error: increment of read-only reference ‘ref’

七、結(jié)論

理解左值和右值的含義讓我弄清楚了幾個(gè)C++內(nèi)在的工作方式褪秀。C++11進(jìn)一步推動(dòng)了右值的限定蓄诽,引入了右值引用(rvalue reference)和移動(dòng)(move semantics)的概念。這些將在下一篇文章中介紹媒吗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仑氛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闸英,更是在濱河造成了極大的恐慌锯岖,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件自阱,死亡現(xiàn)場(chǎng)離奇詭異嚎莉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)沛豌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門趋箩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赃额,“玉大人,你說我怎么就攤上這事叫确√迹” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵竹勉,是天一觀的道長(zhǎng)飞盆。 經(jīng)常有香客問我,道長(zhǎng)次乓,這世上最難降的妖魔是什么吓歇? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮票腰,結(jié)果婚禮上城看,老公的妹妹穿的比我還像新娘。我一直安慰自己杏慰,他們只是感情好测柠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缘滥,像睡著了一般轰胁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朝扼,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天赃阀,我揣著相機(jī)與錄音,去河邊找鬼吟税。 笑死凹耙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肠仪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼备典,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼异旧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起提佣,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤吮蛹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拌屏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潮针,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年倚喂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了每篷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓣戚。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖焦读,靈堂內(nèi)的尸體忽然破棺而出子库,到底是詐尸還是另有隱情,我是刑警寧澤矗晃,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布仑嗅,位于F島的核電站,受9級(jí)特大地震影響张症,放射性物質(zhì)發(fā)生泄漏仓技。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一俗他、第九天 我趴在偏房一處隱蔽的房頂上張望脖捻。 院中可真熱鬧,春花似錦拯辙、人聲如沸郭变。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诉濒。三九已至,卻和暖如春夕春,著一層夾襖步出監(jiān)牢的瞬間未荒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工及志, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留片排,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓速侈,卻偏偏與公主長(zhǎng)得像率寡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倚搬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 襟懷先后論冶共,① 詠記岳晹樓。 處世無良策每界,② 經(jīng)邦有好謀捅僵。③ 生前民已識(shí), 死后帝方愁眨层。 更誦公文意庙楚, 憑欄熱淚流...
    常將三俠閱讀 889評(píng)論 22 18
  • 作文導(dǎo)師團(tuán)溜溜梅“天天圖說”第 一天:小作家樂樂。題目吃蘋果:在一個(gè)陽光明媚的早晨趴樱,小紅去果園摘了一藍(lán)又大又圓的蘋...
    冰魚燕閱讀 218評(píng)論 0 0
  • 陽光溫暖著大地 柳條輕輕搖弋 微風(fēng)蕩漾著湖面 獨(dú)自一人屹立在高樓的窗邊 看著路上過往的人群馒闷、有多少人帶著空虛的靈魂...
    喚醒人生閱讀 200評(píng)論 0 1
  • 選擇器是jQuery的根基酪捡,在jQuery中,對(duì)事件處理窜司,遍歷DOM和Ajax操作都依賴于選擇器沛善。--<鋒利的jq...
    匿名用戶404閱讀 270評(píng)論 0 4