每个C++表达式(带有操作数的操作符、字面量、变量名等)可按照两种独立的特性加以辨别:类型和值类别 (value category)。每个表达式都具有某种非引用类型,且每个表达式只属于三种基本值类别中的一种:纯右值 (prvalue)、亡值 (xvalue)、左值 (lvalue)。
值类别关系图 - 图源参考文献2一、左值
左值 (lvalue):如此称呼的历史原因是,左值可以出现于赋值表达式的左边
描述:能够用&取地址的表达式是左值表达式,从值类别关系图上可以看出来是非亡值的泛左值。
举例:
- 函数名和变量名(实际上是函数指针和具名变量,具名变量如
std::cin
、std::endl
等) - 返回类型为左值引用的函数调用或重载运算符表达式(
std::getline
(
std::cin
, str
)、std::cout
<< 1
) - 内建的前置自增与前置自减表达式(
++i/--i
) - 由赋值运算符或复合赋值运算符连接的表达式(
a=b、a+=b、a%=b
) - 解引用表达式*p
- 字符串字面值(字符串常量)(如:
"abc" == "bcd"
)
二、纯右值
纯右值 (prvalue):“纯 (pure)”的右值
描述:求值符合下列之一的表达式:
- 计算某个运算符的操作数的值(这种纯右值没有结果对象)
- 初始化某个对象或位域(称这种纯右值有一个结果对象)。所有类和数组的纯右值都有结果对象,即使它被舍弃也是如此。在某些语境中,将发生临时量实质化,以创建作为其结果对象的临时量
举例:
- (除了字符串字面量之外的)字面量(如 42、true 或 nullptr)
- 返回类型是非引用的函数调用或重载运算符表达式,例如 str.substr(1, 2)、str1 + str2 或 it++;
- 内建的后置自增与后置自减表达式(a--/a++)
- 内建的赋值及复合赋值表达式(a = b,a += b,a %= b)
- 内建的取地址表达式(&a)
三、亡值
亡值 (xvalue):(“将亡 (expiring)”的值)是代表其资源能够被重新使用的对象或位域的泛左值
举例:
- 返回类型为对象的右值引用的函数调用或重载运算符表达式(std::move(x))
- 内建的下标表达式,其操作数之一是数组右值(a[n])
-
对象成员表达式,其中
a
是右值且m
是非引用类型的非静态数据成员(a.m) -
对象的成员指针表达式,(a.*mp)其中
a
为右值且mp
为数据成员指针
四、泛左值
泛左值 (glvalue):(“泛化 (generalized)”的左值)是其求值确定一个对象、位域或函数的个体的表达式
描述:泛左值包括左值和亡值
五、右值
右值 (rvalue):(如此称呼的历史原因是,右值可以出现于赋值表达式的右边)是纯右值或者亡值
描述:
- 右值不能被取地址:&int()、&i++[3]、&42 及 &std::move(x) 是非法的。
- 右值不能用作内建赋值运算符及内建复合赋值运算符的左操作数。
- 右值可以用于初始化 const 左值引用,这种情况下该右值所标识的对象的生存期被延长到该引用的作用域结尾。
- 右值可以用于初始化右值引用,这种情况下该右值所标识的对象的生存期被延长到该引用的作用域结尾。
- 当被用作函数实参且该函数有两种重载可用,其中之一接受右值引用的形参而另一个接受 const 的左值引用的形参时,右值将被绑定到右值引用的重载之上(从而,当复制与移动构造函数均可用时,以右值实参将调用其移动构造函数,复制和移动赋值运算符与此类似)。
六、参考文献
【1】zh.cppreference.com/w/cpp/language/value_category
【2】话说C++中的左值、纯右值、将亡值
网友评论