C++内联函数学习总结
C++中的內聯函數inline總結
http://blog.csdn.net/coder_xia/article/details/6723387
? ? ? 突然看到C++Primer中講到,對于vector的一個循環,調用語句:(示例代碼i=v.begin()不是很規范,雖然不會出錯,客官請將就著看)
for (int i=v.begin() ; i<v.size() ; i++) ?
{ ?
? ? .... ?
} ?
? ? ? ?對于size()的調用,其實是內聯。想到以前貌似是看《高質量C/C++編程》時,提過,在循環時,可以采用變量保存v.size()的值,以減少每個循環的調用開支。于是決定一搜,順便總結之。
1、inline的引出
考慮下列min()函數(例子來自C++Primer第三版303頁)
int min( int v1, int v2 ) ?
{ ?
? ? return( v1 < v2 << v1 : v2 ); ?
} ?
? ? ? 為這樣的小操作定義一個函數的好處是:
? ? ?a.如果一段代碼包含min()的調用,那閱讀這樣的代碼并解釋其含義比讀一個條件操作符的實例,可讀性會強很多。
? ? ?b.改變一個局部化的實現比更改一個應用中的300個出現要容易得多
? ? ?c.語義是統一的,每個測試都能保證相同的方式實現
? ? ?d.函數可以被重用,不必為其他的應用重寫代碼
? ? ?不過,將min()寫成函數有一個嚴重的缺點:調用函數比直接計算條件操作符要慢很多。那怎么能兼顧以上優點和效率呢?C++提供的解決方案為inline(內聯)函數
2、inline的原理:代碼替代
? ? ? ?在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來進行替代。
? ? ? ?例如,如果一個函數被指定為inline 函數則它將在程序中每個調用點上被內聯地展開例如
int minVal2 = min( i, j ); ?
在編譯時被展開為
int minVal2 = i < j << i : j; ?
?則把min()寫成函數的額外執行開銷從而被消除了。
3、inline的使用
? ? ? ?讓一個函數成為內聯函數,隱式的為在類里定義函數,顯式的則是在函數前加上inline關鍵字說明。
4、使用inline的一些注意事項
? ? ? a.從inline的原理,我們可以看出,inline的原理,是用空間換取時間的做法,是以代碼膨脹(復制)為代價,僅僅省去了函數調用的開銷,從而提高函數的執行效率。如果執行函數體內代碼的時間,相比于函數調用的開銷較大,那么效率的收獲會很少。所以,如果函數體代碼過長或者函數體重有循環語句,if語句或switch語句或遞歸時,不宜用內聯
? ? ? b.關鍵字inline 必須與函數定義體放在一起才能使函數成為內聯,僅將inline 放在函數聲明前面不起任何作用。內聯函數調用前必須聲明。《高質量C/C++編程》里一個例子。
inline void Foo(int x, int y); // inline 僅與函數聲明放在一起 ?
void Foo(int x, int y) ?
{ ?
? ? ... ?
} ?
以上代碼不能成為內聯函數,而以下則可以
void Foo(int x, int y); ?
inline void Foo(int x, int y) // inline 與函數定義體放在一起 ?
{ ?
? ? ... ?
} ?
? ? ? ?所以說,inline 是一種“用于實現的關鍵字”,而不是一種“用于聲明的關鍵字”。對于以上例子,林銳還建議,只在定義前加上inline,而不是在聲明和定義前都加,因為這能體現高質量C++/C 程序設計風格的一個基本原則:聲明與定義不可混為一談。
? ? ? ?c.inline對于編譯器來說只是一個建議,編譯器可以選擇忽略該建議。換句話說,哪怕真的寫成了inline,也沒有任何錯誤的情況下,編譯器會自動進行優化。所以當inline中出現了遞歸,循環,或過多代碼時,編譯器自動無視inline聲明,同樣作為普通函數調用。
總結下:
? ? ? ?樓主覺得可以將內聯理解為C++中對于函數專有的宏,對于C的函數宏的一種改進。對于常量宏,C++提供const替代;而對于函數宏,C++提供的方案則是inline。在C中,大家都知道宏的優勢,編譯器通過復制宏代碼的方式,省去了參數壓棧,生成匯編的call調用,返回參數等操作,雖然存在一些安全隱患,但在效率上,還是很可取的。
? ? ? ?不過函數宏還是有不少缺陷的,主要有以下:
? ? ? ?a.在復制代碼時,容易出現一想不到的邊際效應,比如經典的
#define MAX(a, b) (a) > (b) ? (a) : (b) ?
在執行語句:
result = MAX(i, j) + 2 ; ?
時,會被解釋為
result = (i) > (j) ? (i) : (j) + 2 ; ?
? ? ?b.使用宏,無法進行調試,雖然windows提供了ASSERT宏
? ? ?c.使用宏,無法訪問類的私有成員
? ? ? 所以,C++ 通過內聯機制,既具備宏代碼的效率,又增加了安全性,還可以自由操作類的數據成員,算是一個比較完美的解決方案。
? ? ? 關于宏,大家還可以參考下:http://dev.yesky.com/260/2095260.shtml,更原創,內容豐富,可取,http://wenku.baidu.com/view/1247bc22192e45361066f564.html總結了各家觀點,稍微整理了下,不過還是蠻清楚的。
========
c++內聯函數(inline)及內聯函數的使用及注意點
http://blog.csdn.net/wyq_tc25/article/details/51721636 ?
介紹內聯函數之前,有必要介紹一下預處理宏。內聯函數的功能和預處理宏的功能相似。相信大家都用過預處理宏,我們會經常定義一些宏,如
#define TABLE_COMP(x) ((x)>0?(x):0)
就定義了一個宏。
為什么要使用宏呢?因為函數的調用必須要將程序執行的順序轉移到函數所存放在內存中的某個地址,將函數的程序內容執行完后,再返回到轉去執行該函數前的地方。這種轉移操作要求在轉去執行前要保存現場并記憶執行的地址,轉回后要恢復現場,并按原來保存地址繼續執行。因此,函數調用要有一定的時間和空間方面的開銷,于是將影響其效率。而宏只是在預處理的地方把代碼展開,不需要額外的空間和時間方面的開銷,所以調用一個宏比調用一個函數更有效率。
但是宏也有很多的不盡人意的地方。
1、宏不能訪問對象的私有成員。
2、宏的定義很容易產生二意性。
我們舉個例子:
復制代碼 代碼如下:
#define TABLE_MULTI(x) (x*x)
我們用一個數字去調用它,TABLE_MULTI(10),這樣看上去沒有什么錯誤,結果返回100,是正確的,但是如果我們用TABLE_MULTI(10+10)去調用的話,我們期望的結果是400,而宏的調用結果是(10+10*10+10),結果是120,這顯然不是我們要得到的結果。避免這些錯誤的方法,一是給宏的參數都加上括號。
#define TABLE_MULTI(x) ((x)*(x))
這樣可以確保不會出錯,但是,即使使用了這種定義,這個宏依然有可能出錯,例如使用TABLE_MULTI(a++)調用它,他們本意是希望得到(a+1)*(a+1)的結果,而實際上呢?我們可以看看宏的展開結果: (a++)*(a++),如果a的值是4,我們得到的結果是5*6=30。而我們期望的結果是5*5=25,這又出現了問題。事實上,在一些C的庫函數中也有這些問題。例如: Toupper(*pChar++)就會對pChar執行兩次++操作,因為Toupper實際上也是一個宏。
我們可以看到宏有一些難以避免的問題,怎么解決呢?
下面就是用我要介紹的內聯函數來解決這些問題,我們可以使用內聯函數來取代宏的定義。而且事實上我們可以用內聯函數完全取代預處理宏。
內聯函數和宏的區別在于,宏是由預處理器對宏進行替代,而內聯函數是通過編譯器控制來實現的。而且內聯函數是真正的函數,只是在需要用到的時候,內聯函數像宏一樣的展開,所以取消了函數的參數壓棧,減少了調用的開銷。你可以象調用函數一樣來調用內聯函數,而不必擔心會產生于處理宏的一些問題。
我們可以用Inline來定義內聯函數,不過,任何在類的說明部分定義的函數都會被自動的認為是內聯函數。
下面我們來介紹一下內聯函數的用法。
內聯函數必須是和函數體申明在一起,才有效。像這樣的申明Inline Tablefunction(int I)是沒有效果的,編譯器只是把函數作為普通的函數申明,我們必須定義函數體。
Inline tablefunction(int I) {return I*I};
這樣我們才算定義了一個內聯函數。我們可以把它作為一般的函數一樣調用。但是執行速度確比一般函數的執行速度要快。
我們也可以將定義在類的外部的函數定義為內聯函數,比如:
Class TableClass{
Private:
Int I,j;
Public:
Int add() { return I+j;};
Inline int dec() { return I-j;}
Int GetNum();
}
inline int tableclass::GetNum(){
return I;
}
上面申明的三個函數都是內聯函數。在C++中,在類的內部定義了函數體的函數,被默認為是內聯函數。而不管你是否有inline關鍵字。
內聯函數在C++類中,應用最廣的,應該是用來定義存取函數。我們定義的類中一般會把數據成員定義成私有的或者保護的,這樣,外界就不能直接讀寫我們類成員的數據了。對于私有或者保護成員的讀寫就必須使用成員接口函數來進行。如果我們把這些讀寫成員函數定義成內聯函數的話,將會獲得比較好的效率。
Class sample{
Private:
Int nTest;
Public:
Int readtest(){ return nTest;}
Void settest(int I) {nTest=I;}
}
當然,內聯函數也有一定的局限性。就是函數中的執行代碼不能太多了,如果,內聯函數的函數體過大,一般的編譯器會放棄內聯方式,而采用普通的方式調用函數。這樣,內聯函數就和普通函數執行效率一樣了。
以下轉自:http://www.cnblogs.com/xkfz007/archive/2012/03/27/2420166.html
內聯函數并不總是內聯 Inline function是在C++中引入的一種機制,它可以拓展函數代碼,避免調用函數的額外開銷。在Linux環境下,gcc編譯選項必須加上優化選項才能使inline有效。
inline與static的關系
在這兒有一個比較詳細的分析:http://www.cnblogs.com/xkfz007/articles/2370640.html
內聯函數(inline)機制與陷阱
內聯機制被引入C++作為對宏(Macro)機制的改進和補充(不是取代)。內聯函數的參數傳遞機制與普通函數相同。但是編譯器會在每處調用內聯函數的地方將內聯函數的內容展開。這樣既避免了函數調用的開銷又沒有宏機制的前三個缺陷。
但是程序代碼中的關鍵字"inline"只是對編譯器的建議:被"inline"修飾的函數不一定被內聯(但是無"inline"修飾的函數一定不是)。
許多書上都會提到這是因為編譯器比絕大多數程序員都更清楚函數調用的開銷有多大,所以如果編譯器認為調用某函數的開銷相對該函數本身的開銷而言微不足道或者不足以為之承擔代碼膨脹的后果則沒必要內聯該函數。這當然有一定道理,但是按照C、C++一脈相承的賦予程序員充分自由與決定權的風格來看,理由還不夠充分。我猜想最主要的原因是為了避免編譯器陷入無窮遞歸。如果內聯函數之間存在遞歸調用則可能導致編譯器展開內聯函數時陷入無窮遞歸。有時候函數的遞歸調用十分隱蔽,程序員并不容易發現,所以簡單起見,將內聯與否的決定權交給編譯器。
?
另一種不被內聯的情況是使用函數指針來調用內聯函數。
對于C++中內聯機制的一個常見誤解是:關鍵字"inline"只是對編譯器的建議,如果編譯器發現指定的函數不適合內聯就不會內聯;所以即使內聯使用的不恰當也不會有任何副作用。這句話只對了一半,內聯使用不恰當是會有副作用的:會帶來代碼膨脹,還有可能引入難以發現的程序臭蟲。
根據規范,當編譯器認為希望被內聯的函數不適合內聯的時候,編譯器可以不內聯該函數。但是不內聯該函數不代表該函數就是一個普通函數了,從編譯器的實際實現上來講,內聯失敗的函數與普通函數是有區別的:
(1)普通的函數在編譯時被單獨編譯一個對象,包含在相應的目標文件中。目標文件鏈接時,函數調用被鏈接到該對象上。
(2)若一個函數被聲明成內聯函數,編譯器即使遇到該函數的聲明也不會為該函數編譯出一個對象,因為內聯函數是在用到的地方展開的??墒侨粼谡{用該內聯函數的地方發現該內聯函數的不適合展開時怎么辦?一種選擇是在調用該內聯函數的目標文件中為該內聯函數編譯一個對象。這么做的直接后果是:若在多個文件調用了內聯失敗的函數,其中每個文件對應的目標文件中都會包含一份該內聯函數的目標代碼。
如果編譯器真的選擇了上面的做法對待內聯失敗的函數,那么最好的情況是:沒吃到羊肉,反惹了一身騷。即內聯的好處沒享受到,缺點卻承擔了:目標代碼的體積膨脹得與成功內聯的目標代碼一樣,但目標代碼的效率確和沒內聯一樣。
更糟的是由于存在多份函數目標代碼帶來一些程序臭蟲。最明顯的例子是:內聯失敗的函數內的靜態變量實際上就不在只有一份,而是有若干份。這顯然是個錯誤,但是如果不了解內幕就很難找到原因。
========
C++內聯函數(inline)的工作原理與例子
http://blog.csdn.net/buptzhengchaojie/article/details/50568789) ?
? ? ? ?內聯函數(inline function與一般的函數不同,它不是在調用時發生控制轉移,而是在編譯階段將函數體嵌入到每一個調用該函數的語句塊中。內聯函數(inline function)與編譯器的工作息息相關。編譯器會將程序中出現內聯函數的調用表達式用內聯函數的函數體來替換。
內聯函數的優點:
? ? ? ?內聯函數是將程序執行轉移到被調用函數所存放的內存地址,將函數執行完后,在返回到執行此函數前的地方。這種轉移操作需要保護現場、包括進棧等操作,在被調用函數代碼執行完后,再恢復現場。但是保護現場和恢復現場需要較大的資源開銷。對于一些較小的調用函數來說,若是頻繁調用,函數調用過程甚至可能比函數執行過程需要的系統資源更多。所以引入內聯函數,可以讓程序執行效率更高。
內聯函數的缺點:
? ? ? ?如果調用內聯函數的地方過多,也可能造成代碼膨脹。因為編譯器會把內聯函數的函數體嵌入到每一個調用了它的地方,重復地嵌入。
以下是幾種含有內聯函數的情況:
一、 在類中使用內聯函數和不使用內聯函數
1、
?
2、
3、
?
二、普通函數的調用內聯函數
?
#include<iostream> ?
using namespace std; ?
inline int max(int i, int j, int k) { ?
? ? if (i<j) ?
? ? { ?
? ? ? ? i = j; ?
? ? } ?
? ? if (i < k) { ?
? ? ? ? i = k; ?
? ? } ?
? ? return i; ?
} ?
int main() { ?
? ? int i = 1, j = 2, k = 3; ?
? ? int bigest = max(i, j, k); ?
? ? cout << bigest << endl; ?
? ? return 0; ?
}?
========
C++如何處理內聯虛函數
http://www.cnblogs.com/jingzhishen/p/4199931.html
當一個函數是內聯和虛函數時,會發生代碼替換或使用虛表調用嗎? 為了弄清楚內聯和虛函數,讓我們將它們分開來考慮。通常,一個內聯函數是被展開的。
? ? ? ? class CFoo {
? ? ? ? private:
? ? ? ? ? ? int val;
? ? ? ? public:
? ? ? ? int GetVal() { return val; }
? ? ? ? int SetVal(int v) { return val=v; }
? ? ? ? }; ? ? ? ?
這里,如果使用下列代碼:
? ? ? ? CFoo x;
? ? ? ? x.SetVal(17);
? ? ? ? int y = x.GetVal(); ?
那么編譯器產生的目標代碼將與下面的代碼段一樣:
? ? ? ? CFoo x;
? ? ? ? x.val = 17;
? ? ? ? int y = x.val; ?
? ? 你當然不能這么做,因為val是個私有變量。內聯函數的優點是不用函數調用就能隱藏數據,僅此而已。
? ? 虛函數有多態性,意味著派生的類能實現相同的函數,但功能卻不同。假設 GetVal 被聲明為虛函數,并且你有第二個 以不同方法實現的類 CFoo2:
? ? ? ? class CFoo2 : public CFoo {
? ? ? ? public:?
? ? ? ? // virtual in base class too!
? ? ? ? virtual int CFoo2::GetVal() { return someOtherVal; }?
? ? ? ? };
? ? 如果 pFoo是一個 CFoo 或 CFoo2 指針,那么,無論 pFoo 指向哪個類 CFoo 或 CFoo2,成員函數 pFoo->GetVal 都能調用成功。
? ? 如果一個函數既是虛擬函數,又是內聯函數,會是什么情況呢?記住,有兩種方式建立內聯函數,
第一種是在函數定義中使用關鍵字 inline,如:
? ? ? ? inline CFoo::GetVal() { return val; } ? ? ? ?
第二種是在類的聲明中編寫函數體,就象前面的 CFoo2::GetVal 一樣。所以如果將虛函數體包含在類的聲明中,如:
? ? ? ? class CFoo {
? ? ? ? public:
? ? ? ? virtual int GetVal() { return val; }
? ? ? ? }; ? ? ? ?
? ? 編譯器便認為這個函數 GetVal 是內聯的,同時也是虛擬的。那么,多態性和內聯特性如何同時工作呢?
? ? 編譯器遵循的第一個規則是無論發生什么事情,多態性必須起作用。如果有一個指向 CFoo 對象的指針,pFoo->GetVal 被保證去調用正確的函數。一般情況下,這就是說函數 GetVal 將被實例化為非內聯函數,并有vtable(虛表)入口指向它們。但這并不意味著這個函數不能被擴展!再看看下面的代碼:
? ? ? ? CFoo x;?
? ? ? ? x.SetVal(17)
? ? ? ? int y = x.GetVal() ?
? ? 編譯器知道x是 CFoo,而不是CFoo2,因為這個堆對象是被顯式聲明的。x肯定不會是CFoo2。所以展開 SetVal/GetVal 內聯是安全的。如果要寫更多的復雜代碼:
CFoo x;?
CFoo* pfoo=&x;?
pfoo->SetVal(17);?
int y = pfoo->GetVal();?
...
CFoo2 x2;?
pfoo = &x2;?
pfoo->SetVal(17); //etc.?
編譯器知道 pfoo 第一次指向x,第二次指向x2,所以展開虛擬函數也是安全的。
? ? 你還可以編寫更復雜的代碼,其中,pfoo 所指的對象類型總是透明的,但是大多數編譯器不會做任何更多的分析。即使在前面的例子中,某些編譯器將會安全運行,實例化并通過一個虛表來調用。實際上, 編譯器總是忽略內聯需要并總是使用虛表。唯一絕對的規則是代碼必須工作;也就是說,虛函數必須有多態行為。
? ? 通常,無論是顯式還是隱式內聯,它只是一個提示而已,并非是必須的,就象寄存器一樣。編譯器完全能拒絕展開一個非虛內聯函數,C++編譯器常常首先會報 錯:“內聯中斷-函數太大”。如果內聯函數調用自身,或者你在某處傳遞其地址,編譯器必須產生一個正常(外聯?)函數。內聯函數在DEBUG BUILDS中不被展開,可設置編譯選項來預防。
? ? 要想知道編譯器正在做什么,唯一的方法是看它產生的代碼。對于微軟的編譯器來說,你可以用-FA編譯選項產生匯編清單。你不必知道匯編程序如何做。我鼓勵 你完成這個實驗;這對于了解機器實際所做的事情機器有益,同時你可學習許多匯編列表中的內容。
? ? 有關內聯函數的東西比你第一次接觸它時要復雜得多。有許多種情況強迫編譯器產生正常函數:遞歸,獲取函數地址,太大的那些函數和虛函數。但是如果編譯器決定實例化你的內聯函數,就要考慮把函數放在什么地方?它進入哪個模塊?
? ? 通常類在頭文件中聲明,所以如果某個cpp包含foo.h,并且編譯器決定實例化CFoo::GetVal,則在cpp文件中將它實例化成一個靜態函數。 如果十個模塊包含foo.h,編譯器產生的虛函數拷貝就有十個。實際上,可以用虛表指向不同類型的GetVal拷貝,從而是相同類型的對象只產生拷貝。一 些鏈接器能巧妙地在鏈接時排除冗余,但一般你是不能指望他來保證的。
? ? 我們得出的結論是:最好不要使用內聯虛函數,因為它們幾乎不會被展開,即便你的函數只有一行,你最好還是將它與其它的類函數一起放在模塊(cpp文件) 中。當然,開發者常常將簡短的虛函數放在類聲明中-不是因為他們希望這個函數被展開為內聯,而是因為這樣做更方便和可讀性更強。
=======
C++內聯函數跟普通函數的區別以及實現機制
http://blog.csdn.net/xiaofei2010/article/details/7609355
內聯函數定義:
將函數定義為內聯函數,一般就是將他在程序中每個調用點上“內聯地”展開。在函數返回類型前加上inline關鍵字。
定義在類聲明之中的成員函數將自動地成為內聯函數。
內聯函數適用情況:
1.一個函數被重復調用;
2.函數只有幾行,且不包含for,while,switch語句。
內聯函數應該放在頭文件中定義,這一點不同于其他函數。
內聯函數可能在程序中定義不止一次,只要內聯函數的定義在某個源文件中只出現一次,而且在所有的源文件中,其定義必須是相同的。如果inline函數的定義和聲明是分開的,而在另外一個文件中需要調用這些inline函數得時候,內聯是無法在這些調用函數內展開的。這樣內聯函數在全局范圍內就失去了作用。解決的辦法就是把內聯函數得定義放在頭文件中,當其它文件要調用這些內聯函數的時候,只要包含這個頭文件就可以了。把內聯函數的定義放在頭文件中,可以確保在調用函數時所使用的定義是相同的,并保證在調用點該函數的定義對調用點可見。
內聯函數具有一般函數的特性,它與一般函數所不同之處只在于函數調用的處理:
一般函數進行調用時,要將程序執行權轉到被調用函數中,然后再返回到調用它的函數中;而內聯函數在調用時,是將調用表達式用內聯函數體來替換。
========
總結
以上是生活随笔為你收集整理的C++内联函数学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#内存映射文件学习总结
- 下一篇: C++ 通讯录学习总结