模板
- 模板 Template是代碼重用機(jī)制的重要工具
-
泛型技術(shù):與數(shù)據(jù)類型無關(guān)的程序設(shè)計(jì)技術(shù)纳猫,是概念級的通用程序方法
- 模板將算法設(shè)計(jì)從具體數(shù)據(jù)類型中分離虱岂,設(shè)計(jì)出獨(dú)立于數(shù)據(jù)類型的通用模板程序
- 模板有函數(shù)模板和類模板
C++模板
- 在C中,可以使用宏定義來泛化設(shè)計(jì)函數(shù)台舱,但是不進(jìn)行類型檢查律杠,容易出錯
- 使用template關(guān)鍵字定義模板潭流,在<>中用typename或class聲明模板類型
- <>中的參數(shù)被稱為模板參數(shù),可以是類型參數(shù)(用typename和class聲明)和非類型參數(shù)(確定的數(shù)據(jù)類型)
- 非類型模板參數(shù)必須是整數(shù)類型(包括枚舉)柜去、對象灰嫉、函數(shù)指針,或者是指向外部變量的指針
- 與普通函數(shù)與類的接口實(shí)現(xiàn)分離的分文件存放方式不同嗓奢,模板類和函數(shù)必須將定義和聲明放在同一文件內(nèi)
- 編譯模板時讼撒,編譯器必須知道模板的確切定義,模板函數(shù)和類的定義通常(對于大多數(shù)編譯器而言)保存在同一頭文件中(.hpp)
- 編譯器有兩種模式股耽,包含和分離編譯模式根盒,其中分離模式中模板定義會有一個export參數(shù)告知編譯器該模板實(shí)例在生成時定義必須可見,但并不是所有編譯器都支持該模式
- 大部分編譯器使用包含模式物蝙,此時編譯器一次只能處理一個cpp
函數(shù)模板
- 函數(shù)模板由編譯器在其調(diào)用時確定模板類型的具體類型
- 模板類型可以用來定義函數(shù)的返回值炎滞、形參、局部變量
- 在遇到函數(shù)模板定義時诬乞,編譯器不會生成對應(yīng)代碼册赛,直到調(diào)用時進(jìn)行代碼生成
- 編譯器只生成一次同類型的函數(shù)定義,第二次遇到相同類型直接調(diào)用
- 每個模板類型在函數(shù)被調(diào)用時必須能夠被推導(dǎo)(根據(jù)參數(shù)類型判斷或直接顯式指定)
- 如果不能進(jìn)行推導(dǎo)震嫉,編譯器會無法得知應(yīng)該生成的函數(shù)定義森瘪,產(chǎn)生錯誤
- 模板定義后需緊跟函數(shù)聲明,一個模板可以有多個類型參數(shù)
- 與一般函數(shù)參數(shù)傳遞不同责掏,模板函數(shù)在調(diào)用時不會進(jìn)行隱式類型轉(zhuǎn)換,可能引發(fā)類型不明確的錯誤
- 如:函數(shù)有多個參數(shù)為T類型湃望,調(diào)用時參數(shù)接收了1(int)和1.0(double)换衬,無法明確模板類型
template<typename T> void fn2(T t1, T t2);
// 模板參數(shù)不進(jìn)行隱式類型轉(zhuǎn)換,類型不明確
// fn2(1, 1.0);
// fn2<int>(1, 1.0); // 可能丟失精度的操作
fn2<double>(1, 1.0);
fn2(double(1), double(1.0));
- 可以通過對參數(shù)進(jìn)行顯式轉(zhuǎn)換证芭,或在調(diào)用時顯式指定模板類型解決這一問題
- 小技巧:將函數(shù)賦值為delete瞳浦,可以禁用對該函數(shù)的調(diào)用,通撤鲜浚可以用來避免隱式類型轉(zhuǎn)換
void fn(int a){...}
void fn(double a) = delete;
fn(1.0); // 該調(diào)用將被阻止
- 非類型模板參數(shù)只能是整數(shù)類型(包括枚舉)叫潦、對象、函數(shù)指針和指向外部變量的指針
template<typename T, int i, int* pi> void fn3(T t1);
- 在調(diào)用函數(shù)時官硝,只能向非類型模板參數(shù)傳遞常值矗蕊,不能傳遞變量,并且非類型模板參數(shù)必須接受值(包括默認(rèn)值)
template<typename T, int i> void fn3(T t1);
// 不能向模板參數(shù)傳遞變量
int i = 0;
// fn3<char, i>(i);
fn3<char, 1>(i);
- 模板參數(shù)遵循一般的作用域規(guī)則氢架,但是不能在函數(shù)內(nèi)重用模板參數(shù)名
template<typename T, int i> void fn3(T t1)
{
// 編輯器不會報錯傻咖,注意
// 不能重用模板參數(shù)名
int i = 0;
int T = 0;
}
類模板
- 類模板用來處理機(jī)構(gòu)和成員函數(shù)相同,但成員具體數(shù)據(jù)類型不同的類型
- 類模板的定義必須緊跟模板定義岖研,中間不能有其他代碼
// 抽象類MyStack
template<typename T>
class MyStack
{
private:
T value;
T* top;
public:
virtual void init() = 0;
virtual void push(T v) = 0;
virtual T pop() = 0;
};
- 如果類模板有一個非類型模板參數(shù)卿操,必須在使用(包括實(shí)例化與成員對象、派生類負(fù)責(zé)基類初始化)時對其傳遞值,或使用默認(rèn)值
template<typename T, int MAXSIZE>
class MyStack
{
...
};
- 在用類外用模板聲明成員函數(shù)時害淤,必須將模板聲明放在函數(shù)名前扇雕,類作用域限定符后
- 等于需要再聲明一次模板參數(shù)列表,以告知編譯器成員函數(shù)所屬的類是一個模板類
- 且模板類的聲明和定義都必須在同一個.h或.hpp中窥摄,防止實(shí)例化時編譯器出錯
template<typename T, int MAXSIZE>
void MyStack<T, MAXSIZE>::fn()
{
}
- 類模板實(shí)例化包括模板實(shí)例化和成員函數(shù)實(shí)例化
- 類模板在實(shí)例化對象時必須為所有模板參數(shù)顯式指定類型镶奉,如使用STL的vector
// 必須為模板類指定模板參數(shù)的類型
vector<int> iv;
- 編譯器在實(shí)例化對象時才會處理類模板參數(shù),所做的操作等同于將代碼中所有類型參數(shù)替換成推導(dǎo)的參數(shù)溪王,將所有非類型參數(shù)替換為指定的值腮鞍,但是不會實(shí)例化模板成員函數(shù)
可變參數(shù)函數(shù)模板
- C++11提供了一種可變參數(shù)的模板函數(shù),其參數(shù)類型和個數(shù)都可不確定
- 可變參數(shù)函數(shù)模板只能用于模板函數(shù)莹菱,對于普通函數(shù)需要引入頭文件stdarg.h或cstdarg并進(jìn)行相關(guān)操作
- 可變參數(shù)模板函數(shù)必須有兩個模板參數(shù)移国,其中第二個需要用省略號...args書寫,調(diào)用時使用后綴形式args...
- 可變參數(shù)模板函數(shù)實(shí)質(zhì)上是一種遞歸道伟,首先執(zhí)行函數(shù)迹缀,接收所有參數(shù)中的第一個參數(shù)
- 然后將剩余參數(shù)...args中的第一個參數(shù)在調(diào)用函數(shù)時傳入
- 因此,為了結(jié)束遞歸蜜徽,通常需要有一個只有一個模板參數(shù)的普通模板函數(shù)作為遞歸結(jié)束的函數(shù)
- 這個普通的模板函數(shù)是對可變參數(shù)模板函數(shù)的重載祝懂,因此需要在可變參數(shù)模板函數(shù)之前被定義,否則無法結(jié)束遞歸
// 此函數(shù)將在...args只剩下一個參數(shù)時被調(diào)用拘鞋,參數(shù)個數(shù)1與之匹配
template<typename T>
void fn(T a)
{
cout << a << endl;
}
// 此函數(shù)在...args的參數(shù)個數(shù)>=2時被調(diào)用
template<typename T,typename ...TR>
void fn(T a,TR ...args) // 聲明函數(shù)原型時使用前綴
{
cout << a << endl;
// 必須在內(nèi)部遞歸砚蓬,否則只會使用第一個參數(shù)
fn(args...); // 調(diào)用時使用后綴
}
模板特化
- 在一些情況下,部分類型可能因?yàn)橛胁煌倪\(yùn)算符或其他函數(shù)定義而導(dǎo)致模板函數(shù)中的運(yùn)行過程出現(xiàn)問題
- 此時盆色,可以為這些特定的類型定義特殊的模板灰蛙,稱為模板特化
- 模板特化是一種重載,在聲明特化模板時隔躲,將需要特化的模板參數(shù)從<>移除摩梧,并在使用的位置用具體類型說明
- template<>在其中沒有模板參數(shù)時也需要保留,聲明其為模板特化
template<typename T>
void fn(T t)
{
cout << "normal type" << endl;
}
template<> // 移出模板參數(shù)列表
void fn(char t) // 在原來使用模板參數(shù)的地方使用具體類型
{
cout << "T is char" << endl;
}
int main()
{
fn(1); // normal type
fn('c'); // T is char
}
模板設(shè)計(jì)常見注意點(diǎn)
- 對于內(nèi)聯(lián)的模板函數(shù)和模板成員函數(shù)宣旱,inline和constexpr要放在模板定義之后
template<typename T> inline void fn(T t1);
template<typename T> constexpr void fn(T t1);
- C++11后可以為模板參數(shù)指定默認(rèn)值仅父,且其右側(cè)其他模板參數(shù)都應(yīng)該有默認(rèn)值
template<typename T = double, int i = 1> void fn3(T t1);
- 用模板定義的類的成員函數(shù)稱為成員模板,成員模板不能是虛函數(shù)浑吟,虛函數(shù)的個數(shù)需要在編譯時就確定
- 模板函數(shù)可以被重載笙纤,重載規(guī)則與一般函數(shù)重載相同,要有不同的參數(shù)列表(包括模板參數(shù)列表)
- 模板參數(shù)并不一定在所有類型下都可正常運(yùn)行组力,此時可以用普通函數(shù)對其重載粪糙,確保功能正常(注意聲明順序)
- 模板參數(shù)調(diào)用有特定順序:
- 普通函數(shù)
- 特化模板函數(shù)
- 普通模板函數(shù)
- C++的函數(shù)在調(diào)用時會嘗試匹配最佳的函數(shù),并可能發(fā)生參數(shù)的隱式類型轉(zhuǎn)換忿项,對于模板函數(shù)蓉冈,只會發(fā)生如下兩種:
- 非const類型的實(shí)參傳遞給const類型的形參
- 數(shù)組或引用類型的實(shí)參轉(zhuǎn)換成指針類型的形參城舞,如數(shù)組名或其引用會轉(zhuǎn)換成數(shù)組首地址