面向對象編程語言(C++)複習筆記(2)——函數內聯,重載,模板

(一)內聯函數:

引入內聯函數的目的是爲了解決程序中函數調用的效率問題,這麼說吧,程序在編譯器編譯的時候,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體進行替換,而對於其他的函數,都是在運行時候才被替代。這其實就是個空間代價換時間的i節省。所以內聯函數一般都是1-5行的小函數。在使用內聯函數時要留神:

  1. 在內聯函數內不允許使用循環語句和開關語句;
  2. 內聯函數的定義必須出現在內聯函數第一次調用之前;
  3. 類結構中所在的類說明內部定義的函數是內聯函數。
  • Tip: 只有當函數只有 10 行甚至更少時纔將其定義爲內聯函數.
  • 定義: 當函數被聲明爲內聯函數之後, 編譯器會將其內聯展開, 而不是按通常的函數調用機制進行調用.
  • 優點: 當函數體比較小的時候, 內聯該函數可以令目標代碼更加高效. 對於存取函數以及其它函數體比較短, 性能關鍵的函數, 鼓勵使用內聯.
  • 缺點: 濫用內聯將導致程序變慢. 內聯可能使目標代碼量或增或減, 這取決於內聯函數的大小. 內聯非常短小的存取函數通常會減少代碼大小, 但內聯一個相當大的函數將戲劇性的增加代碼大小. 現代處理器由於更好的利用了指令緩存, 小巧的代碼往往執行更快。
  • 結論: 一個較爲合理的經驗準則是, 不要內聯超過 10 行的函數. 謹慎對待析構函數, 析構函數往往比其表面看起來要更長, 因爲有隱含的成員和基類析構函數被調用!

另一個實用的經驗準則:

內聯那些包含循環或 switch 語句的函數常常是得不償失 (除非在大多數情況下, 這些循環或 switch 語句從不被執行).
有些函數即使聲明爲內聯的也不一定會被編譯器內聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常內聯. 通常, 遞歸函數不應該聲明成內聯函數.(遞歸調用堆棧的展開並不像循環那麼簡單, 比如遞歸層數在編譯時可能是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要原因則是想把它的函數體放在類定義內, 爲了圖個方便, 抑或是當作文檔描述其行爲, 比如精短的存取函數.


(二)函數重載:

C++ 允許多個函數擁有相同的名字,只要它們的參數列表不同就可以,這就是函數的重載(Function Overloading)。藉助重載,一個函數名可以有多種用途。

參數列表又叫參數簽名,包括參數的類型、參數的個數和參數的順序,只要有一個不同就叫做參數列表不同。

重載就是在一個作用範圍內(同一個類、同一個命名空間等)有多個名稱相同但參數不同的函數。重載的結果是讓一個函數名擁有了多種用途,使得命名更加方便(在中大型項目中,給變量、函數、類起名字是一件讓人苦惱的問題),調用更加靈活。

在使用重載函數時,同名函數的功能應當相同或相近,不要用同一函數名去實現完全不相干的功能,雖然程序也能運行,但可讀性不好,使人覺得莫名其妙。

注意,參數列表不同包括參數的個數不同、類型不同或順序不同,僅僅參數名稱不同是不可以的。函數返回值也不能作爲重載的依據。

函數的重載的規則:

  • 函數名稱必須相同
  • 參數列表必須不同(個數不同、類型不同、參數排列順序不同等)。
  • 函數的返回類型可以相同也可以不相同。
  • 僅僅返回類型不同不足以成爲函數的重載。

C++ 是如何做到函數重載的

C++代碼在編譯時會根據參數列表對函數進行重命名,例如void Swap(int a, int b)會被重命名爲_Swap_int_int,void Swap(float x, float y)會被重命名爲_Swap_float_float。當發生函數調用時,編譯器會根據傳入的實參去逐個匹配,以選擇對應的函數,如果匹配失敗,編譯器就會報錯,這叫做重載決議(Overload Resolution)。
不同的編譯器有不同的重命名方式,這裏僅僅舉例說明,實際情況可能並非如此。
從這個角度講,函數重載僅僅是語法層面的,本質上它們還是不同的函數,佔用不同的內存,入口地址也不一樣

實例:

//交換 int 變量的值
void Swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

//交換 float 變量的值
void Swap(float *a, float *b){
    float temp = *a;
    *a = *b;
    *b = temp;
}

 函數重載,這些函數雖然在調用時方便了一些,但從本質上說還是定義了多個個功能相同、函數體相同的函數,只是數據的類型不同而已,這看起來有點浪費代碼,能不能把它們壓縮成一個函數呢?

  • 可以,這就是下面的函數模板

函數模板:

我們知道,數據的值可以通過函數參數傳遞,在函數定義時數據的值是未知的,只有等到函數調用時接收了實參才能確定其值。這就是值的參數化。

在C++中,數據的類型也可以通過參數來傳遞,在函數定義時可以不指明具體的數據類型,當發生函數調用時,編譯器可以根據傳入的實參自動推斷數據類型。這就是類型的參數化。

值(Value)和類型(Type)是數據的兩個主要特徵,它們在C++中都可以被參數化。

所謂函數模板,實際上是建立一個通用函數,它所用到的數據的類型(包括返回值類型、形參類型、局部變量類型)可以不具體指定,而是用一個虛擬的類型來代替(實際上是用一個標識符來佔位),等發生函數調用時再根據傳入的實參來逆推出真正的類型。這個通用函數就稱爲函數模板(Function Template)

在函數模板中,數據的值和類型都被參數化了,發生函數調用時編譯器會根據傳入的實參來推演形參的值和類型。換個角度說,函數模板除了支持值的參數化,還支持類型的參數化。

一但定義了函數模板,就可以將類型參數用於函數定義和函數聲明瞭。說得直白一點,原來使用 int、float、char 等內置類型的地方,都可以用類型參數來代替。

#include <iostream>
using namespace std;

template<typename T> void Swap(T *a, T *b){
    T temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    //交換 int 變量的值
    int n1 = 100, n2 = 200;
    Swap(&n1, &n2);
    cout<<n1<<", "<<n2<<endl;
   
    //交換 float 變量的值
    float f1 = 12.5, f2 = 56.93;
    Swap(&f1, &f2);
    cout<<f1<<", "<<f2<<endl;
   
    return 0;
}

template是定義函數模板的關鍵字,它後面緊跟尖括號<>,尖括號包圍的是類型參數(也可以說是虛擬的類型,或者說是類型佔位符)。typename是另外一個關鍵字,用來聲明具體的類型參數,這裏的類型參數就是T。從整體上看,template<typename T>被稱爲模板頭。

模板頭中包含的類型參數可以用在函數定義的各個位置,包括返回值、形參列表和函數體;本例我們在形參列表和函數體中使用了類型參數T。

類型參數的命名規則跟其他標識符的命名規則一樣,不過使用 T、T1、T2、Type 等已經成爲了一種慣例。

定義了函數模板後,就可以像調用普通函數一樣來調用它們了。

語法:

下面我們來總結一下定義模板函數的語法:
template <typename 類型參數1 , typename 類型參數2 , ...> 返回值類型  函數名(形參列表){
    //在函數體中可以使用類型參數
}

類型參數可以有多個,它們之間以逗號,分隔。類型參數列表以< >包圍,形式參數列表以( )包圍。

typename關鍵字也可以使用class關鍵字替代,它們沒有任何區別。C++ 早期對模板的支持並不嚴謹,沒有引入新的關鍵字,而是用 class 來指明類型參數,但是 class 關鍵字本來已經用在類的定義中了,這樣做顯得不太友好,所以後來 C++ 又引入了一個新的關鍵字 typename,專門用來定義類型參數。不過至今仍然有很多代碼在使用 class 關鍵字,包括 C++ 標準庫、一些開源程序等。

函數模板也可以提前聲明,不過聲明時需要帶上模板頭,並且模板頭和函數定義(聲明)是一個不可分割的整體,它們可以換行,但中間不能有分號。

#include <iostream>
using namespace std;
//聲明函數模板
template<typename T> T max(T a, T b, T c);
int main( ){
    //求三個整數的最大值
    int i1, i2, i3, i_max;
    cin >> i1 >> i2 >> i3;
    i_max = max(i1,i2,i3);
    cout << "i_max=" << i_max << endl;
    //求三個浮點數的最大值
    double d1, d2, d3, d_max;
    cin >> d1 >> d2 >> d3;
    d_max = max(d1,d2,d3);
    cout << "d_max=" << d_max << endl;
    //求三個長整型數的最大值
    long g1, g2, g3, g_max;
    cin >> g1 >> g2 >> g3;
    g_max = max(g1,g2,g3);
    cout << "g_max=" << g_max << endl;
    return 0;
}
//定義函數模板
template<typename T>  //模板頭,這裏不能有分號
T max(T a, T b, T c){ //函數頭
    T max_num = a;
    if(b > max_num) max_num = b;
    if(c > max_num) max_num = c;
    return max_num;
}

 

文章引用:

http://c.biancheng.net/cpp/biancheng/view/136.html

https://www.runoob.com/cplusplus/cpp-inline-functions.html

https://blog.csdn.net/zhanghow/article/details/53588458

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章