天天看點

C++中的類模闆詳細講述

原文位址為: C++中的類模闆詳細講述

一、類模闆定義及執行個體化

. 定義一個類模闆:

View Code

1 template<class 模闆參數表>
2 
3 class 類名{
4 
5 // 類定義......
6 
7 };      

其中,template 是聲明類模闆的關鍵字,表示聲明一個模闆,模闆參數可以是一個,也可以是多個,可以是

類型參數

,也可以是

非類型參數。

類型參數由關鍵字class或typename及其後面的辨別符構成。非類型參數由一個普通參數構成,代表模闆定義中的一個常量。

例:

View Code

1 template<class type,int width>
2 
3 //type為類型參數,width為非類型參數
4 
5 class Graphics;      
注意: (1)

如果在全局域中聲明了與模闆參數同名的變量,則該變量被隐藏掉。

(2)

模闆參數名不能被當作類模闆定義中類成員的名字。

(3)

同一個模闆參數名在模闆參數表中隻能出現一次。

(4)

在不同的類模闆或聲明中,模闆參數名可以被重複使用。

View Code

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;//錯誤:成員名不能與模闆參數type同名
12 
13 };
14 
15 template<class type,class type>//錯誤:重複使用名為type的參數
16 
17 class Rect;
18 
19 template<class type> //參數名”type”在不同模闆間可以重複使用
20 
21 class Round;      
(5)

在類模闆的前向聲明和定義中,模闆參數的名字可以不同。

View Code

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)

類模闆參數可以有預設實參,給參數提供預設實參的順序是先右後左。

View Code

1 template <class type, int size = 1024>
2 
3 class Image;
4 
5 template <class type=double, int size >
6 
7 class Image;      
(7)

類模闆名可以被用作一個類型訓示符。當一個類模闆名被用作另一個模闆定義中的類型訓示符時,必須指定完整的實參表

View Code

1 template<class type>
 2 
 3 class Graphics
 4 
 5 {
 6 
 7 Graphics *next;//在類模闆自己的定義中不需指定完整模闆參數表
 8 
 9 };
10 
11 template <calss type>
12 
13 void show(Graphics<type> &g)
14 
15 {
16 
17 Graphics<type> *pg=&g;//必須指定完整的模闆參數表
18 
19 }      

2. 類模闆執行個體化

定義:從通用的類模闆定義中生成類的過程稱為模闆執行個體化。
C++中的類模闆詳細講述
例:Graphics<int> gi;

類模闆什麼時候會被執行個體化呢?

當使用了類模闆執行個體的名字,并且上下文環境要求存在類的定義時。

對象類型是一個類模闆執行個體,當對象被定義時。此點被稱作類的

執行個體化點

一個指針或引用指向一個類模闆執行個體,當檢查這個指針或引用所指的對象時。

例:

View Code

1 template<class Type>
 2 
 3 class Graphics{};
 4 
 5 void f1(Graphics<char>);// 僅是一個函數聲明,不需執行個體化
 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);//需要執行個體化,因為傳遞給函數f1的是一個Graphics<int>對象。
22 
23   int iobj=sizeof(Graphics<string>);//需要執行個體化,因為sizeof會計算Graphics<string>對象的大小,為了計算大小,編譯器必須根據類模闆定義産生該類型。
24 
25 }      

3. 非類型參數的模闆實參

要點

綁定給非類型參數的表達式必須是一個常量表達式。

從模闆實參到非類型模闆參數的類型之間允許進行一些轉換。包括左值轉換、限定修飾轉換、提升、整值轉換。

可以被用于非類型模闆參數的模闆實參的種類有一些限制。

例:

View Code

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;//錯誤不能通過隐式轉換把0轉換成指針值
10 
11 const double db=3.1415;
12 
13 Rect<double,db> fa1;//錯誤:不能将const double轉換成int.
14 
15 unsigned int fasize=255;
16 
17 Rect<String, fasize> fa2;//錯誤:非類型參數的實參必須是常量表達式,将unsigned改為const就正确。
18 
19 Int arr[10];
20 
21 Graphics<arr> gp;//正确      

二、類模闆的成員函數

要點:

類模闆的成員函數可以在類模闆的定義中定義(inline函數),也可以在類模闆定義之外定義(此時成員函數定義前面必須加上template及模闆參數)。

類模闆成員函數本身也是一個模闆,類模闆被執行個體化時它并不自動被執行個體化,隻有當它被調用或取位址,才被執行個體化。

View Code

1 template<class type>
 2 
 3 Class Graphics{
 4 
 5 Graphics(){…}//成員函數定義在類模闆的定義中
 6 
 7 void out();
 8 
 9 };
10 
11 template<class type>//成員函數定義在類模闆定義之外
12 
13 void Graphics<type>::out(){…}      

三、類模闆的友元聲明

類模闆中可以有三種友元聲明:

. 非模闆友元類或友元函數

View Code

1 class Graphics{void out();};
 2 
 3 Template<class T>
 4 
 5 Class Rect{
 6 
 7 friend class Graphics;//類Graphics、函數
 8 
 9 friend void create();// create、 out是類模闆
10 
11 friend void Graphics::out();// Rect所有執行個體的友元
12 
13 };      

2 、綁定的友元類模闆或函數模闆。

3 、非綁定的友元類模闆或函數模闆。

第二種聲明表示類模闆的執行個體和它的友元之間是一種一對一的映射關系。

如圖:

C++中的類模闆詳細講述

第三種聲明表示類模闆的執行個體和它的友元之間是一種一對多的映射關系。

如圖:

C++中的類模闆詳細講述

例:綁定的友元模闆

View Code

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 };      

例:非綁定的友元模闆

View Code

1 template<class type>
2 
3 class Graphics{
4 
5 template<class T>
6 
7 friend void create(Graphics<T>);
8 
9 };      
注意

當把非模闆類或函數聲明為類模闆友元時,它們不必在全局域中被聲明或定義,但将一個類的成員聲明為類模闆友元,該類必須已經被定義,另外在聲明綁定的友元類模闆或函數模闆時,該模闆也必須先聲明。

例:

View Code

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{};      

四、類模闆的靜态資料成員、嵌套類型

. 類模闆的靜态資料成員

要點:

靜态資料成員的模闆定義必須出現在類模闆定義之外。

類模闆靜态資料成員本身就是一個模闆,它的定義不會引起記憶體被配置設定,隻有對其執行個體化才會配置設定記憶體。

當程式使用靜态資料成員時,它被執行個體化,每個靜态成員執行個體都與一個類模闆執行個體相對應,靜态成員的執行個體引用要通過一個類模闆執行個體。

例:

View Code

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 //靜态成員定義分為兩部分:前一部分是類型,比如Graphics<type>*,後一部分是名稱和值,比如Graphics<type>::next=0;      

2. 類模闆的嵌套類型

要點

在類模闆中允許再嵌入模闆,是以類模闆的嵌套類也是一個模闆,它可以使用外圍類模闆的模闆參數。

當外圍類模闆被執行個體化時,它不會自動被執行個體化,隻有當上下文需要它的完整類類型時,它才會被執行個體化。

公有嵌套類型可以被用在類定義之外,這時它的名字前必須加上類模闆執行個體的名字。

例:

View Code

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 及模闆參 數表。 要點:

在一個類模闆中定義一個成員模闆,意味着該類模闆的一個執行個體包含了可能無限多個嵌套類和無限多個成員函數.

隻有當成員模闆被使用時,它才被執行個體化.

成員模闆可以定義在其外圍類或類模闆定義之外.

例:

View Code

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>      

注意:類模闆參數不一定與類模闆定義中指定的名字相同。

六、類模闆的編譯模式

1. 包含編譯模式

這種編譯模式下,類模闆的成員函數和靜态成員的定義必須被包含在“要将它們執行個體化”的所有檔案中,如果一個成員函數被定義在類模闆定義之外,那麼這些定義應該被放在含有該類模闆定義的頭檔案中。

2. 分離編譯模式

這種模式下,類模闆定義和其inline成員函數定義被放在頭檔案中,而非inline成員函數和靜态資料成員被放在程式文本檔案中。

例:

View Code

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中不可見,但在這個檔案中仍可調用模闆執行個體Graphics<int>::Setup(const int &)。為實作這一點,須将類模聲明為可導出的:

當它的成員函數執行個體或靜态資料成員執行個體被使用時,編譯器隻要求模闆的定 義,它的聲明方式是在關鍵字 template 前加關鍵字 export

. 顯式執行個體聲明

當使用包含編譯模式時,類模闆成員的定義被包含在使用其執行個體的所有程式文本檔案中,何時何地編譯器執行個體化類模闆成員的定義,我們并不能精确地知曉,為解決這個問題,标準C++提供了顯式執行個體聲明:關鍵字template後面跟着關鍵字class以及類模闆執行個體的名字。

例:

View Code

1 #include “Graphics.h”
2 
3 Template class Graphics<int>;//顯式執行個體聲明      
顯式執行個體化類模闆時,它的所有成員也被顯式執行個體化。

七、類模闆的特化及部分特化

1. 類模闆的特化

先看下面的例子:

View Code

1 Template<class type>
2 
3 Class Graphics{
4 
5 Public:void out(type figure){…}};
6 
7 Class Rect{…};      

如果模闆實參是Rect類型,我們不希望使用類模闆Graphics的通用成員函數定義,來執行個體化成員函數out(),我們希望專門定義Graphics<Rect>::out()執行個體,讓它使用Rect裡面的成員函數。

為此,我們可以通過一個顯示特化定義,為類模闆執行個體的一個成員提供一個特化定義。

格式: template <> 成員函數特化定義

下面為類模闆執行個體Graphics<Rect>的成員函數out()定義了顯式特化:

Template<> void Graphics<Rect>::out(Rect figure){…}
注意:

隻有當通用類模闆被聲明後,它的顯式特化才可以被定義。

若定義了一個類模闆特化,則必須定義與這個特化相關的所有成員函數或靜态資料成員,此時類模闆特化的成員定義不能以符号template<>作為打頭。(template<>被省略)

類模闆不能夠在某些檔案中根據通用模闆定義被執行個體化,而在其他檔案中卻針對同一組模闆實參被特化。

2. 類模闆部分特化

如果模闆有一個以上的模闆參數,則有些人就可能希望為一個特定的模闆實參或者一組模闆實參特化類模闆,而不是為所有的模闆參數特化該類模闆。即,希望提供這樣一個模闆:它仍然是一個通用的模闆,隻不過某些模闆參數已經被實際的類型或值取代。通過使用類模闆部分特化,可以實作這一點。

例:

View Code

1 template<int hi,int wid>
2 
3 Class Graphics{…};
4 
5 Template<int hi>//類模闆的部分特化
6 
7 Class Graphics<hi,90>{…};      
格式: template<模闆參數表> 注意:

部分特化的模闆參數表隻列出模闆實參仍然未知的那些參數。

類模闆部分特化是被隐式執行個體化的。編譯器選擇“針對該執行個體而言最為特化的模闆定義”進行執行個體化,當沒有特化可被使用時,才使用通用模闆定義。

例:Graphics<24,90> figure;

它即能從通用類模闆定義被執行個體化,也能從部分特化的定義被執行個體化,但編譯器選擇的是部分特化來執行個體化模闆。

類模闆部分特化必須有它自己對成員函數、靜态資料成員和嵌套類的定義。

八、名字空間和類模闆

類模闆定義也可以被放在名字空間中。例如:

View Code

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 }      

當類模闆名字Graphics被用在名字空間之外時,它必須被名字空間名cplusplus_primer限定修,或者通過一個using聲明或訓示符被引入。例如:

View Code

1 Void main()
2 
3 {
4 
5 using cplusplus_primer::Graphics;
6 
7 Graphics<int> *pg=new Graphics<int>;
8 
9 }      
注意:

在名字空間中聲明類模闆也會影響該類模闆及其成員的特化和部分特化聲明的方式,類模闆或類模闆成員的特化聲明必須被聲明在定義通用模闆的名字空間中(可以在名字空間之外定義模闆特化)。

一個關于隊列的例子,下面将其代碼整理如下:

View Code

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 //載函數聲明為Queue和QueueItem的友元,書上沒有将此函數聲明為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的私有成員,是以需将此運算符重載函數聲明
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 }      

運作結果

轉載請注明本文位址: C++中的類模闆詳細講述

繼續閱讀