樣板(Template)最主要的目的是作為所有資料類型的代名詞,讓設計架構可以更精簡,例如簡化多載函式或眾多類別的延伸介面,這兩者又可分別稱為函式樣板和類別樣板。
當函式希望設計成可以支援多種資料型態的輸入和回傳時,多載函式雖然可以達成,但種類一多時也會造成很冗長且相同的操作內容的程式碼,這時候樣板函式就可以派上用場,我們先來看看它的語法
template< class T >
T func(T a) {
// do something...
}
首先免不了我們需要一個 template 的關鍵字,隨後附帶一對角括號,角括號內用 class 用來定義 T,意思是 T 代表了所有的資料類型,你可以把它理解為一個代名詞。
class 關鍵字和我們前面介紹的類別(Class)是不同物,初學者可能容易混淆。定義樣板時,class 會去認 template 關鍵字和是否在角括號內。
然後寫上函式的定義,這時候所有在定義輸入參數的資料類型,以及返回值的資料型態,全都可以用 T 來表示,這樣就可以完成處理眾多資料類型的樣板函式。
類別樣板常作為多種類別的延伸介面,理解為附加功能可能會更為貼切。它和父類別的概念有點相反,父類別是起始點,類別樣板通常是中繼點或終點,概念圖如下:
運用 template 關鍵字,我們可以將多種類別轉變為統一的代名,然後再由樣板類別去做延伸。承上圖架構舉例,我們有一個 Car 的父類別作為起點,它的子類別延伸出日本車和歐系車,當我們特別想對日本車、歐系車或 SUV 車種各別做額外的延伸時,就可以運用樣板類別,如下例:
class Car {
public:
...
private:
...
};
class Lexus_NX_350 : public Car {
public:
...
private:
...
};
class Toyota_CRV : public Car {
public:
...
private:
...
};
template< class T >
class SUV {
...
}
上例中,我們設計了一套日本車的類別,然後再針對 SUV 車型做一個類別樣板,實際程式碼範例如下:
class Car {
public:
string info() {
return string("This is a car class.");
}
};
class Lexus_NX_350 : public Car {
public:
string info() {
return string("Lexus NX 350.");
}
};
class Toyota_CRV : public Car {
public:
string info() {
return string("Toyota CRV.");
}
};
template< class T >
class SUV {
public:
SUV(T *t) {
m_original_class = t;
}
void extended_info() {
cout << "This is SUV: " << m_original_class->info() << endl;
}
private:
T* m_original_class;
};
int main()
{
Lexus_NX_350 lnx350;
Toyota_CRV crv;
cout << lnx350.info() << endl;
cout << crv.info() << endl;
SUV< Lexus_NX_350 > suv1(&lnx350);
SUV< Toyota_CRV > suv2(&crv);
suv1.extended_info();
suv2.extended_info();
}
上例中,我們將日本車的 SUV 車型做一個簡單的延伸,讓 info() 函式可以附加 SUV 標示的字詞。我們在 SUV 建構式中定義了參數為一個代名詞 T,用來接收所有類型的物件或變數,並且用一個指標成員去指向該物件或變數,你會注意到這個指標也是用代名詞 T 來宣告。
當成員指標指向原形類別之後,我們就可以用類別樣板中的方法去做延伸,如同 extended_info() 函式。那麼在延伸的方法中,程式是什麼時候知道有一個方法是叫 info() 呢? 答案在編譯時期得知的,若所引用的定義域中沒有這樣的方法,或是類型不正確時,編譯就會報錯誤訊息。
Last updated: