inline內聯的用法與作用

pan_jinquan發表於2016-11-12

inline內聯的用法與作用

     行內函數是一種編譯機制,優點從程式碼上是看不出來的,但是程式的執行效率上有差別,通常,編譯器對函式呼叫的處理是一種類似中斷的方式,即當執行到函式呼叫語句時,程式把當前所有的狀態資訊比如CPU所有暫存器(其中一個很重要的就是指令指標暫存器)的值儲存起來,然後放心大膽地轉去執行那個函式的程式碼,執行完後再返回原來的地方,恢復原先儲存過的狀態資訊,於是也就可以接著原來被中斷的指令繼續往下執行。這樣,就很容易實現程式碼的結構化,因為可以把一些獨立的功能模組寫成函式,函式內部的變數和外部的變數互不影響,而且函式執行完後就可以釋放這個函式內部變數的所使用的記憶體空間(這就是為什麼函式退出後,其內部變數不再有效),對記憶體的使用也是很經濟的(否則,如果一個大的程式全部由一個函式組成,那麼所有的變數都得自始至終地佔用記憶體空間),當然,還有其他優點,比如可以實現遞迴,總之是好處多多。  
     可是,任何事情往往都有兩方面,這樣做雖然好處多多,但也是有代價的,那就是前面所說的,任何一次函式呼叫,程式都得進行儲存和恢復狀態資訊的動作,用資料結構的術語說就是進棧和退棧,當然,還有記憶體分配的過程,如果函式的程式碼非常少,這種代價並不是可忽略的,比如說,你編寫一個類,裡面有個記錄狀態的成員變數:  
Class MyClass  
{  
private:  
int m_iState;  
}  
    按照物件導向的思想,函式的屬性應儘量的私有化,但外部怎麼獲得這個屬性值呢?一般的方法就是加一個共有函式,這就實現的物件導向思想中所謂“通過公用介面操作物件的私有屬性”。於是就變成了:  
Class MyClass  
{  
 public:  
 int GetState();  
 private:  
 int m_iState;  
}  

int MyClass::GetState()  
{  
 return m_iState;  
}  
    這樣一來,物件導向思想倒是體現出來了,但你的CPU會恨你:“你丫的,一個鳥樣小的函式就返回一個整數卻讓老子進一次棧、彈一次棧”,記憶體也會憋屈的說:“兄弟,老子也得跟著分配記憶體啊!”  
    但對你來說,也很委屈,怎麼辦,把所有的屬性都改成public?讓外部內碼直接訪問?況且,那樣也不解決所有問題,因為有時候即使不是為了物件導向,我們也需要把獨立的功能模組做成函式,比如說產生隨機數的函式。我想  
int iRand=rand();  
總比:  
int iRand=((int)(MULTIPLIER * Seed + INCREMENT)>>16)&0x7fff;  
    看起來舒服吧?(我這裡只是打個比方,VC的rand函式並不是行內函數)  
    而行內函數就是解決這個問題了,對於程式設計師,他還是把獨立功能寫成函式的形式,但只要宣告為內聯,編譯器就不把它編譯成一次函式呼叫,而只是類似於把函式的程式碼拷貝到被呼叫的地方,而且這完全是編譯器私下裡完成的,原來的訪問許可權等問題絲毫不受影響。這不是兩全齊美了嗎:在保證程式碼的物件導向性和結構化不受損失的條件下,程式的效率也沒有損失,比如上面那個類,就變成了:  
Class MyClass  
{  
 public:  
 inline int GetState();  
 private:  
 int m_iState;  
}  
int inline MyClass::GetState()  
{  
 return m_iState;  
}  
    有一點要注意,行內函數要跟類的宣告寫在同一個檔案中,否則編譯會出錯。按照VC管理原始檔的風格來說,就是行內函數最好寫在宣告類的.h檔案中,而不是像一般函式那樣寫在實現類的.cpp檔案中。  
    當然,行內函數還有另外一種寫法,就是直接寫在類中,此時,不必使用“inline”關鍵字。  
Class MyClass  
{  
public:  
int GetState(){ return m_iState; }  
private:  
int m_iState;  
}  
    最後,還要注意,行內函數只是一種編譯機制,用上面兩種形式宣告的函式僅僅是建議編譯器進行內聯,而編譯器是否內聯不一定。正如前面所說,函式呼叫的開銷只是對小的函式不可忽略,對於重量級的函式還是可以忽略的,而且在絕大多數的場合,函式呼叫才是人間正道,才是解決問題的最佳。所以大多數編譯器並不把帶有迴圈、遞迴等或者程式碼比較多的函式進行內聯編譯,有的甚至不允許宣告成內聯的。    
    在解決C + +中巨集存取私有的類成員的問題過程中,所有和前處理器巨集有關的問題也隨著消失了。這是通過使巨集被編譯器控制來實現的。在 C + +中,巨集的概念是作為行內函數來實現的,而行內函數無論在任何意義上都是真正的函式。唯一不同之處是行內函數在適當時像巨集一樣展開,所以函式呼叫的開銷被取消。因此,應該永遠不使用巨集,只使用行內函數。  
    任何在類中定義的函式自動地成為行內函數,但也可以使用i n l i n e關鍵字放在類外定義的函式前面使之成為行內函數。但為了使之有效,必須使函式體和宣告結合在一起,否則,編譯器將它作為普通函式對待。因此  
inline int PlusOne(int x);  
    沒有任何效果,僅僅只是宣告函式(這不一定能夠在稍後某個時候得到一個內聯定義)。成功的方法如下:  
inline int PlusOne(int x) { return ++x ;}  
    注意,編譯器將檢查函式引數列表使用是否正確,並返回值(進行必要的轉換)。這些事情是前處理器無法完成的。假如對於上面的行內函數,我們寫成一個前處理器巨集的話,將有不想要的副作用。  
    一般應該把內聯定義放在標頭檔案裡。當編譯器看到這個定義時,它把函式型別(函式名+  返回值)和函式體放到符號表裡。當使用函式時,編譯器檢查以確保呼叫是正確的且返回值被  正確使用,然後將函式呼叫替換為函式體,因而消除了開銷。內聯程式碼的確佔用空間,但假如函式較小,這實際上比為了一個普通函式呼叫而產生的程式碼(引數壓棧和執行C A L L)佔用的空間還少。在標頭檔案裡,行內函數預設為內部連線——即它是static, 並且只能在它被包含的編譯單元看到。因而,只要它們不在相同的編譯單元中宣告,在行內函數和全域性函式之間用同樣的名字也不會在連線時產生衝突。  
    為了定義行內函數,通常必須在函式定義前面放一個i n l i n e關鍵字。但這在類內部定義行內函數時並不是必須的。任何在類內部定義的函式自動地為行內函數。如下例:  
#include <iostream.h>  
class point{  
private:  
int i,j,k;  
public:  
point() {i=j=k=0; }  
point(int I,int J,int K) {  
i=I;  
j=J  
k=K;  
}  
void print(const char* msg="") const{  
if(*msg) cout<<"msg"<<endl;  
cout<<"i="<<i<<endl;  
cout<<"j="<<j<<endl;  
cout<<"k="<<k<<endl;  
}  
};  

main(){  
point p,q(1,2,3);  
p.print("value of p");  
q.print("value of q");  
}  
    當然,因為類內部的行內函數節省了在外部定義成員函式的額外步驟,所以我們一定想在類宣告內每一處都使用行內函數。但應記住,內聯的目的是減少函式呼叫的開銷。假如函式較大,那麼花費在函式體內的時間相對於進出函式的時間的比例就會較大,所以收穫會較小。而且內聯一個大函式將會使該函式所有被呼叫的地方都做程式碼複製,結果程式碼膨脹而在速度方面獲得的好處卻很少或者沒有。

相關文章