// 以下是我個人的理解,如有錯誤請告知,非常感謝你。<br />#include <string><br />#include <iostream><br />using namespace std;<br />string one("cute"); // non-const lvalue<br />const string two("fluffy"); // const lvalue<br />string three() // non-const rvalue<br />{<br /> return "kittens";<br />}<br />const string four() // const rvalue<br />{<br /> return "are an essential part of a healthy diet";<br />}<br />string& five(string& i)<br />{<br /> std::cout << i;<br /> i = "haha";<br /> return i;<br />}<br />void six(const string& i)<br />{<br />}<br />void six(string& i)<br />{<br />}<br />// 1,左值,右值針對錶達式而言,而不是針對對象。<br />// 2,運算式是一個或多個運算元通過操作符組合而成,最簡單的運算式只包含一個字面值或變數。<br />// 我認為函數調用也是一個運算式,因為操作符對運算元操作是函數調用。<br />// 3,每個運算式都會產生一個結果,這個結果可以分成左值和右值,這是一個全集,只有兩種可能。<br />// 4,什麼是語句,語句是運算式加分號。<br />// 5,lvalue 是指那些單一運算式結束之後依然存在的持久對象。例如: obj,*ptr, prt[index], ++x 都是 lvalue。<br />// 6,上面的話強調一個運算元,一個操作符的運算式返回的結果,在運算式結束之後依然存在的持久對象,<br />// 7,如果說這個運算式結束之後就是一個分號,那麼這個運算式的結果,也不會消失。<br />// 8,另一個培養判斷一個運算式是不是 lvalue 的直覺感的方法就是自問一下"我能不能對錶達式取址?",<br />// 9,如果能夠,那就是一個 lvalue;如果不能,那就是 一個 rvalue。<br />int main()<br />{<br /> 345; // 345這是一個字面值,所以它又是一個運算式,加上一個分號就構成了語句,這些都是合法的。<br /> // 這個字面值是不可以取址的,所以字面值運算式的結果是一個右值。<br /> string i = "hehe";<br /> i; // i是一個變數,所有它也是一個運算式,加上一個分號就構成了語句。<br /> // 這個運算式的值就是對象i,在分號之後繼續存在,而且也可以被取址,所以這個運算式是一個左值。<br /> three(); // three()是一個運算式,這個運算式的結果是一個string類型的臨時對象,這個對象在分號之後就會消失,<br /> // 我不知道它能不能被取址,反正它是右值。<br /> // 不明白如果它是右值,它就是不能被取址麼,還是說8和5都是必要條件,都滿足才是左值,右值不一定都要滿足,<br /> // 有一個滿足就是右值,還有因為是全集的關係,非左值即是右值。<br /> four(); // four()是一個運算式,這個運算式的結果是一個const string類型的臨時對象,在分號之後就會消失,<br /> // 所以這是一個const右值。<br /> five(i); // five(i)是一個運算式,若且唯若函數調用返回一個引用的時候,函數調用運算式是一個左值。<br /> // Type&可綁定到non-const lvalue,比如本例(可以用這個引用來讀取和修改原來的值),<br /> // 但不能綁定到const lvalue,因為那將違背const正確性;<br /> // 也不能把它綁定到non-const rvalue,這樣做極端危險,你用這個引用來修改臨時對象,但臨時對象早就不存在了,<br /> // 這將導致難以捕捉而令人討厭的 bug,因此 C++ 明智地禁止這這麼做。<br /> five(three()); // Type&綁定到non-const rvalue,這句話不是不行麼,vs2008就是能夠編譯通過且運行沒有問題呀,服了。<br /> // 把編譯選項改為了/w4,出了warning c4239,MSDN:This type conversion is not allowed by the C++ standard,<br /> // but it is permitted here as an extension. This warning is always followed by at least one line of<br /> // explanation describing the language rule being violated.<br /> // C4239.cpp<br /> // compile with: /W4 /c<br /> // struct C {<br /> // C() {}<br /> // };<br /> // void func(void) {<br /> // C & rC = C(); // C4239,C()運算式的結果是標準的non-const rvalue,匿名對象,臨時變數等等叫法我不知道對不對,但是<br /> // non-const rvalue應該是正確的叫法<br /> // const C & rC2 = C(); // OK<br /> // rC2;<br /> // }<br /> // 運算式的結果實際上有四種,non-const lvalue, non-const rvalue, const lvalue, const rvalue<br /> // Type&只可能綁定到non-const lvalue, 不能綁定到const lvalue, const rvalue,顯而易見,因為它們是const 而Type&不是。<br /> // 不能綁定到non-const rvalue已經如上作了說明。<br /> // 關於為什麼,c++語言認為,定義一個非const的函數,你就是有要修改傳入實參的企圖,如果你不想<br /> // 修改它,為什麼不把函數定義一個接受const對象的函數,即定義為const Type&。而一但你把函數定義為Type&,那麼c++語言就認為<br /> // 在二種non-const這一的non-const rvalue就危險了,所以c++壓根就禁止了你這麼做,因為non-cnost rvalue的生命期的問題。<br /> // 總結Type&只能接受四種類型之一的non-const lvalue。兩種情況下常見,一種是直接的定義引用時,一種是five這種情況。<br /> six(i); // 函數six有個重載版本,一個接受const string&, 一個接受string&,當實參為一個non-const的時候,<br /> // i比如本例,這時候會調用<br /> // non-const版本,優先匹配,對於const Type&它能接受四種類型中的每一種類型的對象,對於lvalue的兩種類型沒有什麼說的,<br /> // 對於rvalue的兩種類型,它會先產生一個臨時變數,然後讓引用關聯這個臨時變數,而且我認為這不是一個臨時變數,而是一個<br /> // 臨時的局部變數,因為在分號之後,這個臨時變數應該還不在其生命期內的,因為引用還是要有關聯的記憶體的,引用是左值,總<br /> // 要可以取址吧,取到一個臨時變數是不可以的,所以我把這種變數叫臨時局部變數,說它臨時,是因為程式員看不到,說它局部<br /> // 是因為它的生命期應該從定義到函數結尾return這前。這個在c++ primer的書上也是這樣講了一點,和我說的不一樣,<br /> // double dval = 3.14;<br /> // cosnt int& i = dval; // 書中說編譯器會把這些代碼轉換成如下形式的代碼<br /> //<br /> // double dval = 3.14;<br /> // int temp = dval;<br /> // const int& i = temp;<br /> return 0;<br />}
以下是一些轉載
C++ 03 標準 3.10/1 節上說: "每一個運算式要麼是一個 lvalue ,要麼就是一個 rvalue 。" 應該謹記 lvalue 跟 rvalue 是針對錶達式而言的,而不是對象。
lvalue 是指那些單一運算式結束之後依然存在的持久對象。例如: obj,*ptr, prt[index], ++x 都是 lvalue。
rvalue 是指那些運算式結束時(在分號處)就不複存在了的臨時對象。例如: 1729 , x + y , std::string("meow") , 和 x++ 都是 rvalue。
注意 ++x 和 x++ 的區別。當我們寫 int x = 0; 時, x 是一個 lvalue,因為它代表一個持久對象。 運算式 ++x 也是一個 lvalue,它修改了 x 的值,但還是代表原來那個持久對象。然而,運算式 x++ 卻是一個 rvalue,它只是拷貝一份持久對象的初值,再修改持久對象的值,最後返回那份拷貝,那份拷貝是臨時對象。 ++x 和 x++ 都遞增了 x,但 ++x 返回持久對象本身,而 x++ 返回臨時拷貝。這就是為什麼 ++x 之所以是一個 lvalue,而 x++ 是一個
rvalue。 lvalue 與 rvalue 之分不在於運算式做了什麼,而在於運算式代表了什麼(持久對象或臨時產物)。
另一個培養判斷一個運算式是不是 lvalue 的直覺感的方法就是自問一下"我能不能對錶達式取址?",如果能夠,那就是一個 lvalue;如果不能,那就是 一個 rvalue。 例如:&obj , &*ptr , &ptr[index] , 和 &++x 都是合法的(即使其中一些例子很蠢),而 &1729 , &(x + y) , &std::string("meow") , 和 &x++ 是不合法的。為什麼這個方法湊效?因為取址操作要求它的"運算元必須是一個 lvalue"(見 C++ 03 5.3.1/2)。為什麼要有那樣的規定?因為對一個持久對象取址是沒問題的,但對一個臨時對象取址是極端危險的,因為臨時對象很快就會被銷毀(譯註:就像你有一個指向某個對象的指標,那個對象被釋放了,但你還在使用那個指標,鬼知道這時候指標指向的是什麼東西)。
前面的例子不考慮操作符重載的情況,它只是普通的函數調用語義。"一個函數調用是一個 lvalue 若且唯若它返回一個引用"(見 C++ 03 5.2.2/10)。因此,給定語句 vercor<int> v(10, 1729); , v[0] 是一個 lvalue,因為操作符 []() 返回 int& (且 &v[0] 是合法可用的); 而給定語句 string s("foo");和 string t("bar");,s + t 是一個rvalue,因為操作符 +() 返回 string(而 &(s + t)
也不合法)。
lvalue 和 rvalue 兩者都有非常量(modifiable,也就是說non-const)與常量(const )之分。舉例來說:
string one("cute");
const string two("fluffy");
string three() { return "kittens"; }
const string four() { return "are an essential part of a healthy diet"; }
one; // modifiable lvalue
two; // const lvalue
three(); // modifiable rvalue
four(); // const rvalue
Type& 可綁定到非常量 lvalue (可以用這個引用來讀取和修改原來的值),但不能綁定到 const lvalue,因為那將違背 const 正確性;也不能把它綁定到非常量 rvalue,這樣做極端危險,你用這個引用來修改臨時對象,但臨時對象早就不存在了,這將導致難以捕捉而令人討厭的 bug,因此 C++ 明智地禁止這這麼做。(我要補充一句:VC 有一個邪惡的擴充允許這麼蠻幹,但如果你編譯的時候加上參數 /W4 ,編譯器通常會提示警告"邪惡的擴充被啟用了")。也不能把它綁定到 const ravlue,因為那會是雙倍的糟糕。(細心的讀者應該注意到了我在這裡並沒有談及模板參數推導)。
const Type& 可以綁定到: 非常量 lvalues, const lvalues,非常量 rvalues 以及 const values。(然後你就可以用這個引用來觀察它們)
引用是具名的,因此一個綁定到 rvalue 的引用,它本身是一個 lvalue(沒錯!是 L)。(因為只有 const 引用可以綁定到 rvalue,所以它是一個 const lvalue)。這讓人費解,(不弄清楚的話)到後面會更難以理解,因此我將進一步解釋。給定函數 void observe(const string& str), 在 observe()'s 的實現中, str 是一個 const lvalue,在 observe() 返回之前可以對它取址並使用那個地址。這一點即使我們通過傳一個 rvalue
參數來調用 observe()也是成立的 ,就像上面的 three() 和 four()。也可以調用 observe("purr"),它構建一個臨時 string 並將 str 綁定到那個臨時 string。three() 和 foure() 的返回對象是不具名的,因此他們是 rvalue,但是在 observe()中,str 是具名的,所以它是一個 lvalue。正如前面我說的" lvalue 跟 rvalue 是針對錶達式而言的,而不是對象"。當然,因為 str 可以被綁定到一個很快會被銷毀的臨時對象,所以在
observe() 返回之後我們就不應該在任何地方儲存這個臨時對象的地址。
你有沒有對一個綁定到 rvalue 的 const 引用取址過嗎?當然,你有過!每當你寫一個帶自賦值檢查的拷貝賦值操作符: Foo& operator=(const Foo& other), if( this != &other) { copy struff;}; 或從一個臨時變數來拷貝賦值,像: Foo make_foo(); Foo f; f = make_foo(); 的時候,你就做了這樣的事情。
這個時候,你可能會問"那麼非常量 rvalues 跟 const rvalues 有什麼不同呢?我不能將 Type& 綁定到非常量 rvalue 上,也不能通過賦值等操作來修改 rvalue,那我真的可以修改它們?" 問的很好!在 C++ 98/03 中,這兩者存在一些細微的差異: non-constrvalues 可以調用 non-const 成員函數。 C++ 不希望你意外地修改臨時對象,但直接在non-const rvalues上調用 non-const 成員函數,這樣做是很明顯的,所以這是被允許的。在
C++ 0x中,答案有了顯著的變化,它能用來實現 move 語意。
恭喜!你已經具備了我所謂的"lvalue/rvalue 觀",這樣你就能夠一眼就判斷出一個運算式到底是 lvalue 還是 rvalue。再加上你原來對 const 的認識,你就能完全理解為什麼給定語句 void mutate(string& ref) 以及前面的變數定義, mutate(one) 是合法的,而 mutate(two), mutate(three()), mutate(four()), mutate("purr") 都是不合法的。如果你是 C++ 98/03 程式員,你已經可以分辨出這些調用中的哪些是合法的,哪些是不合法的;是你的"本能直覺",而不是你的編譯器,告訴你
mutate(three()) 是假冒的。你對 lvalue/rvalue 的新認識讓你明確地理解為什麼 three() 是一個 rvalue,也知道為什麼非常量引用不能綁定到右值。知道這些有用嗎?對語言律師而言,有用,但對普通程式員來說並不見得。畢竟,你如果不理解關於 lvalues 和 rvalues 一切就要領悟這個還隔得遠呢。但是重點來了:與 C++ 98/03 相比, C++ 0x 中的 lvalue 和 rvalue 有著更廣泛更強勁的含義(尤其是判斷運算式是否是 modifiable / const
的 lvalue/rvalue,並據此做些處理)。要有效地使用 C++ 0x,你也需具備對 lvalue/rvalue 的理解。現在萬事具備,我們能繼續前行了。