一、什么是内联函数
- 编译器尝试将
inline
函数的代码直接插入调用处(类似宏展开),避免函数调用的压栈、跳转、返回等额外开销。 - 适用于短小频繁调用的函数:如简单的
getter/setter
、数学运算等。 inline
只是 建议,编译器可能忽略(如函数太复杂或递归)。内联展开是以空间换时间,过度使用会导致代码膨胀(code bloat)。
inline int add(int a, int b) {return a + b;
}int main() {int result = add(3, 5); // 可能被替换为 `int result = 3 + 5;`return 0;
}
可以看出,内联函数少了函数压栈,出栈,调用的过程,所以可以节约传参时间,但是也会导致代码膨胀
二、类内定义的成员函数默认是 inline
在类内部直接定义的成员函数(非显式声明)隐式内联:
class MyClass {
public:int getValue() { // 隐式 inlinereturn value;}
private:int value;
};
上述代码等价于:
class MyClass {
public:inline int getValue() { // 隐式 inlinereturn value;}
private:int value;
};
但是具体的行为还是取决于编译器。即使不内联,也会为其生成链接符号(在有虚函数、虚表机制时更复杂)。
三、C++17 引入的 inline
变量
C++17 允许在头文件中定义 inline
变量,避免多次定义错误。在C++17之前如果在头文件中直接定义(非声明)一个全局变量,且该头文件被多个源文件(.cpp
)包含,会导致同一个变量被多次定义(ODR,One Definition Rule),链接时会报错。
3.1、这么写是错误的
global.h
int num = 10;
如果多个 .cpp
文件包含此头文件会导致重复定义问题。
3.2、传统解决方案
使用 extern
声明(推荐方式),将全局变量定义在一个 .cpp
文件中,在 .h
头文件中用 extern
申明这个变量。
global.h
extern int num;
global.cpp
int num = 10;
3.3、C++17 引入 inline
解决了这个问题
global.h
inline int num = 10;
如果多个 .cpp
文件包含此头文件也不会导致重复定义问题。
四、常考点
4.1、inline
和宏的区别
特性 | inline 函数 | 宏 (#define ) |
---|---|---|
类型检查 | ✅ 支持编译时类型检查 | ❌ 不支持 |
编译调试支持 | ✅ 可调试(保留函数信息) | ❌ 难以调试,报错不明确 |
安全性 | ✅ 参数求值安全 | ❌ 多次求值可能副作用 |
作用域 | ✅ 遵循 C++ 命名空间规则 | ❌ 全局替换 |
语义清晰度 | ✅ 强 | ❌ 易错 |
4.2、C++17 中 inline
关键字添加了什么新的特性
C++17 引入 inline
变量,允许在头文件中定义变量而不会造成链接错误(类似函数)。
4.3、inline
有什么副作用
- 可能增加可执行文件大小;
- 滥用会降低指令缓存命中率;
4.4、inline
可以修饰虚函数嘛
不可以,inline
是直接在调用处展开,但是动态多态无法获知当前调用的究竟是哪个函数,需要根据传入的引用或者指针去判断,这本质上就是冲突的。
但是!但是!但是!作者这里试了一下,可以修饰的,编译可以通过,因为 inline
只是建议,编译器不采用就好了。
4.5、inline
可以修饰构造函数嘛
可以,构造函数是普通成员函数的一种,完全可以被 inline
修饰,而且在类内定义的构造函数也默认是隐式 inline 的。
class MyClass {
public:// 隐式 inline 构造函数MyClass(int x) : value(x) {}// 或显式 inlineinline MyClass(double x) : value(static_cast<int>(x)) {}private:int value;
};
4.6、inline
可以修饰析构函数嘛
析构函数(包括虚析构函数)也是特殊的成员函数,也可以用 inline
修饰。
但是和 inline 可以修饰虚函数嘛
这个问题一样,inline
只是建议罢了。
4.7、内联函数可不可以递归展开
内联函数在语法上可以递归,但在实现上一般不会被递归地内联展开。除非递归深度在编译期是静态可知的(如模板递归)。
下面的代码不会报错:
inline int factorial(int n) {return (n <= 1) ? 1 : n * factorial(n - 1);
}
inline
和递归并不冲突,但是这种不知道递归深度的代码是很危险的,所以编译器可能部分展开,也可能一点都不展开。
下面的代码直接在编译器确定了递归深度,所以可能完全展开
template<int N>
inline int factorial() {return N * factorial<N - 1>();
}