六一的部落格


关关难过关关过,前路漫漫亦灿灿。



表达式的属性, 也是左值表达式和右值表达式的简称

表达式: 由运算符和操作数组成

对表达式求值将得到一个结果: 对象或值

表达式有3个讨论维度:

  1. 表达式类型 type
  2. 表达式值类别 value category
  3. 表达式求值结果类别

两个常见的表达式类别:

-
函数调用表达式 表达式类型为函数的返回类型
变量表达式 表达式类型为变量类型, 表达式值类别为左值

表达式类型

可以是基础类型,可以是复合类型,也可以是类类型

表达式类型不一定是具体类型, 可以是引用


表达式值类别

也称作表达式的属性

决定了能施加给表达式求值结果的操作: 是否可寻址, 是否可写

-
左值 lvalue 可寻址; 可读写
右值 rvalue : 速亡值和纯右值 不可寻址; 只读

对象

object

也称作实体

有具体类型(读写规则), 不是抽象类或不完全类型; 拥有内存中的一块区域

可以有名字,也可以没名字

有自己的生存期


读写对象的途径

  1. 对具名对象直接进行读写
  2. 通过绑定对象的引用
  3. 通过指向对象的指针

value

拥有具体类型,不拥有内存空间


表达式求值结果

表达式求值结果或为值,或为对象; 一定是具体类型

表达式类型 表达式求值结果
指针 值; 指针值, 与指针所指对象无关
引用 对象; 引用绑定的对象

左值表达式

lvalue

  1. 可对左值表达式寻址

  2. 可对左值表达式的求值结果进行读写

    左值表达式可以出现在赋值运算符的左侧

    对象的写权限仍受到其他因素的限制: 引用的底层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

右值包括:

-
速亡值表达式
纯右值表达式
  1. 不可对右值表达式寻址
  2. 右值表达式的求值结果只读

    右值表达式只能出现在赋值运算符右侧

可用右值引用绑定一个右值表达式; 可用具有底层const的左值引用绑定一个右值表达式


速亡值表达式

xvalue

expiring value

满足以下条件:

-
表达式类型 右值引用
表达式值类别 右值
  1. 是右值: 不可寻址, 只读

  2. 表达式求值结果为对象(拥有内存空间): 使用右值引用绑定后, 可读写,可寻址


举例

-
返回类型为右值引用的函数调用表达式, 如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

两种分类方式:

  1. 讨论表达式求值结果类别时

    表达式求值结果
    泛左值 glvalue : 左值和速亡值 对象
    纯右值
  2. 讨论表达式是否可寻址, 是否支持写操作时

    -
    左值 可寻址; 可读写
    右值: 速亡值和纯右值 不可寻址; 只读

表达式类型, 表达式值类别, 和表达式求值结果类别三者的关系

  1. 如果表达式类型非引用, 表达式值类别一定是纯右值, 表达式求值结果一定是值
  2. 如果表达式类型为引用, 表达式值类别一定是泛左值(左值或速亡值), 表达式求值结果一定是对象

示例

 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 &&>

左值和右值


表达式的属性, 也是左值表达式和右值表达式的简称

表达式: 由运算符和操作数组成

对表达式求值将得到一个结果: 对象或值

表达式有3个讨论维度:

  1. 表达式类型 type
  2. 表达式值类别 value category
  3. 表达式求值结果类别

两个常见的表达式类别:

-
函数调用表达式 表达式类型为函数的返回类型
变量表达式 表达式类型为变量类型, 表达式值类别为左值

表达式类型

可以是基础类型,可以是复合类型,也可以是类类型

表达式类型不一定是具体类型, 可以是引用


表达式值类别

也称作表达式的属性

决定了能施加给表达式求值结果的操作: 是否可寻址, 是否可写

-
左值 lvalue 可寻址; 可读写
右值 rvalue : 速亡值和纯右值 不可寻址; 只读

对象

object

也称作实体

有具体类型(读写规则), 不是抽象类或不完全类型; 拥有内存中的一块区域

可以有名字,也可以没名字

有自己的生存期


读写对象的途径

  1. 对具名对象直接进行读写
  2. 通过绑定对象的引用
  3. 通过指向对象的指针

value

拥有具体类型,不拥有内存空间


表达式求值结果

表达式求值结果或为值,或为对象; 一定是具体类型

表达式类型 表达式求值结果
指针 值; 指针值, 与指针所指对象无关
引用 对象; 引用绑定的对象

左值表达式

lvalue

  1. 可对左值表达式寻址

  2. 可对左值表达式的求值结果进行读写

    左值表达式可以出现在赋值运算符的左侧

    对象的写权限仍受到其他因素的限制: 引用的底层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

右值包括:

-
速亡值表达式
纯右值表达式
  1. 不可对右值表达式寻址
  2. 右值表达式的求值结果只读

    右值表达式只能出现在赋值运算符右侧

可用右值引用绑定一个右值表达式; 可用具有底层const的左值引用绑定一个右值表达式


速亡值表达式

xvalue

expiring value

满足以下条件:

-
表达式类型 右值引用
表达式值类别 右值
  1. 是右值: 不可寻址, 只读

  2. 表达式求值结果为对象(拥有内存空间): 使用右值引用绑定后, 可读写,可寻址


举例

-
返回类型为右值引用的函数调用表达式, 如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

两种分类方式:

  1. 讨论表达式求值结果类别时

    表达式求值结果
    泛左值 glvalue : 左值和速亡值 对象
    纯右值
  2. 讨论表达式是否可寻址, 是否支持写操作时

    -
    左值 可寻址; 可读写
    右值: 速亡值和纯右值 不可寻址; 只读

表达式类型, 表达式值类别, 和表达式求值结果类别三者的关系

  1. 如果表达式类型非引用, 表达式值类别一定是纯右值, 表达式求值结果一定是值
  2. 如果表达式类型为引用, 表达式值类别一定是泛左值(左值或速亡值), 表达式求值结果一定是对象

示例

 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 &&>