3.4 值类别
值类别(value category)在 C++ 是一个用来描述表达式性质的概念。注意,值类别并不是表达式的类型,放在类型一章的此处,是因为这个概念对于对接下来类型的理解是必要的。
在一节中,我们曾经提到过“能不能赋值”这样的问题。其中提到,如果表达式指代某个对象,那么这个表达式是能够被赋值的。
我们简要地复习一下:
// 这里声明了两个整数类型的对象 a 和 b
int a = 1, b = 1;
bool c = true;
// a 的名字可以组成一个基础表达式
// 这个表达式指代 a 这个对象,因此可以被赋值
a = 1;
// 使用括号包围的表达式与内部表达式的性质相同
// 由于 a 指代一个对象,因此 (a) 也指代这个对象,可以被赋值
(a) = 1;
// 赋值表达式作为整体,指代 = 符号左边的对象
// 因此可以被赋值
(b = a) = 1;
// 如果条件表达式的第二个和第三个操作数都指代对象
// 并且类型相同,那么条件表达式的结果也指代对象,可以被赋值
// 那么条件表达式的结果也指代对象,可以被赋值
// 这里 condition() 是假设的一个返回 bool 类型的函数
(condition() ? a : b) = 1;
(condition() ? a : c) = false;// 错误,a 和 c 的类型不同,此时条件表达式的结果不指代对象
在表达式一节中没有提到的是,如果逗号表达式的最后一个操作数指代对象,那么逗号表达式的结果也指代对象,可以被赋值。例如:
// 左边这个逗号表达式的结果指代对象 b
// 因此可以被赋值
(1, a, b) = 1;
值类别的分类
在 C++ 中,任何表达式属于以下三种值类别之一:
- 左值(lvalue)
- 纯右值(prvalue)
- 将亡值(xvalue)
顾名思义,左值就是可以出现在赋值运算符左边的值,而右值则是只能出现在赋值运算符右边的值。这是从 C 语言,甚至更古老的 CPL 中继承发展下来的概念。
将亡值则是 C++ 中引入的概念——在一些表达式中会产生了一个临时对象,这样的对象的生命周期往往在表达式结束后就会结束。为了区分这样的对象,C++ 引入了将亡值这个概念。
很容易理解到,这里提到的左值,就是前文所述的“指代对象的表达式”,而纯右值则是“不指代对象的表达式”。将亡值虽然“指代对象”,但是这个对象的生命周期往往很短,因此被单独列出。
目前,还没有介绍到会产生将亡值的表达式,这里先介绍表达式左值和纯右值的情形。
左值表达式
下列表达式是左值表达式:
- 如
a
、condtion
这样,指代对象、函数的标识符组成的基本表达式- 注意:虽然类型有
const
限定的对象不能被修改,但是const
限定的对象的名字仍然是左值
- 注意:虽然类型有
- 如
a = 1
、a += b
这样的赋值、复合赋值表达式 - 如
++a
、--a
这样的前置自增、前置自减表达式 - 第二和第三操作数是相同类型的左值时的条件表达式
- 最后一个操作数是左值的逗号表达式
纯右值表达式
下列表达式是右值表达式:
- 如
1
、true
这样的字面量组成的基本表达式 - 如
a++
、a--
这样的后置自增、后置自减表达式 - 如
a + b
、a * b
这样的算术表达式 - 如
a == b
、a < b
这样的关系表达式 - 如
a && b
、a || b
这样的逻辑表达式 - 第二第三操作数类型不同,或者有一个不是左值的条件表达式
- 形如
true ? a : 1
这样,总是选择左值那个操作数的条件表达式依然是纯右值
- 形如
- 最后一个操作数是纯右值的逗号表达式
泛左值和右值
除了上述的左值、纯右值、将亡值之外,C++ 还引入了泛左值和右值的概念。
有的时候,程序员需要通过一个左值的处理方式来处理将亡值,这时,将亡值和左值一起分类为泛左值。
有的时候,程序员需要把将亡值当做右值来处理,这时,将亡值和纯右值一起分类为右值。
这里提到的处理方式目前还没有介绍,但在后面的章节中,读者会看到这两种处理方式。