把代码封装成函数可以让程序结构更加清晰,但在计算机底层,每次调用普通函数都会有一点额外开销。程序需要先暂停当前位置的执行,跳转到函数所在的位置去运行函数体,执行完以后再跳回原来的地方。
对于大多数函数来说,这点跳转时间微不足道。但如果一个函数只有一两行代码,却在循环里被连续调用成千上万次,来回跳转花掉的时间甚至比执行代码本身还要长。为了省去这种短小函数的跳转开销,C++ 提供了 “内联函数(inline function)”。
什么是内联函数?
在 C++ 中,如果我们在定义函数时把 inline 关键字写在返回类型前面,这个函数就叫做 “内联函数”。其中,inline 的英文意思就是 “在行内展开”。
语法:
inline 返回类型 函数名(参数列表) {
// 函数体
}说明:
内联函数的工作原理非常直观。编译器在编译源代码时,一旦遇到内联函数的调用,不会生成普通的 “函数跳转指令”,而是直接把函数体内部的代码复制到调用处。
这样一来,程序在运行时就不需要来回跳转了,而是像顺序执行普通代码一样直接往下走。接下来,我们通过两个简单的例子来看看内联函数是如何使用的。
示例 1:定义并使用内联函数
#include <iostream>
// 返回两个整数中的较大值
inline int getMax(int a, int b) {
return a > b ? a : b;
}
int main() {
int res1 = getMax(10, 20);
int res2 = getMax(35, 15);
std::cout << "res1 = " << res1 << '\n';
std::cout << "res2 = " << res2 << '\n';
return 0;
}运行结果如下。
res1 = 20
res2 = 35分析:
在这个例子中,我们在 getMax() 函数的返回类型前面加上了 inline 关键字,函数内部使用三元运算符比较并返回 a 和 b 中的较大值。
在编译阶段,编译器会把调用内联函数的地方直接替换为函数体内部的代码。对于这个例子来说,main() 函数中的两行调用在编译后就相当于执行了下面的代码:
int main() {
// 原地展开函数体代码
int res1 = (10 > 20 ? 10 : 20);
int res2 = (35 > 15 ? 35 : 15);
std::cout << "res1 = " << res1 << '\n';
std::cout << "res2 = " << res2 << '\n';
return 0;
}从上面可以看出,使用内联函数后,我们在编写代码时依然保留了 getMax(10, 20) 这样清晰好读的函数调用形式,而在程序实际运行时又省去了来回跳转的开销。
内联函数最典型的使用场景,就是在循环内部被反复调用的简短计算函数。下面我们再来看一个计算整数平方的例子。
示例 2:在循环中调用内联函数
#include <iostream>
// 计算整数的平方
inline int getSquare(int x) {
return x * x;
}
int main() {
for (int i = 1; i <= 5; i++) {
std::cout << i << " 的平方是:" << getSquare(i) << '\n';
}
return 0;
}运行结果如下。
1 的平方是:1
2 的平方是:4
3 的平方是:9
4 的平方是:16
5 的平方是:25分析:
getSquare(i) 在循环里被连续调用了 5 次。由于 getSquare() 是内联函数,编译器会在循环体内部直接将 getSquare(i) 展开为 i * i,避免了每次循环都要发生一次函数跳转。
内联函数的适用场景与注意事项
既然内联函数能省去跳转开销,那我们干脆给所有函数都加上 inline 不就行了吗?其实并非如此,因为内联函数本质上是 “以空间换时间”。
普通函数的代码在内存中只存一份,哪怕你在 100 个地方调用它,也是每次跳转到同一个位置去执行。而内联函数每被调用一次,编译器就会把函数体原地复制一份。如果把几十行的长函数设为内联并到处调用,展开后就会多出大量重复指令。这样会让生成的可执行程序体积明显变大,通常称为 “代码膨胀”。
因此,我们需要根据函数的代码长度与内部结构来判断是否适合加 inline,常见情况如下表所示。
| 函数特征 | 是否建议加 inline | 原因说明 |
|---|---|---|
| 代码只有 1~5 行且高频调用 | 建议 | 函数本身执行极快,省去跳转开销的效果最明显 |
| 函数体较长(如超过 10 行) | 不建议 | 函数本身耗时远大于跳转耗时,展开后会导致代码膨胀 |
| 内部包含复杂的循环结构 | 不建议 | 循环本身执行时间较长,省去一次外部跳转几乎没有提升 |
| 内部调用自身的递归函数 | 不能内联 | 递归层数通常在运行时才确定,编译阶段无法无限展开 |
此外,在使用内联函数时,小伙伴们还要注意以下 2 个语法规则:
- inline 只是给编译器的建议:写下
inline并不是强制命令。如果我们给几十行的长函数、带复杂循环的函数或上一节学过的递归函数加上inline,编译器会自动忽略它并按普通函数处理,不会导致编译报错。 - 通常在调用前直接写出完整定义:普通函数可以先把函数声明放在前面、把函数定义放在后面。但内联函数在被调用时,编译器必须立刻看到完整的函数体代码才能原地替换。因此我们通常直接在调用处前面写出内联函数的完整定义,不再单独拆开写声明。
常见问题
1. 平时写只有两三行的短函数时,必须手动加上 inline 吗?
不需要刻意去加。像 GCC、Clang、MSVC 等现代 C++ 编译器已经非常智能。开启编译优化后,哪怕我们没有手写 inline 关键字,编译器也会自动把合适的短小函数做内联展开。
在实际开发中,显式写 inline 更常见的用途是在后面学习多文件编程时,用来把简短函数的完整定义直接放进头文件(.h)里供多个源文件共用,避免触发重复定义报错。现阶段写单文件代码时,小伙伴们直接按普通函数编写就完全够用了。
2. 内联函数里面只能写一行 return 语句吗?能不能定义局部变量或写 if 判断?
完全可以。现代 C++ 对内联函数的语法没有额外限制,大家在里面定义局部变量、写简单的 if...else 条件判断都是合法的。只要函数整体代码简短、不包含耗时很长的复杂循环或递归即可。
