一、什么是內聯函數
- 編譯器嘗試將
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>();
}