C/C++左值性精髓(二)哪些表达式是左值,哪些是右值? zz from CSDN supermegaboy

C/C++左值性精髓

(二)哪些表达式是左值,哪些是右值?

 

1. 左值表达式

 

        如上一节所述,左值具有对象或不完整类型,在C++中还具有函数或引用类型,但是,并非具有上述类型的表达式就是左值,关键是左值必须指示一个对象,无论该对象有效或无效、完整或不完整,在C++中则包含非静态成员函数之外的函数。例如:

 

int i;

int *p = &i;

 

i和p都指示一个对象,且具有对象类型,因而都是左值,而&i虽然具有对象类型,但由于没有指示一个对象,因此不是左值。

不完整类型也可以成为左值,因为不完整类型本质其实与对象类型一样,只不过尚未有完整的对象信息。前向声明是我们最常用的不完整类型,对前向声明的引用是允许的,例如:

 

    extern struct S s;

     S & r = s;

 

虽然s的完整定义尚未可见,但仍可以作为左值初始化左值引用。


2. 右值表达式

 

        C对于右值的定义是表达式的值,C中所有完整表达式的结果都是右值。所谓完整表达式(full expression),指的是这样的表达式,它不是其它表达式或声明符的一部分。包括条件表达式和逗号表达式等等都不产生左值,而子表达式计算产生的中间结果或临时对象,很多人以为都是右值,但实际上,它们不一定是右值。例如:

 

int a[ 5 ] = { 1, 2, 3, 4, 5 };

int *p = a;

*p = 2;                         /* A */

a[ 1 ] = 3;                     /* B */

*p;                             /* C */

a[ 1 ];                         /* D */

 

A和B中的*p和a[ 1 ]都是内置赋值运算符的子表达式,虽然都是中间结果,但显然都属于左值表达式;只有当它们作为完整表达式时,如C和D中的*p和a[ 1 ]所示,都会进行最后的左值转换,使得结果皆为右值。这个现象的本质,是由于C将所有完整表达式的结果一律进行最后的从左值到右值的转换,这个行为可以理解为一个完全求值的过程(完全求值不是标准术语)。

        但C++的完整表达式并不要求进行完全求值,是否保留左值性视需要而定,这个“需要”是什么?其实是C++某些运算符的强制规定,例如内置赋值运算符、前置增量和前置减量运算符等等,这类运算符的结果被强制规定为左值,对于条件运算符,只有第二和第三表达式皆为左值且类型相同时才保证结果为左值。原因无它,仅仅规定而已。

右值是不是对象?由于左值要求一个对象,因此比较容易产生的误解是,右值不是对象。但,如果函数返回一个结构呢?

 

struct S { int a; };

struct S fun( void ){ struct S s; ...; return s }

 

由于POD结构属于聚集,不是标量,因而POD结构不是一个值,标量才被视为一个值。上述代码中,fun返回了一个POD结构S的临时对象,而且是一个右值,即无论C还是C++都存在右值对象,。因此,右值只是不要求是对象,并非不能是对象。


3. 函数调用表达式和强制转换

 

        对于函数调用表达式和强制转换表达式的结果,在C中都属于右值;C++由于增加了引用类型,结果为引用的函数调用表达式和强制转换表达式都属于左值,示例如下:

 

int& fun1( int & r ){ return r; }

 int fun2( void ){ return 10; }

 int i = 20;

 fun2( ) = 30;                               //A

 cout << ( fun1( i ) = 30 );          //B

 (  int & )10 = 20;                        //C

 ( const int & )10 = 20;              //D

 ( int & )i = 40;                             //E

 ( double & )i = 50;                     //F

 cout << ( double & )i;               //G

 

A: fun2的返回值是一个右值,不能作为内置赋值表达式的左操作数,因此A是错误的;

B: fun1返回一个引用,属于左值,因此可以作为内置赋值运算符的左操作数;

C: C试图将一个右值强制转换为引用,但是,只有const引用才能引用一个右值,因此错  误;

D: D比C进步了一点,强制转换为const引用,但仍然是错误的,因为const引用属于不可修改的左值,不能通过const引用修改其引用的对象;

E: E将一个int变量强制转换为int引用并被修改。这个表达式容易出现误解,以为i被临时转换为一个引用,其实不然,( int& )i只是产生一个引用到i的临时引用,i是被引用的对象而非引用本身,被修改的是i的值;

F、G:F和G与E一样,都产生一个引用到i的临时引用,但存在两个问题,一是由于i的类型与double&所引用的类型不同,i的底层布局从double&的角度看来是double,F中的50先被转换为double,再存进i,存进i的内容并非int格式的50,而是浮点数格式的50,如果此时打印i的值,结果将为一个“混乱”的整数;二是由于double和int的二进制宽度不一定相同,如果double宽度大于int,则F和G都将导致未定义行为。

        对于C++新增的static_cast、reinterpret_cast、const_cast三种强制转换方式,由于C++将C风格强制转换和函数风格强制转换都转换为上述三种方式,因此结果与上述例子相同。而对于dynamic_cast,若目标类型为引用,结果为左值,否则为右值。


4. 后缀表达式

 

        在表达式的左值性中,后缀表达式是比较复杂的一种情况。后缀表达式有很多种,这里讨论的是E1.E2和E1->E2形式的后缀表达式。

 

4.1  E1.E2形式的后缀表达式

 

        若E2为静态数据成员或引用数据成员,无论E1的左值性如何,E1.E2的结果都是左值;若E2为非静态非引用数据成员,C和C++标准都规定如果E1为左值,则E1.E2也是左值;当E1为右值时,从原理上说,右值对象的一部分也应该是一个右值,因此在C中,无论E2的左值性如何,E1.E2皆为右值;那么C++中的结果又如何呢?按道理应该顺理成章也为右值吧,但令人惊讶的是,C++98和C++2003都没有对此作出规定!因此在C++98和C++2003中,无论编译器将这种情况作为左值或右值都没有违反标准。这种情况显然是一个漏洞,在C++新标准C++11的制定过程中,WG21的专家在其Defect Reports中承认了这一点,并在C++11中将结果修正为一个prvalue(pure rvalue,纯右值)。

 

struct A

{

    A( int& i ) : r( i ){}

    static int k;

    int &r;

    int j;

};

 int A::k = 10;

 A foo( int& r ){ A a( r ); return A; }

 ......

 int i = 20;

A a( i );

a.k = 30;               //静态成员,左值

a.r = 40;               //引用成员,左值

a.j = 50;               //a是左值,所以a.j是左值

foo( i ).k = 60;        //静态成员,左值,虽然foo( i )返回一个右值

foo( i ).r = 70;        //引用成员,左值,虽然foo( i )返回一个右值

foo( i ).j = 80;        //foo( i ).j左值右值都合法,因为标准没有规定

 

E2为函数成员。若E2为静态成员函数,无论E1的左值性如何,结果都为左值。若E2为非静态成员函数,如3.1节所述,由于非静态成员函数不是左值,因此无论E1的左值性如何,E1.E2的结果都是右值。

 

4.2  E1->E2形式的后缀表达式

 

        由于C中的右值地址无法获得,因此C中的E1总是指向左值的指针,所以C中的E1->E2后缀表达式的结果总是左值;但C++有很大不同,C++中的E1->E2后缀表达式是转换为等价形式(*E1).(E2)进行计算的,因此E1->E2的左值性与E1.E2相同。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值