@@ -77,19 +77,138 @@ void some_function(){
77
77
}
78
78
```
79
79
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
+ ## 重载决议
81
91
82
- ## 算数类型的重载
92
+ 重载决议是编译器在多个候选函数中选择最合适版本的过程。这个过程分为三个阶段:
83
93
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
+ 但是,对于较大的项目而言,这样会把大量的名称引入当前作用域,可能会导致名称冲突。
85
155
:::
86
156
87
- ## 涉及 ` const ` 和 ` volatile ` 的重载
157
+ ### 可行函数筛选
88
158
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
+ ```
90
209
:::
91
210
92
- ## 左值引用和右值引用的重载
211
+ ### 左值引用和右值引用的重载
93
212
94
213
::: important TODO: 补充内容
95
214
:::
@@ -99,7 +218,7 @@ void some_function(){
99
218
::: important TODO: 润色与细化描述
100
219
:::
101
220
102
- 在声明函数类型时,并不能推导出函数调用类型为何 ,因此在声明函数类型时,必须显式指定参数类型,而不能使用 ` auto ` 。例如:
221
+ 在声明函数类型时,并不能推导出函数参数类型为何 ,因此在声明函数类型时,必须显式指定参数类型,而不能使用 `auto`。例如:
103
222
```cpp
104
223
using binary_int_func = int(auto, auto); // [!code error] // 错误,auto 不能用在这里
105
224
```
0 commit comments