左值和右值
2023年12月14日 2023年12月31日
表达式的属性, 也是左值表达式和右值表达式的简称
表达式: 由运算符和操作数组成
对表达式求值将得到一个结果: 对象或值
表达式有3个讨论维度:
- 表达式类型
type
- 表达式值类别
value category
- 表达式求值结果类别
两个常见的表达式类别:
- | |
---|---|
函数调用表达式 | 表达式类型为函数的返回类型 |
变量表达式 | 表达式类型为变量类型, 表达式值类别为左值 |
表达式类型
可以是基础类型,可以是复合类型,也可以是类类型
表达式类型不一定是具体类型, 可以是引用
表达式值类别
也称作表达式的属性
决定了能施加给表达式求值结果的操作: 是否可寻址, 是否可写
- | |
---|---|
左值 lvalue |
可寻址; 可读写 |
右值 rvalue : 速亡值和纯右值 |
不可寻址; 只读 |
对象
object
也称作实体
有具体类型(读写规则), 不是抽象类或不完全类型; 拥有内存中的一块区域
可以有名字,也可以没名字
有自己的生存期
读写对象的途径
- 对具名对象直接进行读写
- 通过绑定对象的引用
- 通过指向对象的指针
值
value
拥有具体类型,不拥有内存空间
表达式求值结果
表达式求值结果或为值,或为对象; 一定是具体类型
表达式类型 | 表达式求值结果 |
---|---|
指针 | 值; 指针值, 与指针所指对象无关 |
引用 | 对象; 引用绑定的对象 |
左值表达式
lvalue
-
可对左值表达式寻址
-
可对左值表达式的求值结果进行读写
左值表达式可以出现在赋值运算符的左侧对象的写权限仍受到其他因素的限制: 引用的底层const, 对象的顶层const
可用左值引用绑定一个左值表达式
左值表达式求值结果一定是对象
举例
- |
---|
变量表达式 |
返回左值引用的函数调用表达式 |
示例
1#include <iostream> 2using std::cout; 3using std::endl; 4int &fcn(int &a) { return ++a; } 5int main() { 6 int a = 5, b = a; 7 int c = a + b; 8 c = 7; // 正确: 值类别为左值, 可写 9 int *p = &a; 10 p = &c; // 正确: 值类别为左值, 可写 11 12 int &&m = 10; 13 m = 20; // 正确: 值类别为左值, 可写 14 15 ++fcn(b); // 正确: 函数返回类型为int &, 值类别为左值, 可作为前置递增运算符的操作数 16 fcn(a) = 100; // 正确: 函数返回类型为int &, 值类别为左值, 可写 17 int *p2 = &fcn(c); // 正确: 函数返回类型为int &, 值类别为左值, 可寻址 18 return 0; 19}
右值: 不可寻址 + 只读
rvalue
右值包括:
- |
---|
速亡值表达式 |
纯右值表达式 |
- 不可对右值表达式寻址
- 右值表达式的求值结果只读
右值表达式只能出现在赋值运算符右侧
可用右值引用绑定一个右值表达式; 可用具有底层const的左值引用绑定一个右值表达式
速亡值表达式
xvalue
expiring value
满足以下条件:
- | |
---|---|
表达式类型 | 右值引用 |
表达式值类别 | 右值 |
-
是右值: 不可寻址, 只读
-
表达式求值结果为对象(拥有内存空间): 使用右值引用绑定后, 可读写,可寻址
举例
- |
---|
返回类型为右值引用的函数调用表达式, 如std::move |
纯右值表达式
prvalue
表达式求值结果为值
举例
- |
---|
返回类型非引用的函数调用表达式 |
字面值表达式 |
隐式转换 |
构造函数(无返回类型) |
纯右值表达式物质化得到速亡值表达式
创建对象, 使用拥有的值初始化对象
示例
1#include <iostream> 2using std::cout; 3using std::endl; 4int fcn(int a) { return a + 1; } 5int main() 6{ 7 int a = 3; 8 int b = fcn(a); 9 fcn(a) = 5; // 错误: 表达式值类别为纯右值, 只读 10 a + b = 6; // 错误: 表达式值类别为纯右值, 只读 11 b++ = 10; // 错误: 表达式值类别为纯右值, 只读 12 int *p = &fcn(10); // 错误: 函数表达式的类型为int, 值类别为纯右值, 不可寻址 13 14 fcn(b); // 表达式类型为int,值类别是纯右值,表达式求值得到值 15 b++; // 表达式类型为int,值类别是纯右值,表达式求值得到值 16 return 0; 17}
泛左值: 求值结果为对象
glvalue
泛左值包括:
- |
---|
左值 |
速亡值 |
泛左值表达式的求值结果为对象
值类别细究
一共有三种:
- |
---|
左值 lvalue |
速亡值 xvalue |
纯右值 prvalue |
两种分类方式:
-
讨论表达式求值结果类别时
表达式求值结果 泛左值 glvalue
: 左值和速亡值对象 纯右值 值 -
讨论表达式是否可寻址, 是否支持写操作时
- 左值 可寻址; 可读写 右值: 速亡值和纯右值 不可寻址; 只读
表达式类型, 表达式值类别, 和表达式求值结果类别三者的关系
- 如果表达式类型非引用, 表达式值类别一定是纯右值, 表达式求值结果一定是值
- 如果表达式类型为引用, 表达式值类别一定是泛左值(左值或速亡值), 表达式求值结果一定是对象
示例
1#include <iostream> 2#include <utility> 3 4using std::cout; 5using std::endl; 6using std::move; 7 8int fcn(int a) { return a + 1; } 9int &fcn2(int &a) { return ++a; } 10int main() 11{ 12 int a = 3, b = 4; 13 3 + 4; // 表达式类型非引用, 纯右值,表达式结果是值 14 a + b; 15 16 fcn(3); // 函数返回类型非引用: 纯右值, 表达式结果是值 17 18 int &r = a; 19 r; // 变量表达式类型为int &,左值,表达式结果为对象 20 21 int c = a + b; 22 fcn2(c) = 10; 23 fcn2(c); // 函数返回类型为左值引用: 表达式类型是int &,左值,表达式结果为对象 24 25 int &&rr = 4 + 8; 26 rr; // 变量表达式类型为int &&, 左值,表达式结果为对象 27 28 int &&rr2 = static_cast<int &&>(c); // 表达式类型是int &&,速亡值,表达式结果为对象 29 int &&rr3 = std::move(4); // 表达式类型是int &&,速亡值,表达式结果为对象 30 int &&rr4 = fnc(6); // 表达式类型是int, 纯右值, 表达式结果为值; 纯右值物质化得到速亡值, 使用右值引用绑定 31 32 return 0; 33}
函数调用表达式
表达式类型为函数的返回类型
函数返回类型/表达式类型 | 表达式值类别 | 表达式求值结果 |
---|---|---|
左值引用 | 左值 | 对象 |
右值引用 | 速亡值 | 对象 |
其他 | 纯右值 | 值 |
变量表达式
表达式类型为变量类型, 一定是左值表达式, 表达式求值结果为对象
示例
1int c = 5; 2c; 3 4int &d = c; // 表达式类型为int &, 值类别为左值 5 6int &&r = 6; // 表达式类型为int &&, 值类别为左值 7r = 10; // 正确: 可对左值进行写操作
要求操作数是左值的运算符
- |
---|
前置/后置递增运算符 ++ |
前置/后置递减运算符 -- |
取地址运算符 & |
赋值运算符(左操作数) & |
返回左值的运算符
- |
---|
前置递增运算符 ++ |
前置递减运算符 -- |
解引用运算符 * |
赋值运算符 = |
下标运算符 [] |
示例
1#include <iostream> 2using std::cout; 3using std::endl; 4int main() { 5 int a = 5, b = a; 6 int c = a + b; 7 int *p = &a; 8 *p = 7; // 值类别为左值: 可写 9 ++a = 10; // 值类别为左值: 可写 10 11 return 0; 12}
返回纯右值的运算符
- |
---|
后置递增运算符 ++ |
后置递减运算符 -- |
取地址运算符 & : 返回一个指针值 |
算术运算符 |
关系运算符 |
位运算符 |
返回速亡值的运算符
- |
---|
static_cast<T &&> |