關於左值和右值的Q & A


Q作為一個程序員,為什么要弄明白左值的概念?
A
:有很多原因。比如說,有些語境下必須要使用左值,如果你不知道哪些表達式是左值,你就可能給錯。

Q請問哪些語境下必須要使用左值
A
下列運算符的操作數要求左值sizeof運算符, 取地址運算符 & , ++ 運算符, -- 運算符,賦值 = 運算符的左側,成員 . 運算符的左側

Q
那么如何判斷一個表達式是左值
A
:依據標准的定義來判斷。[C99]An lvalue is an expression with an object type or an incomplete type other than void; 也就是說,如果一個表達式具有對象類型或非空不完整類型,那就是左值。其實這里關鍵的是對象類型,雖然不完整類型不是對象類型,但由於它可以通過某種方式補充完整,因此可以認為它也是一種對象類型;但void除外,因為void不能被補充完整。

Q
那么如何判斷一個表達式具有對象類型
A
:那我們要先搞清楚什么是對象類型。Types are partitioned into object types (types that fully describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes). 所以通過非函數類型聲明的非類型標識符”都是左值

Q那么如何判斷那些由標識符和運算符共同構成的表達式是否左值呢?
A
每種運算符都規定了它的運算結果是否左值以及它的操作數是否期待左值,依據這些規則就可以判定一個表達式是否左值(以下簡稱它們是左值規則)。其實也不用記得太牢,因為如果你弄錯了,編譯器會報錯的,嘿嘿。

Q
有哪些常見的左值規則
A

(1)
最明顯的左值表達式的例子是有適當類型和內存的標識符;
(2)
間接運算符*的運算結果是左值;
(3)
取地址運算符&的運算結果是右值;
(4)
下列表達式不能產生lvalue: 數組名,函數,枚舉常量,賦值表達式,強制類型轉換(目標類型是引用時除外),函數調用。

Q
暈,*ptr&a都是指針,為嘛一個是左值、一個是右值啊?
A
:簡單說,這就是規則。如果你覺得死記硬背這個規則比較累的話,不妨看看飛雪關於*ptr返回對象的解釋,來幫助你的記憶:*在這里是解引用運算,相當於說,有一個對象被盒子包起來了,然后用*運算打開盒子,使用這個對象。也就是ptr這個指針包裹了它所指向的對象pa,通過*運算,就獲取了pa這個對象。

Q
明白一點了,有沒有什么更通用的幫助理解記憶上述規則的方法呢?
A:來看hpsmouse寫的這個心得:左值和右值的概念寫代碼寫多了就會自然產生相應感覺的——就是飛雪提到的可訪問的存儲。一些感性的例子:
    a+b這個值是一個相當的東西,我們只知道它是 a+b 的結果,但不能掌握它的具體情況,也不太關心它到底怎樣出現甚至是否出現。
    *p 就不一樣,因為 p 是個指針,它必然有一個值,不管是有效的還是無效的,那么那個值代表的內存區域就肯定有某個東西,這是實實在在的。
   
至於 &a,我們只知道它是 a 的地址,同樣不知道也不關心它到底怎樣出現或是否出現。
於是,我們把 a+b&a 這樣有些的表達式稱為 rvalue,把 *p 這樣實實在在的表達式稱為 lvalue

其實,hpsmouse“把 *p 這樣實實在在的表達式稱為 lvalue”,多少觸及到了lvalue的重要實質(pmerOFc語)。實際上,lvalue中的“l”可以理解為“location”(來自這篇文章,謝謝Tiger_Zhao提供鏈接)。早期的左值定義(比如C89)指的就是一個其結果有adrressable location(可以尋址的存儲)的表達式,你可以往這個location放數據或信息。(The "l" in lvalue can be though of as location, meaning that an expression has an addressable location that data or information can be put into.

Q
貌似有點明白了,為什么要強調可訪問的存儲?難道還有些擁有存儲卻不可訪問的表達式結果么?
A
:對的,來看如下例子(來自飛雪): foo的返回的是一個int,這個int的值是1,這個值是擁有存儲的,但是你不應該知道;a + 1會通過+產生一個值,這個值是擁有存儲的,但是這個存儲也不是你應該知道的。所以,它們都不是左值。只有當你擁有表達式的存儲的訪問權時,你才可以把這個表達式放在=的左邊,通過賦值來改變這個對象的狀態。

其實左值無非是可以引用到對象的表達式,因此左值的概念和C里的對象是密不可分的,只要理解好了對象,就比較好把握左值了。C里的對象 (注意和“面向對象”里的“對象”完全兩回事)是指一塊命名的內存區域
An Object is a named region of storage—From The C Programming Language”)。所以,左值的關鍵擁有你可訪問的存儲

當然左值概念經過發展后,已經不再介意一個左值引用的對象是否真的存在了,重要的是,這個左值具有對象或非空不完整類型。例如(來自supermegaboy):
int a; 
int foo(){return 1;}
double i; 
int *p = ( int* )&i; /*p並非真正指向一個int對象,但*p依然是左值*/
int *p;
*p = .........; /*p並沒有指向啥,但*p還是左值*/
Q說了這么多左值,那右值的定義是什么呢?
A[C99]右值(rvalue是指表達式的值。(46頁腳注)What is sometimes called rvalueis in this International Standard described as the “value of an expression”.
實際上,右值里的“r”其實也有“read”的意思(The "r" in rvalue can be thought of as "read" value來自這篇文章),也就是要把存在左值的存儲空間里的東西讀出來。當然,這只是個用於幫助理解記憶的經不起推敲的說法,實際中很多右值並沒有對應的左值,更談不上從什么地方讀出來了。

Q
有點暈。那左值表達式的值也是右值?
A:恩,對。實際上,除了上面必須使用左值的場合以外,所有左值表達式(數組類型的左值除外)在使用的時候其實是被轉化為它所引用的對象所存儲的值使用的。 Except when it is the operand of the sizeof .operator. the unary h operator. the ++ operator. the -- operator. or the left operand of the . operator or an assignment -operator. an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue).
(上述道理和pmerOFc爭論了大半夜才弄明白,感謝pmerOFcsupermegaboy。以上內容譯摘自C89 6.2.2.1)。

也就是說,C中,一個左值表達式,除了應用於一些特定的運算符時以外,在使用時會自動進行左值到右值的轉換,並一直進行到無法再轉換為止。因此,C里表達式的值一定是右值supermegaboy語),而且一定是不擁有可訪問的存儲的。在C++標准里也有類似的說法(飛雪提供)Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue

小結
(1)
定義和含義
     a)
左值是指具有對象類型或非空不完整類型的表達式。(關鍵是要可以引用到對象,也就是要可以擁有可訪問的存儲l-location
  b) 右值(rvalue
是指表達式的值。(在C里表達式的值一定是右值;在期待右值時,左值會自動轉化為右值。r-read
(2)
依據下述規則來判斷左值:
  a)
“通過非函數類型聲明的非類型標識符”都是左值
  b)
每種運算符都規定了它的運算結果是否左值。
(3)
常見規則
  a)
下列運算符的操作數要求左值:Sizeof運算符, 取地址運算符 & , ++ 運算符, -- 運算符,賦值 = 運算符的左側,成員 . 運算符的左側。
  b)
間接運算符*的運算結果是左值;取地址運算符&的運算結果是右值。
  c)
下列表達式不能產生lvalue: 數組名,函數,枚舉常量,賦值表達式,強制類型轉換(目標類型是引用時除外),函數調用。
文章來源:http://apps.hi.baidu.com/share/detail/433907

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com