你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

区分C++11中的表达式值类型

[复制链接]
gaosmile 发布时间:2020-9-4 15:02
C++98 中表达式值的类型只有左值和右值两种类型,可以取到地址的表达式就是左值,不是左值的值就是右值,而C++11中将表达式的值类型划分成了lvalue(左值)、rvalue(右值)、prvalue(纯右值)、xvalue(将亡值)、gvalue(泛左值) 5种。下文从基本概念并结合实际的例子区分C++11中的表达式值类型。
基本概念表达式

  • 表达式的定义
    定义: 由运算符(operator)和运算对象(operand)构成的计算式(类似于数学上的算术表达式)。
    举例: 字面值(literal)和变量(variable)是最简单的表达式,函数的返回值也被认为是表达式。

值类别

  • 表达式是可求值的,对表达式求值将得到一个结果(result)。这个结果有两个属性:类型和值类别(value categories)。
  • 在c++11以后,表达式按值类别分,必然属于以下三者之一:左值(left value,lvalue),将亡值(expiring value,xvalue),纯右值(pure rvalue,pralue)。其中,左值和将亡值合称泛左值(generalized lvalue,glvalue),纯右值和将亡值合称右值(right value,rvalue)。见下图,


微信图片_20200904150124.jpg
  • C++11中右值rvalue的概念包括两个部分:

1、将亡值(xvalue. expiring value),xvalue是C++11新增的概念,与右值引用相关的表达式,如: 将要被移动的对象、T&&函数返回值、std::move返回值和转换为 T&&的类型的转换函数的返回值等;
2、纯右值(pvalue, pure right value),如:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等属于pvalue;
注意:“左值”是表达式的结果的一种属性,但更为普遍地,通常用“左值”来指代左值表达式。所谓左值表达式,就是指求值结果的值类别为左值的表达式。在后文中,我们依然用左值指代左值表达式。对于纯右值和将亡值,亦然。
左值、纯右值和将亡值的描述
左值

  • 描述:能够用&取地址的表达式是左值表达式。

一个区分左值和右值的便捷方法:看能不能对表达式取地址,若能,则为左值,若不能则为右值。所有的具名变量都是左值,而右值是不具名的。
举例:

  • 函数名和变量名
  • 返回左值引用的函数调用
  • 前置自增/自减运算符连接的表达式++i/--i
  • 由赋值运算符或复合赋值运算符连接的表达式(a=b、a+=b、a%=b)
  • 解引用表达式*p
  • 字符串字面值"abc"


纯右值

描述:满足下列条件之一的:

  • 1)本身就是赤裸裸的、纯粹的字面值,如3、false;
  • 2)求值结果相当于字面值或是一个不具名的临时对象。


举例:

  • 除字符串字面值以外的字面值
  • 返回非引用类型的函数调用
  • 后置自增/自减运算符连接的表达式i++/i--
  • 算术表达式(a+b、a&b、a<<b)
  • 逻辑表达式(a&&b、a||b、~a)
  • 比较表达式(a==b、a>=b、a<b)
  • 取地址表达式(&a)


将亡值

描述: 在C++11之前的右值和C++11中的纯右值是等价的。C++11中的将亡值是随着右值引用的引入而新引入的。换言之,“将亡值”概念的产生,是由右值引用的产生而引起的,将亡值与右值引用息息相关。所谓的将亡值表达式,就是下列表达式:

  • 1)返回右值引用的函数的调用表达式
  • 2)转换为右值引用的转换函数的调用表达

在C++11中,用左值去初始化一个对象或为一个已有对象赋值时,会调用拷贝构造函数或拷贝赋值运算符来拷贝资源,而当用一个右值(包括纯右值和将亡值)来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。当该右值完成初始化或赋值的任务时,它的资源已经移动给了被初始化者或被赋值者,同时该右值也将会马上被销毁(析构)。也就是说,当一个右值准备完成初始化或赋值任务时,它已经“将亡”了。这种右值常用来完成移动构造或移动赋值的特殊任务,扮演着“将亡”的角色,所以C++11给这类右值起了一个新的名字——将亡值。
  • 举例:std::move()、tsatic_cast<X&&>(x)(X是自定义的类,x是类对象,这两个函数常用来将左值强制转换成右值,从而使拷贝变成移动,提高效率。

C++11中值的类型总结
  • gvalue: has identity
  • lvalue:  has identity and can not be moved from
  • rvalue: can be moved from
  • xvalue: has identity and can be moved from
  • prvalue: does not have identity and can be moved from

对比说明情况1:++i是左值,而i++是右值

  • ++i对i加1后再赋给i,最终的返回值就是i,所以,++i的结果是具名的,名字就是i;
  • 而对于i++而言,是先对i进行一次拷贝,将得到的副本作为返回结果,然后再对i加1,由于i++的结果是对i加1前i的一份拷贝,所以它是不具名的。假设自增前i的值是6,那么,++i得到的结果是7,这个7有个名字,就是i;而i++得到的结果是6,这个6是i加1前的一个副本,它没有名字,i不是它的名字,i的值此时也是7。
  • 可见,++i和i++都达到了使i加1的目的,但两个表达式的结果不同。


情况2:解引用表达式*p是左值,取地址表达式&a是纯右值

  • &(p)一定是正确的,因为p得到的是p指向的实体,&(p)得到的就是这一实体的地址,正是p的值。由于&(p)的正确,所以*p是左值。
  • 而对&a而言,得到的是a的地址,相当于unsigned int型的字面值,所以是纯右值。


情况3:a+b、a&&b、a==b都是纯右值

  • a+b得到的是不具名的临时对象,而a&&b和a==b的结果非true即false,相当于字面值。


情况4:字符串字面值是左值,而非字符串的字面量是纯右值

  • 不是所有的字面值都是纯右值,字符串字面值是唯一例外。
  • 早期C++将字符串字面值实现为char型数组,实实在在地为每个字符都分配了空间并且允许程序员对其进行操作,所以类似


cout<<&("abc")<<endl;
char *p_char="abc";//注意不是char *p_char=&("abc");

情况5:具名的右值引用是左值,不具名的右值引用是右值。

  • 如:


void foo(int&& t) {
    // t 被一个右值表达式初始化
    // 但是,t 本身是一个左值
}
继续深入学习
C++11中的右值引用和移动语义http://www.jianshu.com/p/61ea80fcf943

收藏 评论0 发布时间:2020-9-4 15:02

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版