← 目录 / 第八章 · 函数 / 8.3 函数重载与内联函数
同一个函数名可以对应多种不同的参数组合吗?调用一个很小的函数,开销真的可以忽略不计吗?本节回答这两个问题——函数重载与内联函数。
8.3.1
假设你要写一个"求两个数中较大值"的函数,但数据类型可能是 int,也可能是 double,还可能需要比较三个数——总不能取名叫 MaxInt、MaxDouble、MaxThreeInt 这么多不同的名字吧?
函数重载(Function Overloading)允许在同一作用域内定义多个同名函数,只要它们的参数列表不同(参数的类型、个数或顺序不同),编译器就能把它们区分开,调用时自动匹配最合适的那一个。
| 1 | int Max(int a, int b) { return a > b ? a : b; } |
| 2 | double Max(double a, double b) { return a > b ? a : b; } |
| 3 | int Max(int a, int b, int c) { return Max(Max(a,b),c); } |
| 4 | |
| 5 | int main() |
| 6 | { |
| 7 | cout << Max(3, 5); // 调用①,结果 5 |
| 8 | cout << Max(3.5, 2.1); // 调用②,结果 3.5 |
| 9 | cout << Max(3, 5, 1); // 调用③,结果 5 |
| 10 | return 0; |
| 11 | } |
int GetValue(int x);double GetValue(int x); ← ❌ 编译错误:重复定义8.3.2
调用一个重载函数时,编译器会拿实参和每个候选函数的参数列表逐一比对,优先选择类型完全匹配的版本;如果没有完全匹配的,会尝试隐式类型转换(见 2.1 节)后再匹配。以调用 Max(3, 5) 为例:
如果编译器找不到任何一个"最合适"的版本——比如有两个候选函数经过转换后都同样匹配、难分高下——就会报调用二义性错误(ambiguous call),程序无法编译通过。
8.3.3
void F(int a, double b) 和 void F(double a, int b) 属于两个不同的重载(顺序不同),但和自己完全相同的签名再定义一遍就是重复定义。void F(int a) 与 void F(int a, int b = 5) 同时存在时,调用 F(10) 对编译器来说,两个函数都能满足(第二个函数省略 b 后就等于 F(10)),于是产生二义性错误,编译不通过。解决办法:要么去掉其中一个重载,要么调用时显式传够参数,避免两个候选同时"够格"。F(int) 和 F(double),调用 F('A') 时 char 既能转成 int 也能转成 double,虽然 C++ 规定会优先转成更"接近"的 int,但这种场景容易让代码意图不清晰,建议调用时用强制转换明确写清楚要用哪个版本。8.3.4
每次调用一个普通函数,程序都要经历"跳转到函数所在的位置 → 执行 → 再跳转回来"这一整套流程,这个过程本身是有开销的(涉及压栈、跳转、返回地址记录等)。对于函数体只有一两行、但被频繁调用的小函数,这份开销占比会变得不可忽视。
在函数定义前加上关键字 inline,就是在告诉编译器:请把每一处调用这个函数的地方,直接替换成函数体本身的代码,省去"真正跳转过去再跳回来"的过程。
| 1 | inline int Square(int x) // inline 写在返回类型前面 |
| 2 | { |
| 3 | return x * x; |
| 4 | } |
| 5 | |
| 6 | int main() |
| 7 | { |
| 8 | int y = Square(5) + 1; // 编译后可能直接变成 y = 5 * 5 + 1; |
| 9 | return 0; |
| 10 | } |
inline 关键字,只是请求编译器尝试内联展开,编译器有最终决定权——如果函数体太大、包含循环或递归,编译器很可能会直接忽略这个建议,仍按普通函数处理。反过来,即使不写 inline,现代编译器也可能自动把简单的小函数做内联优化。因此 inline 更多是一种"性能提示",而不是必须遵守的语法规则。-O2 优化)通常已经足够聪明,会自动内联合适的小函数,手动加 inline 关键字更多是一种"锦上添花"的提示,而不是必需操作。理解它的原理比死记写法更重要。