1. 程式人生 > >c++: 行內函數

c++: 行內函數

我們知道,函式封裝呼叫有利於程式碼的重複利用,因為我們可以函式起一個通俗易懂的名字,因此閱讀和理解函式通常比讀懂等價的條件表示式容易的多。

然而函式相較於等價表示式執行速度要慢一些,因為在大多數機器上,一次函式呼叫意味著其實包含一系列的工作:呼叫前先儲存暫存器,並在返回時恢復;可能需要拷貝實參;程式轉向一個新的位置繼續執行;

行內函數的出現就是為了避免不必要的函式呼叫的開銷,將函式定義為行內函數,通常將它在每個呼叫點上“內聯”展開。

首先我們先熟悉下函式呼叫的原理

1. 函式呼叫的原理

編譯過程的最終產品是可執行程式(由一組機器語言指令組成)。執行程式時,作業系統將這些指令載入計算機記憶體中,因此每條指令都有特定的記憶體地址。計算機隨後將逐步執行這些指令。有時(如有迴圈和分支語句時),將跳過一些指令,向前或向後跳到特定地址。

常規函式呼叫也使程式跳到另一個地址(函式的地址),並在函式結束時返回。執行到函式呼叫指令時,程式將在函式呼叫後立即儲存該指令的記憶體地址,並將函式引數複製到堆疊(為此保留的記憶體塊),跳到標記函式起點的記憶體單元,執行函式程式碼(也許還需將返回值放入暫存器中),然後跳回到地址被儲存的指令處(這與閱讀文章時停下來看腳註,並在閱讀完腳註後返回到以前閱讀的地方類似)。來回跳躍並記錄跳躍位置意味著以前使用函式時,需要一定的開銷。

2. 行內函數

行內函數提供了另一種選擇。編譯器將使用相應的函式程式碼替換函式呼叫。因此,行內函數的執行速度比常規函式稍快,但代價是需要佔用更多記憶體。

函式使用直接在函式宣告或定義前加 inline 即可

inline const string& shortString(...)
{
    ...
}

示例如下:

#include<iostream>

inline double square(double x){return x*x;}

int main()
{
    using namespace std;
    double a,b;
    double c = 13.0;
    
    a = square(5.0);
    b = square(4.5 + 7.5);
    cout << "a=" << a << ",b=" << b << endl;
    cout << "c=" << c << endl;
    cout << "c squared=" << square(c++) << endl;
    cout << "now c=" << c << endl;
    
    return 0;
}

程式輸出結果如下:

a=25,b=144
c=13
c square=169
now c=14

3. 行內函數與巨集定義的區別

C 語言使用前處理器語句 #define 來提供巨集,與行內函數的使用比較像,下面具體說明下兩者的區別。

如下例所示:

#define SQUARE(X) X*X

巨集定義時通過文字替換開實現的–X 是引數的符號標記。

a = square(5.0);  // -> a=5.0*5.0;
b = square(4.5+7.5); // -> b=4.5+7.5*4.5+7.5
d = square(c++); // -> d=c++*c++

可以看出,對於 b,需要使用括號才能正常運算。

#define SQUARE(X) ((X)*(X))

對於 c,卻仍遞增了兩次。

因此,巨集定義和行內函數存在本質的區別,轉換的時候應考慮是否轉換後功能是否正常。

4. 什麼時候使用行內函數

  1. 如果函式執行程式碼的時間很短,優於處理函式呼叫的時間,則行內函數就能體現作用。因此,一個較為合理的經驗準則是, 不要內聯超過 10 行的函式。
  2. 內聯那些包含迴圈或 switch 語句的函式常常是得不償失 (除非在大多數情況下, 這些迴圈或 switch 語句從不被執行)。
  3. 行內函數定義在標頭檔案中,不要放在 cpp 檔案中