@@ -20,7 +20,11 @@ b = 43; // 修改了 a 的值
20
20
21
21
在这个例子中,通过对 ` b ` 赋值,修改了 ` a ` 的值。
22
22
23
- 除了一些特例外,引用总是当做指代的对象来使用(对于读取值或者写入值,一定是当做指代的对象来使用)。并且,在初始化之后,不能更改这种指代。用一个对象初始化引用,也称为将引用绑定到对象。例如:
23
+ 除了一些特例外,引用总是当做指代的对象来使用(对于读取值或者写入值,一定是当做指代的对象来使用)。并且,在初始化之后,不能更改这种指代。
24
+
25
+ 用一个对象初始化引用,也称为将引用** 绑定** 到对象。
26
+
27
+ 例如:
24
28
``` cpp
25
29
int a = 1 ; // a 初始化为不确定值
26
30
int & b = a; // 将 b 绑定到 a
@@ -41,13 +45,13 @@ int& b = a; // 以具有不确定值的 a 初始化引用
41
45
b = 1 ; // 将 1 赋值给 a
42
46
```
43
47
44
- 引用不是一个对象,具体来说,引用类型不需要有对齐和大小、不需要占用内存,也不能声明引用的引用。但根据需要, 编译器可以生成使用存储来实现的引用。
48
+ 引用不是一个对象,具体来说,引用类型不需要有对齐和大小、不需要占用内存,也不能声明引用的引用。(但是,根据需要, 编译器可以生成使用存储来实现的引用。)
45
49
46
50
在前面的[ 声明] ( ../02-program-structure/declaration.md#类型与对象 ) 一节中提到,变量不一定是对象。这里我们可以给出更准确的说法:对象,以及绑定到对象的引用,统称为** 变量** 。
47
51
48
52
## 引用类型的声明与初始化
49
53
50
- 引用类型有两种,左值引用类型和右值引用类型 。
54
+ 引用类型有两种,** 左值引用 ** 类型和 ** 右值引用 ** 类型 。
51
55
52
56
左值引用类型的形式是
53
57
``` cpp
@@ -96,7 +100,7 @@ int a = 1;
96
100
auto & ref = a; // ref 是 int& 类型
97
101
```
98
102
99
- ### 在函数参数中使用左值引用
103
+ ### 函数中的左值引用
100
104
101
105
可以在函数参数中使用左值引用,来修改作为参数传入的对象:
102
106
``` cpp
@@ -109,7 +113,7 @@ set_to_42(b); // b 的值变为 42
109
113
```
110
114
111
115
::: tip 传出参数
112
- 这种通过引用修改参数的方式,也称为传出参数 。例如考虑如下的代码:
116
+ 这种通过引用修改参数的方式,也称为**传出参数** 。例如考虑如下的代码:
113
117
114
118
```cpp
115
119
bool safe_divide(int a, int b, int& result) {
@@ -127,10 +131,9 @@ safe_divide(a, b, c); // c 的值变为 5
127
131
128
132
在这个 ` safe_divide ` 函数的设计中,我们需要从这个函数得到两个结果,一是函数是否执行成功,二是计算的结果。这种情况下,可以使用引用来传出结果。
129
133
130
- 但是,在工程中 ,我们一般不希望函数修改传入的参数,因此这种传出参数的方式在使用时应充分注释 ,或遵循一定的代码规范,以免造成误解。
134
+ 注意,在实际工程中 ,我们一般不希望函数修改传入的参数,传出参数的形式在使用时应充分注释 ,或遵循一定的代码规范,以免造成误解。
131
135
:::
132
136
133
- ### 在函数返回值中使用左值引用
134
137
135
138
可以在返回值中使用左值引用,例如:
136
139
``` cpp
@@ -150,7 +153,7 @@ ref = 3; // 修改了 b 的值
150
153
max(a, b) = 4; // 由于 max 返回的是引用,因此可以直接修改返回值,b 的值变为 4
151
154
```
152
155
153
- 这里可以发现,当函数返回一个左值引用时,可以直接对返回值进行赋值操作, 结合在[值类别](./value-category.md)中的讲解,这种函数的调用是左值表达式。我们可以补充前面值类别的定义:
156
+ 这里可以发现,当函数返回一个左值引用时,可以直接对返回值进行赋值操作。 结合在[值类别](./value-category.md)中的讲解,这种函数的调用是左值表达式。我们可以补充前面值类别的定义:
154
157
155
158
> 下列表达式是左值表达式:
156
159
> - 返回值类型是引用类型的函数调用
@@ -176,7 +179,7 @@ ref = 2; // 为什么?这不是字面量吗?
176
179
```
177
180
你可能会奇怪,为什么右值引用绑定到字面量之后,这个右值引用还能修改?右值引用不是右值吗?
178
181
179
- 首先,右值引用的名字本身,是一个标识符组成的基础表达式,当然是左值表达式。
182
+ 首先,右值引用的名字本身(也即这里的 ` ref ` ) ,是一个标识符组成的基础表达式,当然是左值表达式。
180
183
181
184
此外,` ref ` 所绑定到的对象,实际上是一个从 ` 1 ` 构造而来的临时对象。为了将一个右值表达式绑定给右值引用,会将右值表达式转换为一个与引用类型匹配的[ 将亡值表达式] ( ./value-category.md ) 。
182
185
将亡值表达式代指这个临时的对象,这个临时的对象的生命周期会延长到绑定到它的引用的生命期结束。举例而言:
@@ -185,7 +188,7 @@ ref = 2; // 为什么?这不是字面量吗?
185
188
int && ref = true ; // 用 true 初始化一个 int 类型的临时对象,将这个临时对象绑定到 ref
186
189
ref = 2 ; // 修改了这个临时对象的值,而非字面量 true 的值
187
190
```
188
-
191
+ ::: info 特殊的 auto&&
189
192
前面介绍过 ` auto & ` 可以推导左值引用类型,但 ` auto && ` 并不是推导右值引用,而是“通用引用”,它会根据初始化器的值类别推导出适合的引用类型,例如:
190
193
191
194
``` cpp
@@ -195,8 +198,10 @@ auto&& ref2 = 1; // ref2 是 int&& 类型
195
198
```
196
199
197
200
这一原理及其应用会在后面的章节中详细介绍。
201
+ :::
202
+
198
203
199
- ### 在函数参数中使用右值引用
204
+ ### 函数中的右值引用
200
205
201
206
类似于左值引用,右值引用也可以用在函数参数中,例如:
202
207
``` cpp
@@ -217,10 +222,7 @@ set_a_to(a + 1); // a + 1 是纯右值表达式
217
222
set_a_to (a); // [ !code error] // 错误,a 是左值表达式
218
223
```
219
224
220
- ### 在函数返回值中使用右值引用
221
-
222
- ::: important TODO 补充内容
223
- :::
225
+ 函数返回值中当然也可以使用右值引用,其作用和左值引用相似,但是二者的差异涉及**重载**的知识,在本章后面的部分会介绍相关内容,这里暂且放下。
224
226
225
227
## 常量左值引用
226
228
@@ -244,7 +246,10 @@ const auto& ref2 = a; // ref2 也是 const int& 类型
244
246
```
245
247
246
248
::: info 为什么允许常量左值引用绑定到右值?
247
- 当我们使用常量左值引用时,我们无法通过这个引用修改绑定的对象。因此,通过常量左值引用访问一个对象的过程,可以理解为我们只需要这个对象的值,而不需要知道这个对象在哪里占用什么内存。这就和我们使用算术表达式时一样,在计算 ` 1 + 2 + 3 ` 的时候,我们不需要知道 ` 1 + 2 ` 的结果存储在哪里,只需要知道这个结果是 ` 3 ` 就行,足够我们继续计算。
249
+ 当我们使用常量左值引用时,我们无法通过这个引用修改绑定的对象。
250
+
251
+ 因此,通过常量左值引用访问一个对象的过程,可以理解为我们只需要这个对象的值,而不需要知道这个对象在哪里占用什么内存。这就和我们使用算术表达式时一样,在计算 ` 1 + 2 + 3 ` 的时候,我们不需要知道 ` 1 + 2 ` 的结果存储在哪里,只需要知道这个结果是 ` 3 ` 就行,足够我们继续计算。
252
+
248
253
因此,就像在其他地方使用右值一样,我们也应当允许常量左值引用绑定到右值,使我们能够更方便地使用这个值。
249
254
:::
250
255
@@ -281,7 +286,9 @@ int&& max(int&& a, int&& b) {
281
286
max(1, 2) = 3; // [!code error] // 错误,试图使用悬垂引用
282
287
```
283
288
284
- 在这个例子中,` max ` 函数的两个参数绑定到了从字面量 ` 1 ` 和 ` 2 ` 构造的临时对象。这两个临时对象的生命期在 ` max ` 函数返回后结束,但是 ` ref ` 仍然绑定到这两个临时对象中的较大者。这种情况下,` ref ` 就是一个悬垂引用。在第二次调用 ` max ` 函数时,需要使用 ` ref ` 绑定的对象的值来比较大小,但是这个对象的生命期已经结束,因此这个操作是错误的。
289
+ 在这个例子中,` max ` 函数的两个参数绑定到了从字面量 ` 1 ` 和 ` 2 ` 构造的临时对象。这两个临时对象的生命期在 ` max ` 函数返回后结束,但是 ` ref ` 仍然绑定到这两个临时对象中的较大者。
290
+
291
+ 这种情况下,` ref ` 就是一个悬垂引用。在第二次调用 ` max ` 函数时,需要使用 ` ref ` 绑定的对象的值来比较大小,但是这个对象的生命期已经结束,因此这个操作是错误的。
285
292
286
293
上面的例子用常量左值引用写时,会更难以被发现:
287
294
``` cpp
@@ -293,8 +300,7 @@ const int& max(const int& a, const int& b) {
293
300
}
294
301
}
295
302
296
- // ref 是悬垂引用,由于不能写入,因此显得很隐晦
297
- const int& ref = max(1, 2);
303
+ const int& ref = max(1, 2); // ref 是悬垂引用,由于不能写入,因此显得很隐晦
298
304
const int& ref2 = max(ref, 3); // ![ !code error] // 错误,试图使用悬垂引用
299
305
```
300
306
0 commit comments