C++ inline內聯函數詳解
函數是一個可以重復使用的代碼塊,CPU 會一條一條地挨著執行其中的代碼。CPU 在執行主調函數代碼時如果遇到瞭被調函數,主調函數就會暫停,CPU 轉而執行被調函數的代碼;被調函數執行完畢後再返回到主調函數,主調函數根據剛才的狀態繼續往下執行。
一個 C/C++ 程序的執行過程可以認為是多個函數之間的相互調用過程,它們形成瞭一個或簡單或復雜的調用鏈條,這個鏈條的起點是 main(),終點也是 main()。當 main() 調用完瞭所有的函數,它會返回一個值(例如return 0;)來結束自己的生命,從而結束整個程序。
函數調用是有時間和空間開銷的。程序在執行一個函數之前需要做一些準備工作,要將實參、局部變量、返回地址以及若幹寄存器都壓入棧中,然後才能執行函數體中的代碼;函數體中的代碼執行完畢後還要清理現場,將之前壓入棧中的數據都出棧,才能接著執行函數調用位置以後的代碼。關於函數調用的細節,我們已經在《C語言內存精講》一章中的《一個函數在棧上到底是怎樣的》《用一個實例來深入剖析函數進棧出棧的過程》兩節中講到。
如果函數體代碼比較多,需要較長的執行時間,那麼函數調用機制占用的時間可以忽略;如果函數隻有一兩條語句,那麼大部分的時間都會花費在函數調用機制上,這種時間開銷就就不容忽視。
為瞭消除函數調用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數調用處用函數體替換,類似於C語言中的宏展開。這種在函數調用處直接嵌入函數體的函數稱為內聯函數(Inline Function),又稱內嵌函數或者內置函數。
指定內聯函數的方法很簡單,隻需要在函數定義處增加 inline 關鍵字。請看下面的例子:
#include using namespace std; //內聯函數,交換兩個數的值 inline void swap(int *a, int *b){ int temp; temp = *a; *a = *b; *b = temp; } int main(){ int m, n; cin>>m>>n; cout<<m<<", “<<n<<endl; swap(&m, &n); cout<<m<<”, "<<n<<endl; return 0; }
運行結果:
45 99↙ 45, 99 99, 45
註意,要在函數定義處添加 inline 關鍵字,在函數聲明處添加 inline 關鍵字雖然沒有錯,但這種做法是無效的,編譯器會忽略函數聲明處的 inline 關鍵字。
當編譯器遇到函數調用swap(&m, &n)時,會用 swap() 函數的代碼替換swap(&m, &n),同時用實參代替形參。這樣,程序第 16 行就被置換成:
int temp;
temp = *(&m);
*(&m) = *(&n);
*(&n) = temp;
編譯器可能會將 (&m)、(&n) 分別優化為 m、n。
當函數比較復雜時,函數調用的時空開銷可以忽略,大部分的 CPU 時間都會花費在執行函數體代碼上,所以我們一般是將非常短小的函數聲明為內聯函數。
由於內聯函數比較短小,我們通常的做法是省略函數原型,將整個函數定義(包括函數頭和函數體)放在本應該提供函數原型的地方。下面的例子是一個反面教材,這樣的寫法是不被推薦的:
#include using namespace std; //聲明內聯函數 void swap1(int *a, int *b); //也可以添加inline,但編譯器會忽略 int main(){ int m, n; cin>>m>>n; cout<<m<<", “<<n<<endl; swap1(&m, &n); cout<<m<<”, "<<n<<endl; return 0; } //定義內聯函數 inline void swap1(int *a, int *b){ int temp; temp = *a; *a = *b; *b = temp; }
使用內聯函數的缺點也是非常明顯的,編譯後的程序會存在多份相同的函數拷貝,如果被聲明為內聯函數的函數體非常大,那麼編譯後的程序體積也將會變得很大,所以再次強調,一般隻將那些短小的、頻繁調用的函數聲明為內聯函數。
最後需要說明的是,對函數作 inline 聲明隻是程序員對編譯器提出的一個建議,而不是強制性的,並非一經指定為 inline 編譯器就必須這樣做。編譯器有自己的判斷能力,它會根據具體情況決定是否這樣做。
到此這篇關於C++ inline內聯函數詳解的文章就介紹到這瞭,更多相關C++ inline內聯函數內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!