%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# 表达式
表达式由一个或多个运算对象(operand)组成,对表达式求值将得到一个结果:
- **==字面值==(literal)、==变量名==是最简单的表达式**,其结果就是**字面值和变量的值**。
- **==函数调用==** 是一种特殊的**运算符**,也属于表达式的一种,其值即为**函数的返回结果**。
<br>
# 表达式的属性
每个 C++ **表达式**(**带有操作数的操作符**、**字面值常量**、**变量名**等)都有两个**独立**的属性:
- **==类型==(type)** —— 每个表达式都具有某种 "**==非引用类型==**"(不会有引用类型)。
- **==值类别==(value category)** —— 每个表达式都属于三个主要值类别中的一种:`prvalue`、`xvalue`、`lvalue`;
> [!quote]
> - Ecah C++ expression (**an operator with its operands**, **a literal**, **a variable name**, etc.) is characterized by two independent properties: a ***type*** and a ***value category***.
>
> - Each expression belongs to exactly one of the three primary value categories: ***prvalue, xvalue*** and ***lvalue***.
<br><br>
# 值类型(value type)
C++中的每个**表达式都具有某种 "==非引用类型 (type)=="** ,不会具有引用类型。
**即使表达式涉及到引用,表达式的类型也被视为==引用所指向的类型==,而不是引用类型本身**:
若变量是一个**左值引用** (`T&`) 或**右值引用** ( `T&&`),当其**作为一个表达式**时,**==表达式的类型==都是 `T`**。
这一规则确保了**使用表达式**时总是在**操作被引用的对象的类型**。
```cpp
int n = 5;
int& ref = n;
// 虽然ref是一个引用类型,但表达式`ref`的类型是int,
```
<br><br>
# 值类别(value category)
参见 [Value categories - cppreference.com](https://en.cppreference.com/w/cpp/language/value_category)
## 值类别划分的演变
> [!quote]
>
> ![[_attachment/02-开发笔记/01-cpp/表达式.assets/IMG-表达式-F9B5953978AB2F8FAEE7544526FD514D.png|800]]
>
C++11 之前最初只具有 "**左值 (lvalue)**" 与 **"右值 (rvalue)**" 的划分——分别代表**具有持久存储性的对象**和**临时对象**。
其中左值的本意是 "locator value",即"**左值**"表示一个 "**标识了一个对象**" 的表达式。
![[_attachment/02-开发笔记/01-cpp/表达式.assets/IMG-表达式-0E5C22F4D0C44ED2465B04B4660EE899.png|400]]
随着 C++11 中移动语义的引入,**"值类别"被重新定义**,以描述 **==表达式的两个独立属性==**:
- ***==has identity==***(具有身份)——绑定在某个 **==具有持久存储位置的对象==** 上(该对象**在内存中有确定的地址**,可被`&` 取值)
- 能够确定**该表达式是否与另一个表达式==引用相同的实体==**(例如通过比较表达式标识的**对象或函数的地址**进行确定)
- ***==can be moved from==***(可被移动)——可用于 **==移动语义==**
- 移动构造函数、移动赋值运算符,或实现了移动语义的其他函数重载都**可以绑定到该表达式**;
其中:
- "***has identity***" 的表达式统称为 **`glvalue` 表达式 ——==广义左值表达式==**
- "***can be moved from***" 的表达式统称为 **`rvalue` 表达式 ——==右值表达式==**
在此之下,**根据是否具有这两个属性**,进一步划分并确定了**三个主要"值类别"**:
- "***has identity***" & "***cannot be moved from***" 的表达式—— **`lvalue` ==左值表达式==**
- "***has identity***" & "***can be moved from***" 的表达式—— **`xvalue` ==亡值表达式==**
- "***do not have identity***" & "***can be moved from***"的表达式—— `prvalue` **==纯右值表达式==**
上述划分可由如下表格、韦恩图直观表示 [^1] [^2] :
![[_attachment/02-开发笔记/01-cpp/表达式.assets/IMG-表达式-F776320FB5A392FCF719CBBF3EF0A64E.png|325]]
| | have identity | do not have identity | |
| -------------------- | -------------- | -------------------- | ---------- |
| can be moved from | **==xvalue==** | **==prvalue==** | **rvalue** |
| cannot be moved from | **==lvalue==** | ××× | |
| | **glvalue** | | |
![[_attachment/02-开发笔记/01-cpp/cpp 基本概念/cpp-表达式.assets/IMG-cpp-表达式-F8BDEEC4C7832F7AB7980D15DF0DB65C.png|372]]
> [!caution] 根据 "**能否放在赋值运算符 `=` 左侧或右侧**" 来划分左值、右值是 "从结果倒推原理",认识不够深入。
<br><br>
## 值类别分类
> 参见[^7]
每个 C++表达式的值类别都属于"主要类别 Primary categories"中的一种:`lvalue`,`xvalue` 或 `prvalue`;
表达式的**值类别**如下,包括主要类别(三种)、混合类别(两种)、特殊类别(四种)
- **Primary categories**
- **==lvalue==** ——左值
- **==prvalue==**("pure" rvalue)——纯右值
- **==xvalue==**(an "eXpring" value)——亡值
- **Mixed categories**
- **==glvalue==**("generalized" lvalue)——广义左值
- **==rvalue==** ——右值
- **Special categories**
- **==Pending member function call==** ——挂起的函数调用
- **==Void expression==** ——空表达式
- ==**Bit-fields**== ——位域
- **==Move-eligible expression==** ——可移动表达式
### (1)lvalue 左值
`lvalue` **左值表达式**: "***have identity***" & "***can not be moved from***"
下列表达式都是左值表达式:
- **变量名、函数名**、模版参数对象名、数据成员名,例如 `std::cin`、 `std::endl`;
- **==字符串字面量==**,例如 `"Hello world!"`;
- 字符串字面量本质上是**指向常量字符数组的指针**。
- 所有内置的赋值、复合赋值表达式,例如 `a = b`, `a += b`,`a %= b`;
- **前置递增递减**,例如 `++a`、`--a`;
- **内置间址运算符**,例如 `*p`;
- 对 "**数组左值**" 的**内置下标运算符**,例如 `a[n]`,其中 `a` 是一个数组左值;
- **返回类型为 "==左值引用=="** 的 "函数调用、重载的运算符表达式",例如 `std::getline(std:: cin, str)`、`std:: cout << 1`、`str1 = str2`、`++it`;
- **返回类型为 "==对函数的右值引用=="** 的"函数调用、重载的运算符表达式";
- **转为"==左值引用类型=="** 的强制类型转换表达式,例如 `static_cast<int&>(x)`、`static_cast<void(&)(int)>(x)`;
- **转为"==对函数的右值引用类型=="** 的强制类型转换表达式,例如 `static_cast<void(&&)(int)>(x)`;
- 左值引用类型的 "非类型模版参数";(non-type template parameter)
- ......
###### 左值表达式的性质
- 同广义左值 `glvalue`;
- **==左值可被"取地址"==**(通过内置取址运算符 `&`),例如 `&++i`、`&std::endl`;
- **==左值可用于初始化左值引用==**——用作"别名",将左值引用绑定到左值表达式所标识的对象。
- "**==可修改的左值==**" 可用作内置赋值、复合赋值操作符的左操作数。(字符串字面量是不可修改的左值)
<br>
### (2)prvalue 纯右值
`prvalue` **纯右值表达式**: "***do not have identity***" & "***can be moved from***"
下列表达式都是纯右值表达式:
- **==字面量==**,例如 `42` 、`true`、`nullptr`;
- **返回类型为 "==非引用类型=="** 的"函数调用、重载运算符表达式",例如 `str.substr(1, 2)` 、 `str1 + str2` 、`it++`
- **后置递增递减**,例如 `a++`、`a--`;
- 所有内置的**算术运算表达式**,例如 `a+b` 、 `a%b`、 `a&b`、`a<<b`;
- 所有内置的**逻辑运算表达式**,例如 `a&&b`,`a||b`,`!a`;
- 内置的取址表达式,例如 `&a`;
- 取址运算符 `&` **作用于一个左值对象**,返回一个指向该运算对象的指针,该**指针是一个右值**。
- **转为"==非引用类型=="** 的强制类型转换表达式,例如 `static_cast<double>(x)` 、`std::string{}`、`(int)42`;
- **`this` 指针**
- **lambda 表达式**
- 枚举常量(enumerator)
- 标量类型的非类型模版参数;
- ......
###### 纯右值表达式的性质
- 同右值 `rvalue`;
- **==纯右值不支持多态==**:纯右值所表示对象的**动态类型**始终是**表达式的类型**。
- **非类类型、非数组的纯右值**不能为 "cv 限定类型"(`cv-qualified`) ,除非该纯右值是为了绑定到 "**一个对 cv 限定类型的引用**" 而将其具体化(since C++17)
- 纯右值不能具有 "不完整类型"(除了空类型 `void`、或者用于 `decltype` 说明符);
- 纯右值不能具有抽象类类型或其数组。
<br>
### (3)xvalue 亡值
`xvalue` **亡值表达式**: "***have identity***" & "***can be moved from***" (即属于 `glvalue`,也属于 `rvalue`)
`xvalue` 亡值用来**描述/指代一个==即将被销毁的==、==资源可被移动/复用==的对象**,用于移动语义,通常由下列操作产生:
- **==移动操作==(例如 `std::move`)**;
- **返回"==对对象的右值引用=="的函数调用、重载运算符表达式、强制类型转换**;
这些操作"意图明确地" 返回 `xvalue` ,**==表示该对象的资源可以被移动==**。
下列表达式都是亡值表达式:
- **返回类型为 "==对对象的右值引用=="** 的"函数调用、重载运算符表达式",例如 `std::move(x)`;
- **转为"==对对象类型的右值引用=="** 的强制类型转换表达式,例如 `static_cast<char&&>(x)`;
- 对"**数组右值**"的内置**下标运算符**,例如 `a[n]`,其中数组 `a` 是右值。
- 在临时物化之后指定临时对象的任何表达式(since C++17)
- 一个可移动表达式(since C++23)
> [!caution] "返回类型为 "**==右值引用==**" 的函数",以及 "**到==右值引用==的强制类型转换**",其表达式值类别为 "**==亡值==**",值类型为 "**==非引用类型==**"
>
> 例如:
> - `std::move()` 表达式值类别是 "**==亡值==**",值类型为 "**非引用类型**",而不是一个 "**右值引用(本身是左值)**"。
> - `static_cast<char&&>(x)` 的表达式值类别是 "**==亡值==**",**值类型是 `char`**,而不是一个 **"右值引用 `char&&`(本身是左值)"**。
###### 亡值表达式的性质
同广义左值 `glvalue`、右值 `rvalue`:
`xvalue` 可以绑定到右值引用,支持多态, `non-class` 亡值可以有 `cv-qualifed` 。
<br>
### (4)glvalue 广义左值
`glvalue` **广义左值表达式**:"***have identity***"
###### 广义左值表达式的性质
- 可通过 `lvalue-to-rvalue`、**数组到指针**、**函数到指针**的隐式转换将 **`glvalue` 隐式转换为 `prvalue`**;
- **`glvalue` ==支持多态==**:其**所标识对象的动态类型**不一定是**表达式的静态类型**。
- 在表达式允许的情况下,`glvalue` 可以是不完整类型。
<br>
### (5)rvalue 右值
`rvalue` **右值表达式**: "***can be moved from***"
###### 右值表达式的性质
- 右值**不能被内置取址运算符 `&` 取地址**,例如 `&int()`、`&i++`、`&42`、`&std::move(x)` 都是无效的;
- 右值**不能用作内置赋值操作符、复合赋值操作符的左操作数**;
- 右值 **可用于 "==初始化 const 左值引用==**"、或 "**==右值引用==**",在这种情况下,**由右值标识的==临时对象的生命周期被延长**==,直到引用的作用域结束。
- 当**右值用作函数实参**并且函数有两个重载可用时(一个接受 `rvalue` 引用形参,另一个接受 const `lvalue` 形参),则**右值绑定到右值引用重载**;
- 如果**拷贝/移动构造函数**都可用,则**右值实参调用移动构造函数**;拷贝/移动赋值操作符也是如此)
<br>
### 总结
左值(`lvalue`)、亡值(`xvalue`)、纯右值(`rvalue`)是**表达式的 ==值类别== 属性**(区分于 "**值类型**") 。
有的表达式要求**左值运算对象**,有的需要**纯右值运算对象**;有的表达式**求值结果为左值**,有的表达式**求值结果为纯右值** [^4]。
- 当一个对象被用作纯右值的时候,用的是对象的**值(内容)**;
- 当一个对象被用作左值的时候,用的是对象的**身份(在内存中的位置)**;
在需要纯右值的地方,可以使用左值代替(**实际使用左值的内容/值**),但不能把纯右值当作左值(即内存位置)使用。
亡值 `xvalue` 主要用于**移动语义**,用以明确地特指/标识一个**即将被销毁、资源可被移动/复用**的对象。
> [!example] "函数调用表达式" 值类别说明示例
>
> ```cpp
> void process(int&) {
> cout << "int&" << endl;
> }
>
> void process(int&&) {
> cout << "int&&" << endl;
> }
>
> int var = 25;
>
> int prvalue() { // 函数调用表达式是纯右值
> return 42;
> }
>
> int& lvalue() { // 函数调用表达式是左值
> return var;
> }
>
> int&& xvalue(int&& t) { // "具名的右值引用"形参本身是左值, 而返回"右值引用"类型的函数调用表达式是"亡值"
> // t is initialized with an rvalue expression
> // but is actually an lvalue expression itself
> process(t); // 调用process(int&);
> return static_cast<int&&>(t);
> }
>
>
> int main() {
> process(prvalue()); // 调用process(int&&)
> process(lvalue()); // 调用process(int&)
> process(xvalue(42)); // 调用process(int&&)
> }
> ```
>
> [!faq]
>
>
> - **字面值**(如整数、浮点数、字符字面值等)都是**纯右值** `prvalue`;
> - **指向字符串字面值的指针** 是 **左值**(`lvalue`),因为其本质上是**指向常量字符数组的指针**。
>
> ```cpp
> int x = 42; // 42 是一个整数字面值, 是一个右值
> const char* str = "Hello"; //"Hello"是一个字符串字面值, 但 `str` 是一个左值
> ```
>
>
<br><br><br>
# 常量表达式
**常量表达式**[^3] [^6](Compile-time Constant Expressions):**在==编译时就能得到计算结果==的表达式**,**值不会改变**,**不依赖于任何运行时信息**。
#### 常量表达式
以下几种为常量表达式:
- **字面值**
- **枚举值**
- **由常量表达式初始化的 `const` 对象**
- **`constexpr` 变量或函数的结果**
- `sizeof` 运算符
- `sizeof...` 运算符
#### 常量表达式的使用场景
常量表达式可以用作**非类型模板参数**、**数组大小**,以及在其他**需要常量表达式的上下文中**使用。
- **数组大小**(C++中的数组大小必须是一个常量表达式)
- **`cast` 标签**:在 `switch` 语句中,`case` 后面跟随的值必须是**整型常量表达式**。
- **非类型模版参数**
- **初始化 `constexpr` 变量**
- **静态断言**(`std::static_cast`):用于编译时的断言检查,其条件必须是常量表达式。
<br><br><br>
# 字面量 Literal
C++中的 **字面量/字面值** (literal) 是**嵌入在源代码**中的 **==常量值==** [^5] [^6](P35) ,**是在编译时已知的固定值**。
> Literals are the tokens of a C++ program that represent constant values embedded in the source code.
主要类型的字面量:
- **整数字面量**:表示整数值,如 `42`, `-7`。
- **浮点数字面量**:表示小数值,如 `3.14`, `2.5e-3`。
- **字符字面量**:表示单个字符,如 `'a'`, `'1'`, `'\n'`。
- 以 `\` 开头,表示其后为**八进制数**:`char A = '\101'; // 65,'A'`
- 以 `\x` 开头,表示其后为**十六进制数**:`char B = '\x42'; // 66, 'B'`
- **字符串字面量**:表示字符串,如 `"Hello, world!"`。
- **布尔字面量**:表示布尔值,即 `true` 或 `false`。
- **指针字面量**:特殊的字面量 `nullptr` 用于表示空指针
C++11 中引入了一些新的标准字面量:
- **原始字符串字面量**(Raw String Literals):允许包含转义字符的字符串,如 `R"(C:\Files\Name)"`。
- **Unicode 字符字面量**:如 `u'á'`、`U'\U0001F609'`(表情符号)。
- **二进制字面量**:以 `0b` 或 `0B` 开头的二进制数,如 `0b1010`。
- **八进制字面量**:以 `0` 开头,例如 `074`;
- **十六进制字面量**:以`0x` 开头,例如 `0x1111`
C++11 起,支持**自定义字面量类型**。
### 用户自定义字面量
> sinc C++11
参见: https://en.cppreference.com/w/cpp/language/user_literal
<br><br><br>
# 运算符优先级与结合律
参见 [^6] (P147)
> [!NOTE] "优先级" 和 "结合律" 规定了运算符与运算对象的 "==结合方式=="。
>
> - **高优先级**的运算符,**优先结合 "运算对象"**。
> - **多个运算符优先级相同时**,则按照 "**结合律**" 结合运算对象。
> - 例如,算术运算符为 "**左结合律**",`a/b*c` 将先计算 `a/b`,而不是 `b*c`。
> - 例如,`cin` 与 `cout` 为 "**左结合律**" ,所以 `cout << a << b << c` 是从左到右依次输出。
>
>
> [!caution] C++ 中并未规定对 "运算对象" 的 "**==求值顺序==**"(即先计算 or 后计算哪个操作数)。
>
> ![[_attachment/02-开发笔记/01-cpp/cpp 基本概念/cpp-表达式.assets/IMG-cpp-表达式-FEF4FE1B23D2DC36B747A24B475F5C59.png|525]]
<br>
## 运算符优先级表
![[_attachment/02-开发笔记/01-cpp/cpp 基本概念/cpp-表达式.assets/IMG-cpp-表达式-5380B47932424B2BC9C9B3E413AEFEED.png|866]]
![[_attachment/02-开发笔记/01-cpp/cpp 基本概念/cpp-表达式.assets/IMG-cpp-表达式-1E2F0941423C64641E09690A28218921.png|667]]
![[_attachment/02-开发笔记/01-cpp/cpp 基本概念/cpp-表达式.assets/IMG-cpp-表达式-562BC1B7889E47FC853D5B404F586C84.png|638]]
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: [lvalues, rvalues, glvalues, prvalues, xvalues, help! – C++ on a Friday](https://blog.knatten.org/2018/03/09/lvalues-rvalues-glvalues-prvalues-xvalues-help/)
[^2]: [Value categories, and references to them - UWP applications | Microsoft Learn](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/cpp-value-categories)
[^3]: [Constant expressions - cppreference.com](https://en.cppreference.com/w/cpp/language/constant_expression)
[^4]: [What are rvalues, lvalues, xvalues, glvalues, and prvalues?](https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues)
[^5]: [Expressions - cppreference.com](https://en.cppreference.com/w/cpp/language/expressions#Literals)
[^6]: 《C++ Primer》
[^7]: 《C++ Primer》P471