Overview
1.The value category of an expression 表达式的值的类别
2.lvalue and rvalue 左值和右值
3.如何判断表达式是左值还是右值 ? 看是否能取到地址
4.literal (字面量) 是左值还是右值 ?
The value category of an expression 表达式的值的类别
回顾一下 expression (表达式是什么) ?
expression: a combination of literals, variables, operators, and function calls that can be executed to produce a singular value”.
intuitively,
1.我们将表达式定义为”可以执行以产生单值的文字, 变量, 运算符和函数调用的组合”.
2.表达式有两个属性, type (表达式的类型) 和value category (表达式的值的类别), 表达式的类型容易理解, 通过 type inference (类型推断) 可以得到; 表达式的类型必须在编译时确定, 但是表达式的值可以在编译时或者运行时确定
#include <iostream>
int main() {
auto v1 = 12 / 4; // int / int => 编译器推断表达式的类型是int, 所以v1的类型是int
auto v2 = 12.0 / 4; // double / int => 编译器表达式的类型是double, 所以v2的类型是double
return 0;
}
但是只有类型推断还不够, 还有个表达式在=左边或者在=右边的合法性问题
int main() {
int x{};
x = 5; // 有效assigment, 我们可以将5赋值给x
5 = x; // 无效assigment, 将x赋值给5的意义是什么?
return 0;
}
那么问题来了, 编译器如何知道哪些表达式可以合法地出现在=的左右两侧呢?
The answer lies in the second property of expressions: the value category. The value category of an expression (or subexpression) indicates whether an expression resolves to a value, a function, or an object of some kind.
所以表达式有第二个属性: the value category, 表达式的value category指示出表达式是否解析为值/函数/某类对象
lvalue and rvalue 左值和右值
在 cpp11 之前, 表达式只有 lvalue (左值), rvalue (右值) 这两种 value category; 在 cpp11 中, 新增添加了三个 value categotires: glvalue, prvalue, xvalue, 用来支持一个新特性 move sematics (移动语义)
Lvalue and rvalue expressions
An lvalue is an expression that evaluates to an identifiable object or function (or bit-field).
The term “identity” is used by the C++ standard, but is not well-defined. An entity (such as an object or function) that has an identity can be differentiated from other similar entities (typically by comparing the addresses of the entity).
An rvalue is an expression that is not an l-value. Commonly seen rvalues include literals (except C-style string literals, which are lvalues) and the return value of functions and operators. Rvalues aren’t identifiable (meaning they have to be used immediately), and only exist within the scope of the expression in which they are used.
Lvalue expressions are those that evaluate to variables or other identifiable objects that persist beyond the end of the expression.
Rvalue expressions are those that evaluate to literals or values returned by functions/operators that are discarded at the end of the expression.
intuitively,
1.根据左值和右值的字面意思理解, 合法地表达式等号左边就是左值, 右边是右值; 然而只根据左值和右值的字面意思, 有些情况无法准确区分左值和右值
2.左值: 指向一个特定内存的具有名称的值 (具名对象), 它有一个相对稳定的内存地址, 并且有一段较长的生命周期
3.右值: 不指向稳定内存的匿名值 (不具名对象), 它的生命周期很短, 通常是暂时性的
4.判断左值和还是右值的方法: 利用取地址符 & 来判断左值还是右值, 能取到内存地址的值为左值, 否则都是右值
5.反过来想取地址符 & 只能取左值, 而不能取右值, 如果我们用取地址符取右值, 那么就会报错
6.左值在表达式结束之后持续存在; 右值在表达式结束之后直接被遗弃
举一个不容易区分左值还是右值的例子
int x = 1; // x是左值, 1是右值
int y = 3; // y是左值, 3是右值
int z = x + y; // z是左值, x+y结果是右值
int a = 1; // a是左值
int b = a; // b是左值, a还是左值
如何判断表达式是左值还是右值 ? 看是否能取到地址
获取左值和右值的方法: 采用取地址符, 来拿到地址; 如果能拿到地址, 那么就是左值; 如果拿不到地址, 那就是右值
#include <iostream>
using namespace std;
int main() {
int a = 1; // a是左值
int b = a; // b是左值, a还是左值
cout << &a << endl; // 输出结果为 0x16fdff098
cout << &b << endl; // 输出结果为 0x16fdff094
cout << &1 << endl; // 编译不通过, 提示错: 表达式必须为左值或函数提示符 C/C++158
int c, d;
int *p;
p = &(c + 2); // 编译不通过, 提示错: 表达式必须为左值或函数提示符 C/C++158
int c, d;
int *p;
d = c + 2;
p = &d; // 改成左值就可以取地址了
return 0;
}
上面的代码左右值判断比较简单, 但是并非所有的情况都简单, 有些时候左值和右值的判断都是反直觉的: 比如想一下下面的 x++ 和 ++x, 先猜测一下哪个感觉更像左值, 哪个感觉更像右值? 感觉似乎 ++x 像左值, 因为返回的是加完之后的值; x++感觉像右值, 因为返回的时临时的 x;
int x = 1;
int get_val() {
return x;
}
void set_val(int val) {
x = val;
}
int main() {
x++;
++x;
int y = get_val();
set_val(6);
}
分析一下上面这段代码
1.x++ 和 ++x虽然都是自增操作, 但是左右值不同
2.为了检验真相, 采用取地址符看编译结果
int x = 3;
int *p = &x++; // 编译失败 x++为右值
int *q = &++x; // 编译成功 ++x为左值
return 0;
3.x++ 是右值, 执行 x++ 时, 编译器会生成一份 x 值的临时复制, 也就是一个临时变量, 最后返回这个临时变量
4.++x 是左值, 执行 ++x 时, 直接对 x 执行 +1 操作后马上返回其自身, 所以 ++x 是左值
5.上面的两类情况, 记忆它们区别的关键是”找临时变量”, 哪里产生临时变量, 哪里就是右值, 不难想到 x++ 这种操作返回的是临时变量 x 的 copy, 因此 x++ 是右值
int x = 1;
int get_val() {
return x;
}
int main() {
cout << &x; // x是一个左值, 编译没问题
int *p = &get_val(); // x变成一个右值, 这行编译失败, 原因是get_val() 返回的是x的临时复制, 此时x是个右值; 道理类似于x++;
return 0;
}
再看另一个例子
int x = 1;
int get_val() {
return x;
}
void set_val(int val) {
int *p = &val; // 这里为什么编译能通过? 实参6是一个右值, 但是进入函数之后形参val变成了一个左值
// 所以这里编译没有任何问题
x = val;
}
int main() {
int y = get_val();
set_val(6); // 6是一个右值
return 0;
}
literal (字面量) 是左值还是右值?
值得注意的是, 通常字面量 (literal) 都是右值, 但”字符串”这种字面量是个左值
什么是字面量 (literal) ? 字面量用于表达源代码中一个固定值的表示法, 整数, 浮点数和字符串都是字面量
int *x = &6; // 编译不通过, 6 是个右值
double *y = &3.14; // 编译不通过, 3.14 是个右值
float *z = &1.99f; // 编译不通过, 1.99 是个右值
auto p = &"hello"; // 编译通过, "hello" 是个左值
Reference
1.现代 C++ 教程:高速上手 C++11/14/17/20
2.https://en.cppreference.com/w/cpp/language/value_category
转载请注明来源, from goldandrabbit.github.io