C++類別別模板用法詳解

2020-07-16 10:04:41
如果需要的多個函數具有相同的解決問題的邏輯,只是它們所使用的形參的型別不同,則可以使用函數模板。同樣,如果需要的多個類僅在其某些資料成員的型別方面有所不同,或者僅在其成員函數的形參型別方面有所不同,則都可以使用類別範本

宣告一個類別範本和宣告一個函數模板類似,可以使用諸如 T、T1、T2 之類的識別符號(或者由程式設計師選擇的任何其他識別符號)作為泛型來編寫類,然後使用正確編寫的模板頭作為類宣告的字首。

例如,假設要定義一個類,用來表示一個泛型型別的陣列,並新增一個過載運算子 [] 以執行邊界檢查,則可以定義 SimpleVector 類,並輸入適當的資料成員和建構函式,按以下方式編寫模板:
template<class T>
class SimpleVector
{
        unique_ptr<T []>aptr;
        int arraysize;
    public:
        SimpleVector (int) ; // 建構函式
        SimpleVector (const SimpleVector &) ; // 複製建構函式
        int size () const{ return arraySize; }
        T &operator [] (int) ; // 過載[]運算子
        void print () const; // 輸出陣列元素
};
這個類別範本將把型別 T 的元素儲存在一個動態生成的陣列中。這就解釋了為什麼指向這個陣列底部的指標 aptr 被宣告為 T[] 型別的指標。這裡使用了 unique_ptr 獨佔指標作為 aptr 的型別,因為 SimpleVector 物件將不會和程式的其他部分共用動態分配的陣列。

同樣,過載的陣列下標運算子返回一個 T 型別的值。但需要注意的是,size 成員函數返回的值和表示儲存在陣列中的元素數量的成員 armySize 都是 int 型別。這是有道理的,因為無論陣列儲存的元素資料是什麼型別,陣列中元素的數量總是一個整數。

可以將 SimpleVector 模板看作一種通用模式,通過對它進行特殊化即可建立多個不同的 SimpleVector 類,以儲存 double、long、string 或任何其他可以定義的型別。規則是在類別範本的名稱 SimpleVector 後面附加一個實際型別的列表(以尖括號括起來),從而形成一個實際的類的名稱,範例如下:
  • SimpleVector <double> 是儲存 double 陣列的類的名稱。
  • SimpleVector <string> 是儲存 string 陣列的類的名稱。
  • SimpleVector <char> 是儲存 char 陣列的類的名稱。

現在來看一個定義 SimpleVector 物件的範例,它通過使用轉換建構函式建立了一個包含 10 個 double 型別元素的陣列。

SimpleVector <double> dTable(10);

在類中定義一個模板類的成員函數是很簡單的,例如,在 SimpleVector 類中即可輕鬆定義 size() 函數。但是,如果要在類的外面定義成員函數,則必須使用模板頭來給成員函數的定義新增字首(該模板頭指定了型別形參的列表),然後在定義中還需要使用類別範本的名稱,後面還要跟著一個型別形參的列表(使用尖括號括起來)。

現在使用運算子 [] 函數來說明如何在類外部定義成員函數:
template<class T>
T &SimpleVector<T>::operator[](int sub)
{
    if(sub < 0 || sub >= arraySize)
        throw IndexOutOfRangeException(sub)
    return aptr[sub];
}
在這個定義中,由於作用域解析運算子(::)之前需要類的名稱,所以在該位置新增了 SimpleVector <T>。以下是另外一個範例,它是轉換建構函式的定義:
template<class T>
SimpleVector<T>::SimpleVector(int s)
{
    arraySize = s;
    aptr = make_unique<T[]> (s);
    for (int count = 0; count < arraySize; count++)
        aptr[count] = T();
}
在該範例中,需要在作用域解析運算子之前有 SimpleVector <T>,但是在作用域解析運算子之後,卻只有 SimpleVector,而沒有 <T>,這是因為在作用域解析運算子後面需要的不是類的名稱,而是成員函數的名稱,在這種情況下恰好是建構函式。

將型別形參列表附加到模板類名稱的規則有一個例外。只要該類的名稱在模板類的作用域內,則該列表和包含它的尖括號就可以省略。因此,當一個類的名稱在類本身的任何地方被使用,或者位於被定義在類之外的成員函數的區域性作用域內時,該列表即可被省略。

例如,來看以下複製建構函式:
template<class T>
SimpleVector<T>::SimpleVector(const SimpleVector &obj)
{
    arraySize = obj.arraySize;
    aptr = make_unique<T []>(arraySize);
    for (int count = 0; count < arraySize; count++)
        aptr[count] = obj[count];
}
該函數就不需要將 <T> 附加到 SimpleVector 以表示其引數型別。

SimpleVector 的轉換建構函式假定型別形參T在執行賦值語句 "aptr[count] = T();" 時有一個預設的建構函式 T()。如果T是基礎型別,則 C++ 編譯器將使用預設值 0 替換 T()。例如,如果 T 是 int,那麼該賦值語句相當於 aptr[count] = int(),並且值 0 將被儲存在 aptr[count] 中。

SimpleVector 模板的程式碼在 SimpleVector.h 檔案中列出。
//SimpleVector.h
#include <iostream>
#include <cstdlib>
#include <memory>
using namespace std;

// Exception for index out of range
struct IndexOutOfRangeException
{
    const int index;
    IndexOutOfRangeException(int ix) :
    index(ix) {}
};

template <class T>
class SimpleVector
{
        unique_ptr<T []> aptr;
        int arraySize;
    public:
        SimpleVector(int); // Constructor
        SimpleVector(const SimpleVector &); // Copy constructor
        int size() const { return arraySize; }
        T &operator[](int);    // Overloaded [] operator
        void print () const; //    outputs the array elements
};
template <class T>
SimpleVector<T>::SimpleVector(int s)
{
    arraySize = s;
    aptr = make_unique<T[]>(s);
    for (int count = 0; count < arraySize; count++)
        aptr[count] = T();
}
template <class T>
SimpleVector<T>::SimpleVector(const SimpleVector &obj)
{
    arraySize = obj.arraySize;
    aptr = make_unique<T[]>(obj.arraySize);
    for (int count = 0; count < arraySize; count++)
        aptr[count] = obj[count];   
}
template <class T>
T &SimpleVector<T>::operator[](int sub)
{
    if (sub < 0 || sub >= arraySize)
        throw IndexOutOfRangeException(sub);
    return aptr[sub];
}

template <class T>
void SimpleVector<T>::print() const
{
    for (int k = 0; k < arraySize; k++)
        cout << aptr [k] << " ";
    cout << endl;
}

下面的程式演示了 SimpleVector 模板的使用:
//This program demonstrates the SimpleVector template.
#include <iostream>
#include "SimpleVector.h"
using namespace std;

int main()
{
    const int SIZE = 10;
    SimpleVector<int> intTable (SIZE);
    SimpleVector<double> doubleTable(SIZE);
    // Store values in the arrays
    for (int x = 0; x < SIZE; x++)
    {
        intTable[x] = (x * 2);
        doubleTable[x] = (x * 2.14);
    }
    // Display the values in the arrays
    cout << "These values are in intTable:n";
    intTable.print();
    cout << "These values are in doubleTable:n";
    doubleTable.print ();
    // Use the built-in + operator on array elements
    for (int x = 0; x < SIZE; x++)
    {   
        intTable[x] = intTable[x] + 5;
        doubleTable[x] = doubleTable[x] + 1.5;
    }
    // Display the values in the array
    cout << "These values are in intTable:n";
    intTable.print ();
    cout << "These values are in doubleTable:n"; doubleTable.print ();
    // Use the built-in ++ operator on array elements
    for (int x = 0; x < SIZE; x++)
    {
        intTable[x]++;
        doubleTable[x]++;
    }
    // Display the values in the array
    cout << "These values are in intTable:n";
    intTable.print ();
    cout << "These values are in the doubleTable: n";
    doubleTable.print();
    cout << endl;
    return 0;
}
程式輸出結果:

These values are in intTable:
0 2 4 6 8 10 12 14 16 18
These values: are in doubleTable:
0 2.14 4.28 6.42 8.56 10.7 12.84 14.98 17.12 19.26
These values are in intTable:
5 7 9 11 il3 15 17 19 21 23
These values are in doubleTable:
1.5 3.64 5.78 7.92 10.06 12.2 14.34 16.48 18.62 20.76
These values are in intTable:
6 8 .10 12 14 16 18 20 22 24
These values are in the doubleTable:
2.5 4.64 6.78 8.92 11.06 13.2 15.34 17.48 19.62 21.76

注意,在包含驅動模組程式碼的檔案中已經 include 包含模板程式碼的檔案,這樣可以避免單獨連結已編譯的使用模板的檔案,降低複雜性。