Skip to content

Commit c62110f

Browse files
committed
Update: enhance documentation on const qualifiers, overload resolution, and value categories
1 parent 7155a60 commit c62110f

File tree

4 files changed

+153
-8
lines changed

4 files changed

+153
-8
lines changed

src/zh/03-types/cv-qualifiers.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ const auto f = e; // f 的类型是 const int
6363
因为 `const` 带来的不能被修改的性质,类型有 `const` 限定的对象往往被称为常量。相对地,没有被 `const` 限定的对象往往被称为变量。但技术性地说,`const` 限定的对象也是变量,只是不能被修改。当看到一个对象声明为 `const int a = 1;` 时,我们可以说 `a` 是一个常量,也可以说 `a` 是一个变量,或者说 `a` 是一个常变量,这都是正确的,这些说法并不冲突。
6464
:::
6565

66+
### 在函数参数中使用 const 限定符
67+
68+
在函数参数中使用的 `const` 限定符会被忽略。例如:
69+
```cpp
70+
void func(const int a);
71+
void func(int a); // 两个函数是等价的
72+
```
73+
74+
注意这仅是指最外层的 `const` 限定符。对于复合类型而言,需要注意观察 `const` 限定符的位置。在介绍复合类型时,会对此详细讨论。
75+
6676
## volatile 限定符
6777
6878
对于 `volatile` 限定的类型的对象,编译器认为其值可能在任何时候被修改。具体而言,类型有 `volatile` 限定的对象的读写都被视作有副作用(普通的对象只有写视为副作用),因此当访问 `volatile` 限定的对象值时,程序总是会从对象的内存位置得到值,而不能简化。

src/zh/03-types/overload.md

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,138 @@ void some_function(){
7777
}
7878
```
7979

80-
此时,即使 `make_some_value` 的返回类型发生了变化,`some_function` 中,(至少这里写出的部分)完全不需要进行任何修改。这样,代码的维护成本就大大降低了。
80+
假如此时 `make_some_value` 的返回类型发生了变化,`some_function` 中,这段代码
81+
```cpp
82+
auto temp_value = sqrt(value);
83+
```
84+
会变成
85+
```cpp
86+
auto temp_value = sqrt(value); // 是的,对于程序员写下的代码来说,完全没有变化
87+
```
88+
于是,上面写出来的部分完全不需要进行任何修改。这样,代码的维护成本就大大降低了。
89+
90+
## 重载决议
8191

82-
## 算数类型的重载
92+
重载决议是编译器在多个候选函数中选择最合适版本的过程。这个过程分为三个阶段:
8393

84-
::: important TODO: 补充内容
94+
- **候选函数收集**:查找与被调用函数同名且在作用域内的所有函数声明
95+
- **可行函数筛选**:排除参数数量不匹配或无法隐式转换参数类型的候选
96+
- **最佳匹配选择**:根据优先级确定最优匹配
97+
98+
### 候选函数收集
99+
100+
简单来说,候选函数是指与被调用函数同名,并且在相同作用域内的所有函数声明。例如:
101+
102+
```cpp
103+
void foo(int a);
104+
void foo(int a, int b);
105+
void foo(double a);
106+
107+
namespace example {
108+
void foo(int a);
109+
void foo(int a, int b);
110+
void foo(double a);
111+
}
112+
113+
foo(1); // 候选函数集合为 {foo(int), foo(int, int), foo(double)}
114+
example::foo(2); // 候选函数集合为 {example::foo(int), example::foo(int, int), example::foo(double)}
115+
```
116+
117+
这里调用 `foo(1)` 时,会在全局作用域中查找 `foo` 函数,而调用 `example::foo(2)` 时,会在 `example` 命名空间中查找 `foo` 函数。
118+
119+
这里要考虑[`using` 声明](../02-program-structure/scope.md#using-声明)和[`using` 指令](../02-program-structure/scope.md#using-namespace-指令) 的影响。
120+
121+
对于 `using` 声明,它将接下来的名称替换为一个限定的名称,例如:
122+
123+
```cpp
124+
void foo(int a);
125+
126+
namespace example {
127+
void foo(int a);
128+
}
129+
130+
int main() {
131+
using example::foo;
132+
foo(1); // 候选函数集合为 {example::foo(int)},而不包括 {foo(int)}
133+
}
134+
```
135+
136+
对于 `using` 指令,它将整个命名空间的名称引入当前作用域,例如:
137+
138+
```cpp
139+
void foo(int a);
140+
141+
namespace example {
142+
void foo(int a);
143+
}
144+
145+
int main() {
146+
using namespace example;
147+
foo(1); // 候选函数集合为 {foo(int), example::foo(int)},这里会因为无法决定用哪个而报错
148+
}
149+
```
150+
151+
::: info using namespace std;
152+
在 C++ 中,有一个特殊的命名空间 `std`,其中包含了大量的标准库函数和对象。
153+
在一些快速开发测试的情况,程序员不会定义很多函数,而倾向于直接使用标准库。这时候就会 `using namespace std;` 语句,将 `std` 命名空间中的所有名称引入当前作用域,省去每次都写 `std::` 的麻烦。
154+
但是,对于较大的项目而言,这样会把大量的名称引入当前作用域,可能会导致名称冲突。
85155
:::
86156
87-
## 涉及 `const``volatile` 的重载
157+
### 可行函数筛选
88158
89-
::: important TODO: 补充内容
159+
在候选函数集合中,会排除那些参数数量不匹配,或者参数无法转换过去的函数。例如:
160+
161+
```cpp
162+
void foo(int a, int b);
163+
void foo(int a);
164+
165+
foo(1);
166+
// 候选函数集合为 {foo(int), foo(int, int)}
167+
// 其中,foo(int, int) 无法匹配参数 1,不是可行函数,因此调用 foo(int)
168+
```
169+
170+
### 最佳匹配选择
171+
172+
在可行函数集合中,会根据参数的类型和优先级选择最佳匹配的函数。例如:
173+
174+
```cpp
175+
void foo(int a);
176+
void foo(double a);
177+
178+
foo(1); // 调用 foo(int)
179+
```
180+
181+
这里简单概括一下最佳匹配的规则:
182+
- 如果有类型完全匹配的函数,选择这个函数
183+
- 否则,选择最接近的类型
184+
185+
如果此时无法判断调用哪个函数,编译器会报错。例如:
186+
187+
```cpp
188+
void foo(bool a);
189+
void foo(short a);
190+
191+
foo(1); // [!code error] // 无法判断调用哪个函数
192+
```
193+
194+
::: info 浮点与整数
195+
C++ 中常常被认为比较糟糕的一点是,浮点数和整数之间是可转换的。例如:
196+
197+
```cpp
198+
int a = 1.1; // a 的值是 1,浮点转换到整数,截断小数部分
199+
```
200+
201+
只有上述这种情况,问题看起来并不大,但当我们考虑函数重载时:
202+
203+
```cpp
204+
void foo(int a);
205+
void foo(float a);
206+
207+
foo(1.1); // [!code error] // 无法判断调用哪个函数
208+
```
90209
:::
91210
92-
## 左值引用和右值引用的重载
211+
### 左值引用和右值引用的重载
93212
94213
::: important TODO: 补充内容
95214
:::
@@ -99,7 +218,7 @@ void some_function(){
99218
::: important TODO: 润色与细化描述
100219
:::
101220
102-
在声明函数类型时,并不能推导出函数调用类型为何,因此在声明函数类型时,必须显式指定参数类型,而不能使用 `auto`。例如:
221+
在声明函数类型时,并不能推导出函数参数类型为何,因此在声明函数类型时,必须显式指定参数类型,而不能使用 `auto`。例如:
103222
```cpp
104223
using binary_int_func = int(auto, auto); // [!code error] // 错误,auto 不能用在这里
105224
```

src/zh/03-types/reference-type/object-ref.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,22 @@ set_a_to(a); // a 是左值,也可以传入
268268
269269
这个 `set_a_to` 函数当然也可以使用 `int` 类型作为参数,但当类型更为复杂,构造参数的成本也不得不被考虑时,常量左值引用的作用就会凸显出来。
270270
271+
## 常量左值引用的语法结构
272+
273+
前面我们介绍了 `const int&` 这种形式的常量左值引用。考虑引用的语法形式 `type_name &`,所以这里引用指代的类型是 `const int` 而不是 `int`。换句话说,`const` 限定的是 `int` 类型,而非 `int &` 这个引用类型。
274+
275+
在[const限定](../cv-qualifiers.md)一章中,我们提到限定符的顺序是可以交换的,`const int` 和 `int const` 是等价的。因此,`const int&` 和 `int const&` 是等价的。
276+
277+
但需要注意,C++ 规定,引用类型本身是不能被限定的,因此 `int& const` 是错误的写法。
278+
279+
此外,使用别名处理时,添加在引用类型上的限定符会被忽略,例如:
280+
```cpp
281+
using int_ref = int&;
282+
using const_int_ref = const int_ref; // const_int_ref 是 int& 类型,而非 const int&
283+
284+
int a = 1;
285+
const int_ref ref = a; // ref 是 int& 类型
286+
```
271287

272288
## 悬垂引用
273289

src/zh/03-types/value-category.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: 3.4 值类别
2+
title: 3.4 值类别(不是类型)
33
---
44

55
**值类别**(value category)在 C++ 是一个用来描述表达式性质的概念。注意,值类别并不是表达式的类型,放在类型一章的此处,是因为这个概念对于对接下来类型的理解是必要的。

0 commit comments

Comments
 (0)