1. 為什么設計內聯函數
函數調用是有時間和空間開銷的:調用一個函數之前通常要將實參、局部變量、返回地址以及若干寄存器都壓入棧中,然后才能執行函數體中的代碼,函數體中的代碼執行完畢后還要恢復。
為了消除函數調用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數調用處用函數體替換,這種在函數調用處直接嵌入函數體的函數稱為內聯函數(Inline Function)。內聯函數可以避免函數調用開銷。
2. 定義內聯函數
指定內聯函數的方法很簡單,只需要在函數定義處增加 inline 關鍵字。
void swap(int *, int *); // 函數原型,也可以添加 inline,但編譯器會忽略
int main(){
int m = 10;
int n = 11;
swap(&m, &n); //編譯器可能會將函數代碼直接嵌入此處
}
inline void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
注意:要在函數定義處添加 inline 關鍵字**
由於內聯函數比較短小,我們通常的做法是省略函數原型,將整個函數定義(包括函數頭和函數體)放在本應該提供函數原型的地方。
inline void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main(){
int m = 10;
int n = 11;
swap(&m, &n); //編譯器可能會將函數代碼直接嵌入此處
}
對函數作 inline 聲明只是對編譯器提出的一個建議,不是強制性的,並非一經指定為 inline 編譯器就必須這樣做。編譯器會根據具體情況決定是否這樣做。
3. 內聯函數代替宏
宏是可以帶參數的,它在形式上和函數非常相似。不過不像函數,宏僅僅是字符串替換,不是按值傳遞,所以在編寫宏時要特別注意,一不小心可能就會踩坑。如下所示:
#include <iostream>
using namespace std;
#define SQ(y) y *y
#define SQ2(y) (y)*(y)
#define SQ3(y) ((y)*(y))
int main()
{
int n = 10;
int sq = SQ(n); // 此時宏調用沒問題
int sq2 = SQ(n+1); // 結果錯的:n + 1 * n + 1,可以改進成 SQ2 的形式
int sq3 = 200 / SQ2(n) // 200 / 10 * 10,結果錯的,此時可以改進成 SQ3 的形式
cout << sq << endl;
return 0;
}
宏定義是一項“細思極密”的工作,一不小心就會踩坑,而且不一定在編譯和運行時發現,給程序埋下隱患。
可以將宏換位內聯函數。
#include <iostream>
using namespace std;
inline int SQ(int y) { return y * y; }
int main()
{
int n, sq;
cin >> n;
sq = SQ(n);
cout << sq << endl;
sq = SQ(n + 1);
cout << sq << endl;
sq = 200 / SQ(n + 1);
cout << sq << endl;
return 0;
}
和宏一樣,內聯函數可以定義在頭文件中,並且頭文件被多次#include
后也不會引發重復定義錯誤。這一點和非內聯函數不同,非內聯函數是禁止定義在頭文件中的,它所在的頭文件被多次#include
后會引發重復定義錯誤。
可以看到內聯函數主要有兩個作用,一是消除函數調用時的開銷,二是取代帶參數的宏。
4. 如何規范地使用 C++內聯函數
-
inline 在函數聲明處是無效的,編譯器會忽略函數聲明處的 inline 關鍵字。
-
內聯函數不應該有聲明,應該將函數定義放在本應該出現函數聲明的地方,這是一種良好的編程風格。
-
內聯函數在編譯時會將函數調用處用函數體替換,編譯完成后函數就不存在了。如果將內聯函數的聲明放在頭文件中,定義放在源文件中,這時會出錯,能正常編譯,但無法鏈接。