C++重要概念:左值、纯右值、将亡值(重要)

前言

C++中有“左值”、“右值”的概念,C++11以后,又有了“左值”、“纯右值”、“将亡值”的概念。

表达式

要说清“三值”,首先要说清表达式。
定义: 由运算符(operator)和运算对象(operand)构成的计算式(类似于数学上的算术表达式)。

举例: 字面值(literal)和变量(variable)是最简单的表达式,函数的返回值也被认为是表达式。

每个 C++ 表达式(带有操作数的操作符、字面量、变量名等)可按照两种独立的特性加以辨别:类型和值类别 (value category)。
每个表达式都具有某种非引用类型,且每个表达式只属于三种基本值类别中
的一种:纯右值 (prvalue)、亡值 (xvalue)、左值 (lvalue)。

值类别

表达式是可求值的,对表达式求值将得到一个结果(result)。
这个结果有两个属性:类型和值类别(value categories)。下面我们将详细讨论表达式的值类别。
在c++11以后,表达式按值类别分,必然属于以下三者之一:

  • 左值(left value,lvalue)
  • 右值(pure rvalue,pralue)
  • 将亡值(expiring value,xvalue)

其中,左值和将亡值合称泛左值(generalized lvalue,glvalue),纯右值和将亡值合称右值(rightvalue,rvalue)。
如图:

在这里插入图片描述

严格来讲,“左值”是表达式的结果的一种属性,但更为普遍地,我们通常用“左值”来指代左值表达式(正如上边一段中做的那样)。所谓左值表达式,就是指求值结果的值类别为左值的表达式。通常我们无需区分“左值”指的是前者还是后者,因为它们表达的是同一个意思,不会引起歧义。在后文中,我们依然用左值指代左值表达式。对于纯右值和将亡值,亦然。

详细说明

左值

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

示例 : 函数名和变量名(实际上是函数指针和具名变量,具名变量如std::cin、std::endl等)、返回左值引用的函数调用、前置自增/自减运算符连接的表达式++i/–i、由赋值运算符或复合赋值运算符连接的表达式( a=b,a+=b,a%=b),解引用表达式*p,字符串字面值"abc"。

简单理解,就是具名的,可以对其取地址的。

纯右值

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

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

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

判断题

  1. ++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的目的,但两个表达式的结果不同。

利用类的运算符重载代码示例:
++i

	Complex& operator ++()
	{
		++i;
		return *this;
	}

i++

	Complex operator ++(int)
	{
		int tmp = i;
		i++;
		return Complex(tmp);
	}
  1. 解引用表达式 * p是左值,取地址表达式 &a 是纯右值。

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

  1. a+b、a&&b、a==b 都是纯右值

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

将亡值

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

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

详细来说:
在C++11中,我们用左值去初始化一个对象或为一个已有对象赋值时,会调用拷贝构造函数或拷贝赋值运算符来拷贝资源(所谓资源,就是指new出来的东西),而当我们用一个右值(包括纯右值和将亡值)来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。当该右值完成初始化或赋值的任务时,它的资源已经移动给了被初始化者或被赋值者,同时该右值也将会马上被销毁(析构)。也就是
说,当一个右值准备完成初始化或赋值任务时,它已经“将亡”了。而上面1)和2)两种表达式的结果都是不具名的右值引用,它们属于右值。
又因为
1)这种右值是与C++11新生事物——“右值引用”相关的“新右值”
2)这种右值常用来完成移动构造或移动赋值的特殊任务,扮演着“将亡”的角色,所以C++11给这类右值起了一个新的名字——将亡值。

程序示例1:

int fun()
{
	int a = 10;
	return a;
}

int main()
{
	int x = 0;
	x = fun();
}

对于 fun()函数,在return时候,并不是返回变量a本身, a在fun()函数结束时生存期也结束,a的值会被存储在一个临时量中,这个临时量就叫做“将亡值”。

在这里插入图片描述

程序示例2:类

class Object
{
private:
	int value;
public:
	Object(int x = 0) : value(x)
	{
		cout << "Obejct::create" << this << endl;
	}
	~Object() { cout << "Object::~Object" << this <<endl; }

	Object(const Object& src) : value(src.value)
	{
		cout << "Object::copy" << this << endl;
	}

	int GetValue()const
	{
		return this->value;
	}
};

Object fun(Object obj)
{
	int val = obj.GetValue();
	Object obja(val);
	return obja;
}

int main(void)
{
	Object objx(0);
	Object objy(0);

	objy = fun(objx);

	return 0;
}

运行结果:
在这里插入图片描述

在这里插入图片描述

在这个例子中,将亡值的生存周期是fun()函数调用语句结束,即,在return obja时拷贝构造出一个将亡值, 接着析构obja与obj,返回到函数调用点,将 这个将亡值赋值给 objy,语句结束时析构掉将亡值。

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_索伦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值