%% # 纲要 > 主干纲要、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