C++類別別模板(模板類)詳解

2020-07-16 10:04:22
人們需要編寫多個形式和功能都相似的函數,因此有了函數模板來減少重複勞動;人們也需要編寫多個形式和功能都相似的類,於是 C++ 引人了類別範本的概念,編譯器從類別範本可以自動生成多個類,避免了程式設計師的重複勞動。

例如,在《C++運算子過載》一章中的《C++實現可變長度的動態陣列》一節中,我們實現了一個可變長的整型陣列類,可能還需要可變長的 double 陣列類,可變長的 CStudent 陣列類,等等。如果要把類似於可變長整型陣列類的程式碼都重寫一遍,無疑非常麻煩。有了類別範本的機制,只需要寫一個可變長的陣列類別範本,編譯器就會由該類別範本自動生成整型、double 型等各種型別的可變長陣列類了。

C++ 中類別範本的寫法如下:

template <型別參數列>
class 類別範本名{
    成員函數和成員變數
};

型別參數列的寫法如下:

class類塑引數1, class型別引數2, ...

類別範本中的成員函數放到類別範本定義外面寫時的語法如下:

template <型別參數列>
返回值型別  類別範本名<型別引數名列表>::成員函數名(參數列)
{
    ...
}

用類別範本定義物件的寫法如下:

類別範本名<真實型別參數列> 物件名(建構函式實際參數列);

如果類別範本有無參建構函式,那麼也可以使用如下寫法:

類別範本名 <真實型別參數列> 物件名;

類別範本看上去很像一個類。下面以 Pair 類別範本為例來說明類別範本的寫法和用法。

實踐中常常會碰到,某項資料記錄由兩部分組成,一部分是關鍵字,另一部分是值。關鍵字用來對記錄進行排序和檢索,根據關鍵字能查到值。例如,學生記錄由兩部分組成,一部分是學號,另一部分是績點。要能根據學號對學生進行排序,以便方便地檢索績點,則學號就是關鍵字,績點就是值。

下面的Pair類別範本就可用來處理這樣的資料記錄:
#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Pair
{
public:
    T1 key;  //關鍵字
    T2 value;  //值
    Pair(T1 k,T2 v):key(k),value(v) { };
    bool operator < (const Pair<T1,T2> & p) const;
};
template<class T1,class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> & p) const
//Pair的成員函數 operator <
{ //"小"的意思就是關鍵字小
    return key < p.key;
}
int main()
{
    Pair<string,int> student("Tom",19); //範例化出一個類 Pair<string,int>
    cout << student.key << " " << student.value;
    return 0;
}
程式的輸出結果是:
Tom 19

範例化一個類別範本時,如第 21 行,真實型別參數列中的引數是具體的型別名,如 string、int 或其他類的名字(如 CStudent)等,它們用來一一對應地替換類別範本定義中“型別參數列”中的型別引數。類別範本名 <真實型別參數列>就成為一個具體的類的名字。

編譯器編譯到第 21 行時,就會用 string 替換 Pair 模板中的 T1,用 int 替換 T2,其餘部分原樣保留,這樣就自動生成了一個新的類。這個類的名字編譯器是如何處理的不需要知道,可以認為它的名字就是 Pair <string, int>。也可以說,student 物件的型別就是 Pair<string, int>。

Pair<string, int> 類的成員函數自然也是通過替換 Pair 模板的成員函數中的 T1、T2 得到的。

編譯器由類別範本生成類的過程叫類別範本的範例化。由類別範本範例化得到的類叫模板類。

函數模板作為類別範本成員

類別範本中的成員函數還可以是一個函數模板。成員函數模板只有在被呼叫時才會被範例化。例如下面的程式:
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
    template <class T2>
    void Func(T2 t) { cout << t; }  //成員函數模板
};
int main()
{
    A<int> a;
    a.Func('K');  //成員函數模板Func被範例化
    a.Func("hello");
    return 0;
}
程式的輸出結果是:
Khello