一、類模板定義及實例化
1. 定義一個類模板:
1 template<class 模板參數(shù)表> 2 3 class 類名{ 4 5 // 類定義...... 6 7 };
其中,template 是聲明類模板的關(guān)鍵字,表示聲明一個模板,模板參數(shù)可以是一個,也可以是多個,可以是類型參數(shù) ,也可以是非類型參數(shù)。類型參數(shù)由關(guān)鍵字class或typename及其后面的標(biāo)識符構(gòu)成。非類型參數(shù)由一個普通參數(shù)構(gòu)成,代表模板定義中的一個常量。
例:
1 template<class type,int width> 2 3 //type為類型參數(shù),width為非類型參數(shù) 4 5 class Graphics;
注意:
(1)如果在全局域中聲明了與模板參數(shù)同名的變量,則該變量被隱藏掉。
(2)模板參數(shù)名不能被當(dāng)作類模板定義中類成員的名字。
(3)同一個模板參數(shù)名在模板參數(shù)表中只能出現(xiàn)一次。
(4)在不同的類模板或聲明中,模板參數(shù)名可以被重復(fù)使用。
1 typedef string type; 2 3 template<class type,int width> 4 5 class Graphics 6 7 { 8 9 type node;//node不是string類型 10 11 typedef double type;//錯誤:成員名不能與模板參數(shù)type同名 12 13 }; 14 15 template<class type,class type>//錯誤:重復(fù)使用名為type的參數(shù) 16 17 class Rect; 18 19 template<class type> //參數(shù)名”type”在不同模板間可以重復(fù)使用 20 21 class Round;
(5)在類模板的前向聲明和定義中,模板參數(shù)的名字可以不同。
1 // 所有三個 Image 聲明都引用同一個類模板的聲明 2 3 template <class T> class Image; 4 5 template <class U> class Image; 6 7 // 模板的真正定義 8 9 template <class Type> 10 11 class Image { //模板定義中只能引用名字”Type”,不能引用名字”T”和”U” };
(6)類模板參數(shù)可以有缺省實參,給參數(shù)提供缺省實參的順序是先右后左。
View Code
(7)類模板名可以被用作一個類型指示符。當(dāng)一個類模板名被用作另一個模板定義中的類型指示符時,必須指定完整的實參表
View Code
2.類模板實例化
定義:從通用的類模板定義中生成類的過程稱為模板實例化。

例:Graphics<int> gi;
類模板什么時候會被實例化呢?
①當(dāng)使用了類模板實例的名字,并且上下文環(huán)境要求存在類的定義時。
②對象類型是一個類模板實例,當(dāng)對象被定義時。此點被稱作類的實例化點。
③一個指針或引用指向一個類模板實例,當(dāng)檢查這個指針或引用所指的對象時。
例:
1 template<class Type> 2 3 class Graphics{}; 4 5 void f1(Graphics<char>);// 僅是一個函數(shù)聲明,不需實例化 6 7 class Rect 8 9 { 10 11 Graphics<double>& rsd;// 聲明一個類模板引用,不需實例化 12 13 Graphics<int> si;// si是一個Graphics類型的對象,需要實例化類模板 14 15 } 16 17 int main(){ 18 19 Graphcis<char>* sc;// 僅聲明一個類模板指針,不需實例化 20 21 f1(*sc);//需要實例化,因為傳遞給函數(shù)f1的是一個Graphics<int>對象。 22 23 int iobj=sizeof(Graphics<string>);//需要實例化,因為sizeof會計算Graphics<string>對象的大小,為了計算大小,編譯器必須根據(jù)類模板定義產(chǎn)生該類型。 24 25 }
3.非類型參數(shù)的模板實參
要點:
①綁定給非類型參數(shù)的表達(dá)式必須是一個常量表達(dá)式。
②從模板實參到非類型模板參數(shù)的類型之間允許進(jìn)行一些轉(zhuǎn)換。包括左值轉(zhuǎn)換、限定修飾轉(zhuǎn)換、提升、整值轉(zhuǎn)換。
③可以被用于非類型模板參數(shù)的模板實參的種類有一些限制。
例:
1 Template<int* ptr> class Graphics{…….}; 2 3 Template<class Type,int size> class Rect{……..}; 4 5 const int size=1024; 6 7 Graphics<&size> bp1;//錯誤:從const int*->int*是錯誤的。 8 9 Graphics<0> bp2;//錯誤不能通過隱式轉(zhuǎn)換把0轉(zhuǎn)換成指針值 10 11 const double db=3.1415; 12 13 Rect<double,db> fa1;//錯誤:不能將const double轉(zhuǎn)換成int. 14 15 unsigned int fasize=255; 16 17 Rect<String, fasize> fa2;//錯誤:非類型參數(shù)的實參必須是常量表達(dá)式,將unsigned改為const就正確。 18 19 Int arr[10]; 20 21 Graphics<arr> gp;//正確
二、類模板的成員函數(shù)
要點:
①類模板的成員函數(shù)可以在類模板的定義中定義(inline函數(shù)),也可以在類模板定義之外定義(此時成員函數(shù)定義前面必須加上template及模板參數(shù))。
②類模板成員函數(shù)本身也是一個模板,類模板被實例化時它并不自動被實例化,只有當(dāng)它被調(diào)用或取地址,才被實例化。
1 template<class type> 2 3 Class Graphics{ 4 5 Graphics(){…}//成員函數(shù)定義在類模板的定義中 6 7 void out(); 8 9 }; 10 11 template<class type>//成員函數(shù)定義在類模板定義之外 12 13 void Graphics<type>::out(){…}
三、類模板的友元聲明
類模板中可以有三種友元聲明:
1.非模板友元類或友元函數(shù)
1 class Graphics{void out();}; 2 3 Template<class T> 4 5 Class Rect{ 6 7 friend class Graphics;//類Graphics、函數(shù) 8 9 friend void create();// create、 out是類模板 10 11 friend void Graphics::out();// Rect所有實例的友元 12 13 };
2、綁定的友元類模板或函數(shù)模板。
3、非綁定的友元類模板或函數(shù)模板。
第二種聲明表示類模板的實例和它的友元之間是一種一對一的映射關(guān)系。
如圖:

第三種聲明表示類模板的實例和它的友元之間是一種一對多的映射關(guān)系。
如圖:

例:綁定的友元模板
1 template<class type> 2 3 void create(Graphics<type>); 4 5 template<class type> 6 7 class Graphics{ 8 9 friend void create<type>(Graphics<type>); 10 11 };
例:非綁定的友元模板
1 template<class type> 2 3 class Graphics{ 4 5 template<class T> 6 7 friend void create(Graphics<T>); 8 9 };
注意:當(dāng)把非模板類或函數(shù)聲明為類模板友元時,它們不必在全局域中被聲明或定義,但將一個類的成員聲明為類模板友元,該類必須已經(jīng)被定義,另外在聲明綁定的友元類模板或函數(shù)模板時,該模板也必須先聲明。
例:
1 template <class T> 2 3 class A { 4 5 private: 6 7 friend class B<T>; //錯誤:類B必須先聲明 8 9 }; 10 11 template <class T> 12 13 class B{};
四、類模板的靜態(tài)數(shù)據(jù)成員、嵌套類型
1.類模板的靜態(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)成員的實例引用要通過一個類模板實例。
例:
1 template<class type> 2 3 class Graphics{ 4 5 static Graphics *next; 6 7 static const type item; 8 9 }; 10 11 template<class type> 12 13 Graphics<type> * Graphics<type>::next=0; 14 15 template<class type> 16 17 type Graphics<type>::item=NULL; 18 19 //靜態(tài)成員定義分為兩部分:前一部分是類型,比如Graphics<type>*,后一部分是名稱和值,比如Graphics<type>::next=0;
2.類模板的嵌套類型
要點:
①在類模板中允許再嵌入模板,因此類模板的嵌套類也是一個模板,它可以使用外圍類模板的模板參數(shù)。
②當(dāng)外圍類模板被實例化時,它不會自動被實例化,只有當(dāng)上下文需要它的完整類類型時,它才會被實例化。
③公有嵌套類型可以被用在類定義之外,這時它的名字前必須加上類模板實例的名字。
例:
1 template<class type> 2 3 class Graphics{ 4 5 public: 6 7 template<class T> 8 9 class Rect{void out(type a,T b);}; 10 11 }; 12 13 Graphics<int>::Rect<double> node; 14 15 //引用公有嵌套類型必須加上類模板實例名字
五、成員模板
定義:成員定義前加上template及模板參數(shù)表。
要點:
①在一個類模板中定義一個成員模板,意味著該類模板的一個實例包含了可能無限多個嵌套類和無限多個成員函數(shù).
②只有當(dāng)成員模板被使用時,它才被實例化.
③成員模板可以定義在其外圍類或類模板定義之外.
例:
1 template<class type> 2 3 class Graphics<type>{ 4 5 public:template<class T> 6 7 class Rect{void out(type a,T b);};}; 8 9 template<class Gtype> template<class TT> 10 11 void Graphics<Gtype>::Rect<TT>::out(Gtype a,TT b){}//成員模板被定義在類模板定義之外(要根上完整模板實參) 12 13 Graphics<int>的實例可能包括下列嵌套類型: 14 15 Graphics<int>::Rect<double> 16 17 Graphics<int>::Rect<string>
注意:類模板參數(shù)不一定與類模板定義中指定的名字相同。
六、類模板的編譯模式
1.包含編譯模式
這種編譯模式下,類模板的成員函數(shù)和靜態(tài)成員的定義必須被包含在“要將它們實例化”的所有文件中,如果一個成員函數(shù)被定義在類模板定義之外,那么這些定義應(yīng)該被放在含有該類模板定義的頭文件中。
2.分離編譯模式
這種模式下,類模板定義和其inline成員函數(shù)定義被放在頭文件中,而非inline成員函數(shù)和靜態(tài)數(shù)據(jù)成員被放在程序文本文件中。
例:
1 //------Graphics.h--------- 2 3 export template<class type> 4 5 Class Graphics 6 7 {void Setup(const type &);}; 8 9 //-------Graphics.c------------ 10 11 #include “Graphics.h” 12 13 Template <class type> 14 15 Void Graphics<type>::Setup(const type &){…} 16 17 //------user.c----- 18 19 #include “Graphics.h” 20 21 Void main() 22 23 {Graphics<int> *pg=new Graphics<int>; 24 25 Int ival=1; 26 27 //Graphics<int>::Setup(const int &)的實例(下有注解) 28 29 Pg->Setup(ival); 30 31 }
Setup的成員定義在User.c中不可見,但在這個文件中仍可調(diào)用模板實例Graphics<int>::Setup(const int &)。為實現(xiàn)這一點,須將類模聲明為可導(dǎo)出的:當(dāng)它的成員函數(shù)實例或靜態(tài)數(shù)據(jù)成員實例被使用時,編譯器只要求模板的定義,它的聲明方式是在關(guān)鍵字template前加關(guān)鍵字export
3.顯式實例聲明
當(dāng)使用包含編譯模式時,類模板成員的定義被包含在使用其實例的所有程序文本文件中,何時何地編譯器實例化類模板成員的定義,我們并不能精確地知曉,為解決這個問題,標(biāo)準(zhǔn)C++提供了顯式實例聲明:關(guān)鍵字template后面跟著關(guān)鍵字class以及類模板實例的名字。
例:
1 #include “Graphics.h” 2 3 Template class Graphics<int>;//顯式實例聲明
顯式實例化類模板時,它的所有成員也被顯式實例化。
七、類模板的特化及部分特化
1.類模板的特化
先看下面的例子:
1 Template<class type> 2 3 Class Graphics{ 4 5 Public:void out(type figure){…}}; 6 7 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ù)通用模板定義被實例化,而在其他文件中卻針對同一組模板實參被特化。
2.類模板部分特化
如果模板有一個以上的模板參數(shù),則有些人就可能希望為一個特定的模板實參或者一組模板實參特化類模板,而不是為所有的模板參數(shù)特化該類模板。即,希望提供這樣一個模板:它仍然是一個通用的模板,只不過某些模板參數(shù)已經(jīng)被實際的類型或值取代。通過使用類模板部分特化,可以實現(xiàn)這一點。
例:
1 template<int hi,int wid> 2 3 Class Graphics{…}; 4 5 Template<int hi>//類模板的部分特化 6 7 Class Graphics<hi,90>{…};
格式:template<模板參數(shù)表>
注意:
①部分特化的模板參數(shù)表只列出模板實參仍然未知的那些參數(shù)。
②類模板部分特化是被隱式實例化的。編譯器選擇“針對該實例而言最為特化的模板定義”進(jìn)行實例化,當(dāng)沒有特化可被使用時,才使用通用模板定義。
例:Graphics<24,90> figure;
它即能從通用類模板定義被實例化,也能從部分特化的定義被實例化,但編譯器選擇的是部分特化來實例化模板。
③類模板部分特化必須有它自己對成員函數(shù)、靜態(tài)數(shù)據(jù)成員和嵌套類的定義。
八、名字空間和類模板
類模板定義也可以被放在名字空間中。例如:
1 Namespace cplusplus_primer{ 2 3 Template<class type> 4 5 Class Graphics{…}; 6 7 Template<class type> 8 9 Type create() 10 11 {…} 12 13 }
當(dāng)類模板名字Graphics被用在名字空間之外時,它必須被名字空間名cplusplus_primer限定修,或者通過一個using聲明或指示符被引入。例如:
1 Void main() 2 3 { 4 5 using cplusplus_primer::Graphics; 6 7 Graphics<int> *pg=new Graphics<int>; 8 9 }
注意:在名字空間中聲明類模板也會影響該類模板及其成員的特化和部分特化聲明的方式,類模板或類模板成員的特化聲明必須被聲明在定義通用模板的名字空間中(可以在名字空間之外定義模板特化)。
一個關(guān)于隊列的例子,下面將其代碼整理如下:

1 #include "iostream.h" 2 3 template <class Type> class QueueItem; 4 5 template <class Type> 6 7 class Queue { 8 9 public: 10 11 friend ostream& operator<<(ostream &os,const Queue<Type> &q); 12 13 Queue() : front( 0 ), back ( 0 ) { } 14 15 ~Queue(){} 16 17 void add( const Type & ); 18 19 bool is_empty() const 20 21 { 22 23 return front == 0; 24 25 } 26 27 Type remove(); 28 29 private: 30 31 QueueItem<Type> *front; 32 33 QueueItem<Type> *back; 34 35 }; 36 37 template <class Type> 38 39 class QueueItem 40 41 { 42 43 public: 44 45 QueueItem(Type val){item=val;next=0;} 46 47 friend class Queue<Type>; 48 49 friend ostream& operator<<(ostream &os,const Queue<Type> &q); 50 51 friend ostream& operator<<(ostream &os,const QueueItem<Type> &qi); 52 53 54 55 private: 56 57 Type item; 58 59 QueueItem *next; 60 61 }; 62 63 template <class Type> 64 65 void Queue<Type>::add(const Type &val) 66 67 { 68 69 QueueItem<Type> *pt =new QueueItem<Type>(val); 70 71 if ( is_empty() ) 72 73 front = back = pt; 74 75 else 76 77 { 78 79 back->next = pt; 80 81 back = pt; 82 83 } 84 85 } 86 87 template <class Type> 88 89 Type Queue<Type>::remove() 90 91 { 92 93 if ( is_empty() ) 94 95 { 96 97 cerr << "remove() on empty queue \n"; 98 99 exit(-1); 100 101 } 102 103 QueueItem<Type> *pt = front; 104 105 front = front->next; 106 107 Type retval = pt->item; 108 109 delete pt; 110 111 return retval; 112 113 } 114 115 template <class Type> 116 117 ostream& operator<<(ostream &os, const Queue<Type> &q) //輸出隊列成員 118 119 { 120 121 os << "< "; 122 123 QueueItem<Type> *p; 124 125 for ( p = q.front; p; p = p->next ) 126 127 os << *p << “ ;//用到了Queue和QueueItem的私有成員,因此需將此運算符重 128 129 //載函數(shù)聲明為Queue和QueueItem的友元,書上沒有將此函數(shù)聲明為QueueItem 130 131 os << “ >”;//的友元。 132 133 return os; 134 135 } 136 137 template <class Type> 138 139 ostream& operator<< ( ostream &os, const QueueItem<Type> &qi ) 140 141 { 142 143 os << qi.item;//用到了QueueItem的私有成員,因此需將此運算符重載函數(shù)聲明 144 145 //為QueueItem的友元 146 147 return os; 148 149 } 150 151 void main() 152 153 { 154 155 Queue<int> qi; 156 157 cout << qi << endl; 158 159 int ival; 160 161 for ( ival = 0; ival < 10; ++ival ) 162 163 qi.add( ival ); 164 165 cout << qi << endl; 166 167 int err_cnt = 0; 168 169 for ( ival = 0; ival < 10; ++ival ) { 170 171 int qval = qi.remove(); 172 173 if ( ival != qval ) err_cnt++; 174 175 } 176 177 cout << qi << endl; 178 179 if ( !err_cnt ) 180 181 cout << "!! queue executed ok\n"; 182 183 else cout << “?? queue errors: " << err_cnt << endl; 184 185 }

運行結(jié)果
|