C++ 模板簡介
一、模板
使用模板的目的就是能夠讓程序員編寫與類型無關(guān)的代碼金吗。
模板是一種對類型進(jìn)行參數(shù)化的工具漩怎,通常有兩種形式:函數(shù)模板和類模板。函數(shù)模板針對僅參數(shù)類型不同的函數(shù)侨赡;類模板針對僅數(shù)據(jù)成員和成員函數(shù)類型不同的類蓖租。
注意:
模板的聲明或定義只能在全局粱侣,命名空間或類范圍內(nèi)進(jìn)行。即不能在局部范圍蓖宦,函數(shù)內(nèi)進(jìn)行齐婴,比如不能在main函數(shù)中聲明或定義一個模板。
二稠茂、函數(shù)模板
函數(shù)模板定義了參數(shù)化的非成員函數(shù)柠偶,這使得程序員能夠用不同類型的參數(shù)調(diào)用相同的函數(shù),由編譯器決定調(diào)用哪一種類型睬关,并且從模板中生成相應(yīng)的代碼诱担。
定義一個函數(shù)模板:
template <class 形參名, class 形參名, ...>
返回類型 函數(shù)名(參數(shù)列表)
{ ... }
其中 template 和 class 是關(guān)鍵字,class 可以用 typename 關(guān)鍵字代替电爹,在這里 typename 和 class 沒區(qū)別蔫仙,<>括號中的參數(shù)叫模板形參,模板形參和函數(shù)形參很相像丐箩,模板形參不能為空匀哄。一但聲明了模板函數(shù)就可以用模板函數(shù)的形參名聲明類中的成員變量和成員函數(shù),即可以在該函數(shù)中使用內(nèi)置類型的地方都可以使用模板形參名雏蛮。模板形參需要調(diào)用該模板函數(shù)時提供的模板實參來初始化模板形參涎嚼,一旦編譯器確定了實際的模板實參類型就稱他實例化了函數(shù)模板的一個實例。
三挑秉、類模板
1. 類模板定義及實例化
定義一個類模板:
template <class 形參名, class 形參名, ...>
class 類名
{ ... };
其中法梯,template 是聲明類模板的關(guān)鍵字,表示聲明一個模板犀概,模板參數(shù)可以是一個立哑,也可以是多個,可以是類型參數(shù) 姻灶,也可以是非類型參數(shù)铛绰。類型參數(shù)由關(guān)鍵字 class 或 typename 及其后面的標(biāo)識符構(gòu)成。非類型參數(shù)由一個普通參數(shù)構(gòu)成产喉,代表模板定義中的一個常量捂掰。
注意:
(1) 如果在全局域中聲明了與模板參數(shù)同名的變量,則該變量被隱藏掉曾沈。
(2) 模板參數(shù)名不能被當(dāng)作類模板定義中類成員的名字这嚣。
(3) 同一個模板參數(shù)名在模板參數(shù)表中只能出現(xiàn)一次。
(4) 在不同的類模板或聲明中塞俱,模板參數(shù)名可以被重復(fù)使用姐帚。
(5) 在類模板的前向聲明和定義中,模板參數(shù)的名字可以不同障涯。
(6) 類模板參數(shù)可以有缺省實參罐旗,給參數(shù)提供缺省實參的順序是先右后左膳汪。
(7) 類模板名可以被用作一個類型指示符。當(dāng)一個類模板名被用作另一個模板定義中的類型指示符時九秀,必須指定完整的實參表
類型參數(shù)和非類型參數(shù)
-
1. 類型參數(shù):類型參數(shù)由關(guān)鍵字 class 或 typename 后接說明符構(gòu)成遗嗽,如 template <class T> void h(T a){};其中 T 就是一個類型參數(shù),類型參數(shù)的名字由用戶自已確定颤霎。類型參數(shù)表示的是一個未知的類型媳谁。類型參數(shù)可作為類型說明符用在模板中的任何地方,與內(nèi)置類型說明符或類類型說明符的使用方式完全相同友酱,即可以用于指定返回類型晴音,變量聲明等。
- 1.1 針對函數(shù)模板缔杉,不能為同一個模板類型形參指定兩種不同的類型锤躁,比如template <class T>void h(T a, T b){},語句調(diào)用 h(2, 3.2) 將出錯或详,因為該語句給同一模板形參 T 指定了兩種類型系羞,第一個實參 2 把模板形參 T 指定為 int,而第二個實參 3.2 把模板形參指定為 double霸琴,兩種類型的形參不一致椒振,會出錯迷帜。
- 1.2 針對類模板丐黄,當(dāng)我們聲明類對象為:A<int> a,比如 template <class T>T g(T a, T b){}衅檀,語句調(diào)用 a.g(2, 3.2) 在編譯時不會出錯选调,但會有警告夹供,因為在聲明類對象的時候已經(jīng)將T轉(zhuǎn)換為 int 類型,而第二個實參 3.2 把模板形參指定為 double仁堪,在運(yùn)行時哮洽,會對 3.2 進(jìn)行強(qiáng)制類型轉(zhuǎn)換為 3 。當(dāng)我們聲明類的對象為:A<double> a 弦聂,此時就不會有上述的警告鸟辅,因為從 int 到 double 是自動類型轉(zhuǎn)換。
-
2. 非類型參數(shù):模板的非類型形參也就是內(nèi)置類型形參横浑,如template<class T, int a> class B{};其中int a就是非類型的模板形參剔桨。
- **2.1 **非類型模板的形參只能是整型,指針和引用徙融。
- **2.2 **調(diào)用非類型模板形參的實參必須是一個常量表達(dá)式,即他必須能在編譯時計算出結(jié)果瑰谜。
- **2.3 **任何局部對象欺冀,局部變量树绩,局部對象的地址,局部變量的地址都不是一個常量表達(dá)式隐轩,都不能用作非類型模板形參的實參饺饭。全局指針類型,全局變量职车,全局對象也不是一個常量表達(dá)式瘫俊,不能用作非類型模板形參的實參。
- **2.4 **全局變量的地址或引用悴灵,全局對象的地址或引用const類型變量是常量表達(dá)式扛芽,可以用作非類型模板形參的實參。
- **2.5 **sizeof表達(dá)式的結(jié)果是一個常量表達(dá)式积瞒,也能用作非類型模板形參的實參川尖。
- **2.6 **非類型形參一般不應(yīng)用于函數(shù)模板中,比如有函數(shù)模板template<class T, int a> void h(T b){}茫孔,若使用 h(2) 調(diào)用會出現(xiàn)無法為非類型形參 a 推演出參數(shù)的錯誤叮喳,對這種模板函數(shù)可以用顯示模板實參來解決,如用 h<int, 3>(2) 這樣就把非類型形參 a 設(shè)置為整數(shù) 3缰贝。
- **2.7 **非類型模板形參的形參和實參間所允許的轉(zhuǎn)換:
1馍悟、允許從數(shù)組到指針,從函數(shù)到指針的轉(zhuǎn)換剩晴。如:
template <int *a> class A{}; int b[1]; A <b> m;
即數(shù)組到指針的轉(zhuǎn)換
2锣咒、const 修飾符的轉(zhuǎn)換。如:template<const int *a> class A{}; int b; A<&b> m; 即從int *到const int *
的轉(zhuǎn)換李破。
3宠哄、提升轉(zhuǎn)換。如:template<int a> class A{}; const short b=2; A<b> m;
即從 short 到 int 的提升轉(zhuǎn)換
4嗤攻、整值轉(zhuǎn)換毛嫉。如:template<unsigned int a> class A{}; A<3> m;
即從 int 到 unsigned int 的轉(zhuǎn)換。
5妇菱、常規(guī)轉(zhuǎn)換承粤。
注意:
(1) 可以為類模板的類型形參提供默認(rèn)值,但不能為函數(shù)模板的類型形參提供默認(rèn)值闯团。函數(shù)模板和類模板都可以為模板的非類型形參提供默認(rèn)值辛臊。
(2) 類模板的類型形參默認(rèn)值形式為:template<class T1, class T2=int> class A{};
為第二個模板類型形參 T2 提供 int 型的默認(rèn)值。
(3) 類模板類型形參默認(rèn)值和函數(shù)的默認(rèn)參數(shù)一樣房交,如果有多個類型形參則從第一個形參設(shè)定了默認(rèn)值之后的所有模板形參都要設(shè)定默認(rèn)值彻舰,比如template<class T1=int, class T2>class A{};
就是錯誤的,因為 T1 給出了默認(rèn)值,而 T2 沒有設(shè)定刃唤。
(4) 在類模板的外部定義類中的成員時 template 后的形參表應(yīng)省略默認(rèn)的形參類型隔心。比如template<class T1, class T2=int> class A{public: void h();};
定義方法為template<class T1,class T2> void A<T1,T2>::h(){}
。
類模板實例化
定義:從通用的類模板定義中生成類的過程稱為模板實例化尚胞。
類模板什么時候會被實例化呢硬霍?
① 當(dāng)使用了類模板實例的名字,并且上下文環(huán)境要求存在類的定義時笼裳。
② 對象類型是一個類模板實例唯卖,當(dāng)對象被定義時。此點被稱作類的實例化點躬柬。
③ 一個指針或引用指向一個類模板實例拜轨,當(dāng)檢查這個指針或引用所指的對象時。
template<class Type>
class Graphics{};
void f1(Graphics<char>);// 僅是一個函數(shù)聲明楔脯,不需實例化
class Rect
{
Graphics<double>& rsd;// 聲明一個類模板引用撩轰,不需實例化
Graphics<int> si;// si是一個Graphics類型的對象,需要實例化類模板
}
int main(){
Graphcis<char>* sc;// 僅聲明一個類模板指針,不需實例化
f1(*sc);//需要實例化,因為傳遞給函數(shù)f1的是一個Graphics<int>對象昧廷。
int iobj=sizeof(Graphics<string>);//需要實例化堪嫂,因為sizeof會計算Graphics<string>對象的大小,為了計算大小木柬,編譯器必須根據(jù)類模板定義產(chǎn)生該類型皆串。
}
2. 類模板的成員函數(shù)
要點:
① 類模板的成員函數(shù)可以在類模板的定義中定義(inline 函數(shù)),也可以在類模板定義之外定義(此時成員函數(shù)定義前面必須加上 template 及模板參數(shù))眉枕。
② 類模板成員函數(shù)本身也是一個模板恶复,類模板被實例化時它并不自動被實例化,只有當(dāng)它被調(diào)用或取地址速挑,才被實例化谤牡。
3. 類模板的友元聲明
非模板友元類或友元函數(shù)
顧名思義,第一種聲明表示具體的類或函數(shù)姥宝。
綁定的友元類模板或函數(shù)模板
第二種聲明表示類模板的實例和它的友元之間是一種一對一的映射關(guān)系翅萤。
如圖:
非綁定的友元類模板或函數(shù)模板
第三種聲明表示類模板的實例和它的友元之間是一種一對多的映射關(guān)系。
如圖:
注意:當(dāng)把非模板類或函數(shù)聲明為類模板友元時腊满,它們不必在全局域中被聲明或定義套么,但將一個類的成員聲明為類模板友元,該類必須已經(jīng)被定義碳蛋,另外在聲明綁定的友元類模板或函數(shù)模板時胚泌,該模板也必須先聲明。
4. 類模板的靜態(tài)數(shù)據(jù)成員肃弟、嵌套類型
類模板的靜態(tài)數(shù)據(jù)成員
要點:
① 靜態(tài)數(shù)據(jù)成員的模板定義必須出現(xiàn)在類模板定義之外玷室。
② 類模板靜態(tài)數(shù)據(jù)成員本身就是一個模板零蓉,它的定義不會引起內(nèi)存被分配,只有對其實例化才會分配內(nèi)存阵苇。
③ 當(dāng)程序使用靜態(tài)數(shù)據(jù)成員時壁公,它被實例化感论,每個靜態(tài)成員實例都與一個類模板實例相對應(yīng)绅项,靜態(tài)成員的實例引用要通過一個類模板實例。
類模板的嵌套類型
要點:
① 在類模板中允許再嵌入模板比肄,因此類模板的嵌套類也是一個模板快耿,它可以使用外圍類模板的模板參數(shù)。
② 當(dāng)外圍類模板被實例化時芳绩,它不會自動被實例化掀亥,只有當(dāng)上下文需要它的完整類類型時,它才會被實例化妥色。
③ 公有嵌套類型可以被用在類定義之外搪花,這時它的名字前必須加上類模板實例的名字。
5. 成員模板
定義:成員定義前加上 template 及模板參數(shù)表嘹害。
要點:
① 在一個類模板中定義一個成員模板,意味著該類模板的一個實例包含了可能無限多個嵌套類和無限多個成員函數(shù)撮竿。
② 只有當(dāng)成員模板被使用時,它才被實例化笔呀。
③ 成員模板可以定義在其外圍類或類模板定義之外幢踏。
注意:類模板參數(shù)不一定與類模板定義中指定的名字相同。
6. 類模板的特化及部分特化
類模板的特化
先看下面的例子:
Template<class type>
Class Graphics
{
Public:
void out(type figure){…}
};
Class Rect{…};
如果模板實參是 Rect 類型许师,我們不希望使用類模板 Graphics 的通用成員函數(shù)定義房蝉,來實例化成員函數(shù) out(),我們希望專門定 Graphics<Rect>::out() 實例微渠,讓它使用 Rect 里面的成員函數(shù)搭幻。
為此,我們可以通過一個顯示特化定義逞盆,為類模板實例的一個成員提供一個特化定義檀蹋。
格式:template<> 成員函數(shù)特化定義
下面為類模板實例Graphics<Rect>的成員函數(shù)out()定義了顯式特化:
Template<> void Graphics<Rect>::out(Rect figure){…}
注意:
① 只有當(dāng)通用類模板被聲明后,它的顯式特化才可以被定義纳击。
② 若定義了一個類模板特化续扔,則必須定義與這個特化相關(guān)的所有成員函數(shù)或靜態(tài)數(shù)據(jù)成員,此時類模板特化的成員定義不能以符號 template<> 作為打頭焕数。(template<> 被省略)
③ 類模板不能夠在某些文件中根據(jù)通用模板定義被實例化纱昧,而在其他文件中卻針對同一組模板實參被特化。
類模板部分特化
如果模板有一個以上的模板參數(shù)堡赔,則有些人就可能希望為一個特定的模板實參或者一組模板實參特化類模板识脆,而不是為所有的模板參數(shù)特化該類模板。即,希望提供這樣一個模板:它仍然是一個通用的模板灼捂,只不過某些模板參數(shù)已經(jīng)被實際的類型或值取代离例。通過使用類模板部分特化,可以實現(xiàn)這一點悉稠。
template<int hi,int wid>
Class Graphics{…};
Template<int hi>//類模板的部分特化
Class Graphics<hi,90>{…};
格式:template<模板參數(shù)表>
注意:
① 部分特化的模板參數(shù)表只列出模板實參仍然未知的那些參數(shù)宫蛆。
② 類模板部分特化是被隱式實例化的。編譯器選擇 “針對該實例而言最為特化的模板定義” 進(jìn)行實例化的猛,當(dāng)沒有特化可被使用時耀盗,才使用通用模板定義。
例:Graphics<24,90> figure;
它即能從通用類模板定義被實例化卦尊,也能從部分特化的定義被實例化叛拷,但編譯器選擇的是部分特化來實例化模板。
③類模板部分特化必須有它自己對成員函數(shù)岂却、靜態(tài)數(shù)據(jù)成員和嵌套類的定義忿薇。
7. 名字空間和類模板
類模板定義也可以被放在名字空間中。例如:
Namespace cplusplus_primer
{
Template<class type>
Class Graphics{…};
Template<class type>
Type create()
{…}
}
當(dāng)類模板名字 Graphics 被用在名字空間之外時躏哩,它必須被名字空間名 cplusplus_primer 限定修飾署浩,或者通過一個 using 聲明或指示符被引入。例如:
Void main()
{
using cplusplus_primer::Graphics;
Graphics<int> *pg=new Graphics<int>;
}
注意:在名字空間中聲明類模板也會影響該類模板及其成員的特化和部分特化聲明的方式震庭,類模板或類模板成員的特化聲明必須被聲明在定義通用模板的名字空間中(可以在名字空間之外定義模板特化)瑰抵。
** 參考資料: **
C++中的類模板詳細(xì)講述
C++ 模板詳解(一)
C++ 模板詳解(二)