Kodewick
第 9 课,共 30 课目录

重载和默认参数

第 9 课,共 30 课 · 第 2 部分 · 流程和函数 · 约 15 分钟

在 C 语言里,两个函数不能同名。C++ 可以:只要参数不一样,几个函数就能共用一个名字,这叫重载。调用时,编译器看你给的实参,挑出合适的那一个。

main.cpp
1#include <iostream>2 3int bigger(int a, int b) {4    if (a > b) {5        return a;6    }7    return b;8}9 10double bigger(double a, double b) {11    if (a > b) {12        return a;13    }14    return b;15}16 17int main() {18    std::cout << bigger(3, 8) << '\n';19    std::cout << bigger(2.5, 1.5) << '\n';20    return 0;21}
输出
8
2.5

不用起 bigger_int、bigger_double 这样的名字,用的人只要记住「bigger 取大的那个」。

同名,参数不同

参数不同,可以是个数不同,也可以是类型不同:

main.cpp
1#include <iostream>2 3int area(int side) {4    return side * side;5}6 7int area(int width, int height) {8    return width * height;9}10 11double area(double radius) {12    return 3.14159 * radius * radius;13}14 15int main() {16    std::cout << "正方形:" << area(4) << '\n';17    std::cout << "长方形:" << area(4, 6) << '\n';18    std::cout << "圆:" << area(2.0) << '\n';19    return 0;20}
输出
正方形:16
长方形:24
圆:12.5664

三个 area:一个参数的 int、两个参数的 int、一个参数的 double。area(4) 和 area(2.0) 都只给了一个实参,靠类型分开:4 是 int,2.0 是 double。

编译器怎么挑

编译器先找类型完全一样的。找不到,再看哪个能「升一级」就合适,比如 char 和 bool 可以当 int 用。还不行,才考虑别的转换。

main.cpp
1#include <iostream>2 3void show(int n) {4    std::cout << "int:" << n << '\n';5}6 7void show(double d) {8    std::cout << "double:" << d << '\n';9}10 11int main() {12    show(7);13    show(7.5);14    show('A');15    return 0;16}
输出
int:7
double:7.5
int:65

'A' 是 char,两个 show 都不完全一样;char 升成 int 最自然,所以挑了 show(int),显示的是 'A' 的编码 65。

先猜一猜

这段程序显示什么?

1#include <iostream>2 3void show(int n) {4    std::cout << "int\n";5}6 7void show(double d) {8    std::cout << "double\n";9}10 11int main() {12    show(10 / 4);13    return 0;14}

只有返回类型不同,不算重载。int read_value() 和 double read_value() 参数一样,调用 read_value() 时编译器没法分辨,所以第二个定义就是编译错误。

挑不出来

有时候两个函数一样合适,编译器不会替你猜,直接报错:

main.cpp
1#include <iostream>2 3double cost(int count, double price) {4    return count * price;5}6 7double cost(double weight, int price) {8    return weight * price;9}10 11int main() {12    std::cout << cost(3, 5) << '\n';13    return 0;14}
输出
main.cpp: In function 'int main()':
main.cpp:12:22: error: call of overloaded 'cost(int, int)' is ambiguous
   12 |     std::cout << cost(3, 5) << '\n';
      |                  ~~~~^~~~~~

call of overloaded 'cost(int, int)' is ambiguous:调用 cost(int, int) 有歧义。第一个 cost 要把 5 转成 double,第二个要把 3 转成 double,各转一个,不分高下。在你自己的电脑上,g++ 还会在下面列出两个候选(candidate)。

改法是把意思写清楚:写 cost(3, 5.0) 就是第一个,写 cost(3.0, 5) 就是第二个。更好的办法是一开始就别让两个重载这么像。

默认参数

有的参数大多数时候都是同一个值。给它一个默认值,调用时不写,就用默认的:

main.cpp
1#include <iostream>2 3double final_price(double price, double discount = 0) {4    return price * (1 - discount);5}6 7int main() {8    std::cout << final_price(200) << '\n';9    std::cout << final_price(200, 0.25) << '\n';10    return 0;11}
输出
200
150

final_price(200) 没给 discount,它就是 0;final_price(200, 0.25) 给了,就用 0.25。

几个参数都可以有默认值。调用时从左往右给,没给的用默认值:

main.cpp
1#include <iostream>2 3void line(int width = 10, char ch = '-') {4    for (int i = 0; i < width; i++) {5        std::cout << ch;6    }7    std::cout << '\n';8}9 10int main() {11    line();12    line(5);13    line(5, '*');14    return 0;15}
输出
----------
-----
*****

line(5) 的 5 给了第一个参数 width,ch 还是 '-'。没法跳过 width 只给 ch。

默认值只能放在后面

一个参数有了默认值,它右边的参数也都得有。下面这样写是编译错误:

1void line(int width = 10, char ch) {2    // ...3}

g++ 会说 default argument missing for parameter 2:第 2 个参数少了默认值。道理和上面一样:实参从左往右对上参数,调用 line('*') 时,'*' 只会给 width,ch 永远拿不到值。所以要把「常常不用给」的参数排在最后。

注意: 先写了声明(原型)的函数,默认值写在声明里,定义里不要再写一遍,否则也是编译错误。

重载和默认参数撞车

默认参数让一个函数能用好几种个数的实参来调用,于是它可能和另一个重载撞上:

main.cpp
1#include <iostream>2 3int total(int a, int b) {4    return a + b;5}6 7int total(int a, int b, int c = 0) {8    return a + b + c;9}10 11int main() {12    std::cout << total(1, 2, 3) << '\n';13    std::cout << total(1, 2) << '\n';14    return 0;15}
输出
main.cpp: In function 'int main()':
main.cpp:13:23: error: call of overloaded 'total(int, int)' is ambiguous
   13 |     std::cout << total(1, 2) << '\n';
      |                  ~~~~~^~~~~~

total(1, 2, 3) 只有第二个能接;total(1, 2) 两个都能接,编译器又挑不出来了。第二个 total 有了默认值,已经能做第一个的事,删掉第一个就好。

能用默认参数解决的,就别再写一个重载。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

两个数或三个数的平均

写两个重载的 average:一个接收两个 int,一个接收三个 int,都返回 double 的平均数。程序要显示 7.5 和 6。

要显示
7.5
6
exercise1.cpp
1#include <iostream>2 3int main() {4    std::cout << average(5, 10) << '\n';5    std::cout << average(4, 6, 8) << '\n';6    return 0;7}
输出
点「运行」或按 Ctrl+Enter
练习 2

小费

给 tip 函数的 rate 加上默认值 0.15,让 tip(100) 也能用。程序要显示 15 和 20。

要显示
15
20
exercise2.cpp
1#include <iostream>2 3double tip(double bill, double rate) {4    return bill * rate;5}6 7int main() {8    std::cout << tip(100) << '\n';9    std::cout << tip(100, 0.2) << '\n';10    return 0;11}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它:响几声

这段程序编译不过:ring() 不知道该调哪一个。删掉多余的函数,让 ring() 响两声,ring(3) 响三声。

要显示
叮叮
叮叮叮
exercise3.cpp
1#include <iostream>2 3void ring() {4    std::cout << "叮\n";5}6 7void ring(int times = 2) {8    for (int i = 0; i < times; i++) {9        std::cout << "叮";10    }11    std::cout << '\n';12}13 14int main() {15    ring();16    ring(3);17    return 0;18}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • 重载:几个函数同名,只要参数的个数或类型不同;只有返回类型不同不算。
  • 编译器按实参挑:先找类型完全一样的,再找能「升一级」的(char → int),再看别的转换。
  • 两个一样合适就报错 call of overloaded ... is ambiguous;把实参写清楚,或者别写太像的重载。
  • 默认参数 double discount = 0:调用时不给就用它;实参从左往右对上参数。
  • 有默认值的参数要放在最后;默认值写在声明里,不在定义里再写。
  • 能用默认参数解决的,就别再加一个重载,免得撞车。