左值与右值

左值就是那些能够出现在赋值符号左边的东西。

 右值就是那些可以出现赋值符号右边的东西。

掺合汇编 联想
  例如: 
       a = b + 25;
     a就是一个左值,因为它标识了一个可以存储结果值的地点,b + 25是个右值,因为它指定了一个值。但是它们可以互换吗? b + 25 = a; 原先用作左值的a此时也可以当作右值,因为每个位置都包含一个值。然而,b + 25不能作为左值,因为它并未标识一个特定的位置。因此,这条赋值语句是非法的。
     注意当计算机计算b + 25时,它的结果必然保存于机器的某个地方。但是,程序员并没有办法预测该结果会存储在什么地方,也无法保证这个表达式的值下次还会存储于那个地方。其结果是,这个表达式不是一个左值。基于同样的理由,字面值常量也都不是左值。
     听上去似乎是变量可以作为左值而表达式不能作为左值,但这个推断并不准确。在下面的赋值语句中,左值便是一个表达是式。
    int a[30];
    ...
    a[b + 10] = 0;
    下标引用实际上是一个操作符,所以表达式的左边实际上是表达式,但它却是一个合法的左值,因为它标识一个特定的位置,我们以后可以在程序中引用它。这里另外一个例子:
    int a, *pi;
    ...
    pi = &a;
    *pi = 20;
    第2条赋值语句,它左边的那个值显然是一个表达式,但它却是一个合法的左值。为什么?指针pi的值是内存中某个特定位置的地址,*操作符使机器指向那个位置。当它作为左值使用时,这个表达式指定需要进行修改的位置。当它作为右值使用时,它就是提取当前存储于这个位置的值。
    指针中的左右值举例:
    char cp = 'a';
    char *cp = &ch;
    1、&ch 可以作为右值,但是不能作为左值。为什么这个不是一个合法的左值?因为&操作符的结果是一个右值,它不能当作左值使用。当表达式&ch进 行求值时,它的结构应该存储于计算机的什么地方呢?它肯定会位于某个地方,但你无法知道它位于何处。这个表达式并未标识任何机器内存的特定位置,所以它不 是一个合法的左值。

   2、cp 可以作为左值,也可以作为右值

   3、&cp 可以作为右值,但是不可以作为左值。

   4、*cp 可以作为右值,也可以作为左值。

   5、*cp + 1 可以作为右值,但是不可以作为左值。*的优先级高于+,所以首先执行间接访问操作,我们可以得到它的值。我们取得这个值的一份拷贝并把它与1相加,表达式 的最终结果为字符'b'。这个表达式的最终结果的存储位置并未清晰定义,所以它不是一个合法的左值。优先级表格证实+的结构不能作为左值。

   6、*(cp + 1) 可以作为右值,也可以作为左值。

   7、++cp 可以作为右值,但不可以作为左值。 在这个表达式中,我们增加了指针变量cp的值。表达式的结果是增值后的指针的一份拷贝,因为前缀++先增加它的操作数的值再返回这个结果。这份拷贝的存储位置并未清晰定义,所以它不是一个合法的左值。
 
  8、cp++ 可以作为右值,但是不可以作为左值。 后缀++操作符同样增加cp的值,但它先返回cp值的一份考本然后再增加cp的值。这样,这个表达式的值就是cp原来的值的一份拷贝。
  
  9、* ++cp 可以作为右值,也可以作为左值。

  10、*cp++ 可以作为右值,也可以作为左值。

  11、++*cp 可以作为右值,但是不可以作为左值。 由于这两个操作符的结合性都是从右向左,所以首先执行的是间接访问操作,然后,cp所指向的位置的值增加1,表达式的结构是这个增值后的值的一份拷贝。

  12、(*cp) ++ 可以作为右值,但是不可以作为左值。

  13、++*++cp 可以作为右值,但是不可以作为左值。


左值可以被赋值(被更新),而右值没有这个限制
比如
const int a=3;
int b=4;
a只能作为右值,b作为左右值均可
简单地说:
左值是放在赋值号左边的值(可以改变),右值是放在赋值号右边的值(没有限制)
通俗的讲,左值就是能够出现在赋值符号左面的东西,而右值就是那些可以出现在赋值符号右面的东西了。  

举个很简单的例子:  

a=b+100;  

那么这里a就是左值,b+25就是一个右值。左值和右值之间是不一定都能互换的,上面的这个例子就是不能互换的,如果写成  

b+100=a;  

大家都能看出来这样写会不编译通过的,因为编译器无法判断b+100的内存地址,所以不能操作。  

看了这个例子,可以做一个总结,左值必须应该是一个变量或者是表达式等,但是它的物理位置是可以确定的,而右值不一定,这也是它们两者之间的区别。  

关于左值是表达式的例子有数组,还有指针这些都可以。  

int array[10];  

int a=5;  

array[a+3]=10; //这里左值就是一个数组表达式了


一般来说,左值,右值的概念用于赋值表达式。从字面上理解,左值在等号左边,右值在等号右边。
左值我们用的是它的地址,有地址且可地址可用的实体都可以用作左值,右值我们用的是它的值,只要有一个值,就可以作为右值,比如:文字常量,变量。特别的,符号常量(const)对象中的值不能修改,所以只能用作右值。
举例来说:int a; char b;   a,b都可以作左值。特别的,(a = 5)这个表达式的的值是&a(a的引用),所以它可以作为左值。进一步说,如果一个函数返回一个可用的引用,那么这个函数调用表达式也可以用作左值,其被赋值的对象是函数返回值所引用的对象。当然,这样的东西可读性太差,不推荐使用。
至于右值……基本上所有有值的实体都可以作为右值
++i是直接给i变量加1,然后返回i本身,因为i是变量,所以可以被赋值,因此是左值表达式
i++现产生一个临时变量,记录i的值,而后给i加1,接着返回临时变量,然后临时变量不存在了,所以,不能再被赋值,因此是右值表达式

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写的这个心得:左值和右值的概念写代码写多了就会自然产生相应感觉的——就是飞雪提到的“可访问的存储”。一些感性的例子:
关于左值和右值的Q <wbr>& <wbr>Aa+b这个值是一个相当“悬”的东西,我们只知道它是a+b 的结果,但不能掌握它的具体情况,也不太关心它到底怎样出现甚至是否出现。
关于左值和右值的Q <wbr>& <wbr>A 而*p 就不一样,因为 p是个指针,它必然有一个值,不管是有效的还是无效的,那么那个值代表的内存区域就肯定有某个东西,这是实实在在的。
关于左值和右值的Q <wbr>& <wbr>A
至于&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 thatan expression has anaddressable location that data or information can be put into.)

Q貌似有点明白了,为什么要强调可访问的存储?难道还有些拥有存储却不可访问的表达式结果么?
A
:对的,来看如下例子(来自飞雪):

int a; 
int foo(){return 1;}

foo的返回的是一个int,这个int的值是1,这个值是拥有存储的,但是你不应该知道;a + 1会通过+产生一个值,这个值是拥有存储的,但是这个存储也不是你应该知道的。所以,它们都不是左值。只有当你拥有表达式的存储的访问权时,你才可以把这个表达式放在=的左边,通过赋值来改变这个对象的状态。

其实左值无非是可以引用到对象的表达式,因此左值的概念和C里的对象是密不可分的,只要理解好了对象,就比较好把握左值了。C里的对象(注意和“面向对象”里的“对象”完全两回事)是指一块命名的内存区域
(An Object is a named region of storage—From “The C Programming Language”)。所以,左值的关键拥有你可访问的存储

当然左值概念经过发展后,已经不再介意一个左值引用的对象是否真的存在了,重要的是,这个左值具有对象或非空不完整类型。例如(来自supermegaboy):

double i; 
int *p = ( int* )&i;     int *p; 
*p = .........;

Q说了这么多左值,那右值的定义是什么呢?
A:[C99]右值(rvalue)是指表达式的值。(46页脚注)What is sometimes called “rvalue” is 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://blog.sina.com.cn/s/blog_6a35a0f60100qpnw.html



转载于:https://my.oschina.net/u/1178070/blog/424148

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值