C++模板类详解
一、模板類定義及實例化
1. 定義一個模板類:
template<class 模板參數表>class 類名{// 類定義......};其中,template 是聲明模板類的關鍵字,表示聲明一個模板,模板參數可以是一個,也可以是多個,可以是類型參數 ,也可以是非類型參數。類型參數由關鍵字class或typename及其后面的標識符構成。非類型參數由一個普通參數構成,代表模板定義中的一個常量。
例:
template<class type,int width>//type為類型參數,width為非類型參數class Graphics;注意:
(1)如果在全局域中聲明了與模板參數同名的變量,則該變量被隱藏掉。
(2)模板參數名不能被當作模板類定義中類成員的名字。
(3)同一個模板參數名在模板參數表中只能出現一次。
(4)在不同的模板類或聲明中,模板參數名可以被重復使用。
typedef string type;template<class type,int width>class Graphics{type node;//node不是string類型typedef double type;//錯誤:成員名不能與模板參數type同名};template<class type,class type>//錯誤:重復使用名為type的參數class Rect;template<class type> //參數名”type”在不同模板間可以重復使用class Round;
(5)在模板類的前向聲明和定義中,模板參數的名字可以不同。
(6)類模板參數可以有缺省實參,給參數提供缺省實參的順序是先右后左。
(7)模板類名可以被用作一個類型指示符。當一個模板類名被用作另一個模板定義中的類型指示符時,必須指定完整的實參表
2.模板類實例化
定義:從通用的模板類定義中生成類的過程稱為模板實例化。
例:Graphics<int> gi;
模板類什么時候會被實例化呢?
①當使用了模板類實例的名字,并且上下文環境要求存在類的定義時。
②對象類型是一個模板類實例,當對象被定義時。此點被稱作類的實例化點。
③一個指針或引用指向一個模板類實例,當檢查這個指針或引用所指的對象時。
例:
template<class Type>class Graphics{};void f1(Graphics<char>);// 僅是一個函數聲明,不需實例化class Rect {Graphics<double>& rsd;// 聲明一個模板類引用,不需實例化Graphics<int> si;// si是一個Graphics類型的對象,需要實例化模板類}int main(){Graphcis<char>* sc;// 僅聲明一個模板類指針,不需實例化f1(*sc);//需要實例化,因為傳遞給函數f1的是一個Graphics<int>對象。int iobj=sizeof(Graphics<string>);//需要實例化,因為sizeof會計算Graphics<string>對象的大小,為了計算大小,編譯器必須根據模板類定義產生該類型。}3.非類型參數的模板實參
要點:
①綁定給非類型參數的表達式必須是一個常量表達式。
②從模板實參到非類型模板參數的類型之間允許進行一些轉換。包括左值轉換、限定修飾轉換、提升、整值轉換。
③可以被用于非類型模板參數的模板實參的種類有一些限制。
例:
template<int* ptr> class Graphics{……};template<class Type,int size> class Rect{……};const int size=1024;Graphics<&size> bp1;//錯誤:從const int*->int*是錯誤的。Graphics<0> bp2;//錯誤不能通過隱式轉換把0轉換成指針值const double db=3.1415;Rect<double,db> fa1;//錯誤:不能將const double轉換成int.unsigned int fasize=255;Rect<String, fasize> fa2;//錯誤:非類型參數的實參必須是常量表達式,將unsigned改為const就正確。Int arr[10];Graphics<arr> gp;//正確二、類模板的成員函數
要點:
①類模板的成員函數可以在類模板的定義中定義(inline函數),也可以在類模板定義之外定義(此時成員函數定義前面必須加上template及模板參數)。
②類模板成員函數本身也是一個模板,類模板被實例化時它并不自動被實例化,只有當它被調用或取地址,才被實例化。
Class Graphics{Graphics(){…}//成員函數定義在模板類的定義中void out();};template<class type>//成員函數定義在模板類定義之外void Graphics<type>::out(){…}
三、模板類的友元聲明
模板類中可以有三種友元聲明:
1.非模板友元類或友元函數
class Graphics{void out();};template<class T>Class Rect{friend class Graphics;//類Graphics、函數friend void create();// create、 out是類模板friend void Graphics::out();// Rect所有實例的友元};2、綁定的友元模板類或函數模板。
3、非綁定的友元模板類或函數模板。
第二種聲明表示模板類的實例和它的友元之間是一種一對一的映射關系。
如圖:
第三種聲明表示模板類的實例和它的友元之間是一種一對多的映射關系。
如圖:
例:綁定的友元模板
template<class type>void create(Graphics<type>);template<class type>class Graphics{friend void create<type>(Graphics<type>);};例:非綁定的友元模板
template<class type>class Graphics{template<class T>friend void create(Graphics<T>);};
注意:當把非模板類或函數聲明為模板類友元時,它們不必在全局域中被聲明或定義,但將一個類的成員聲明為模板類友元,該類必須已經被定義,另外在聲明綁定的友元模板類或函數模板時,該模板也必須先聲明。
例:
template <class T>class A {private:friend class B<T>; //錯誤:類B必須先聲明};template <class T>class B{};四、類模板的靜態數據成員、嵌套類型
1.類模板的靜態數據成員
要點:
①靜態數據成員的模板定義必須出現在模板類定義之外。
②模板類靜態數據成員本身就是一個模板,它的定義不會引起內存被分配,只有對其實例化才會分配內存。
③當程序使用靜態數據成員時,它被實例化,每個靜態成員實例都與一個模板類實例相對應,靜態成員的實例引用要通過一個模板類實例。
例:
template<class type>class Graphics{static Graphics *next;static const type item;};template<class type>Graphics<type> * Graphics<type>::next=0;template<class type>type Graphics<type>::item=NULL;//靜態成員定義分為兩部分:前一部分是類型,比如Graphics<type>*,后一部分是名稱和值,比如Graphics<type>::next=0;2.模板類的嵌套類型
要點:
①在模板類中允許再嵌入模板,因此模板類的嵌套類也是一個模板,它可以使用外圍類模板的模板參數。
②當外圍模板類被實例化時,它不會自動被實例化,只有當上下文需要它的完整類類型時,它才會被實例化。
③公有嵌套類型可以被用在類定義之外,這時它的名字前必須加上模板類實例的名字。
例:
template<class type>class Graphics{public:template<class T>class Rect{void out(type a,T b);};};Graphics<int>::Rect<double> node;//引用公有嵌套類型必須加上模板類實例名字
五、成員模板
定義:成員定義前加上template及模板參數表。
要點:
①在一個模板類中定義一個成員模板,意味著該模板類的一個實例包含了可能無限多個嵌套類和無限多個成員函數.
②只有當成員模板被使用時,它才被實例化.
③成員模板可以定義在其外圍類或模板類定義之外.
例:
</pre><pre class="cpp" name="code">template<class type>class Graphics<type>{public:template<class T>class Rect{void out(type a,T b);}; };template<class Gtype> template<class TT>void Graphics<Gtype>::Rect<TT>::out(Gtype a,TT b){}//成員模板被定義在模板類定義之外(要跟上完整模板實參)Graphics<int>的實例可能包括下列嵌套類型:Graphics<int>::Rect<double>Graphics<int>::Rect<string>注意:模板類參數不一定與模板類定義中指定的名字相同。
?
六、模板類的編譯模式
1.包含編譯模式
這種編譯模式下,模板類的成員函數和靜態成員的定義必須被包含在“要將它們實例化”的所有文件中,如果一個成員函數被定義在模板類定義之外,那么這些定義應該被放在含有該模板類定義的頭文件中。
2.分離編譯模式
這種模式下,模板類定義和其inline成員函數定義被放在頭文件中,而非inline成員函數和靜態數據成員被放在程序文本文件中。
例:
</pre><pre class="cpp" name="code">//------Graphics.h---------export template<class type>Class Graphics{void Setup(const type &); };//-------Graphics.c------------#include “Graphics.h”template <class type>void Graphics<type>::Setup(const type &){…}//------user.c-----#include “Graphics.h”void main(){Graphics<int> *pg=new Graphics<int>;int ival=1;//Graphics<int>::Setup(const int &)的實例(下有注解)Pg->Setup(ival);}Setup的成員定義在User.c中不可見,但在這個文件中仍可調用模板實例Graphics<int>::Setup(const int &)。為實現這一點,須將模板類聲明為可導出的:當它的成員函數實例或靜態數據成員實例被使用時,編譯器只要求模板的定義,它的聲明方式是在關鍵字template前加關鍵字export
3.顯式實例聲明
當使用包含編譯模式時,模板類成員的定義被包含在使用其實例的所有程序文本文件中,何時何地編譯器實例化模板類成員的定義,我們并不能精確地知曉,為解決這個問題,標準C++提供了顯式實例聲明:關鍵字template后面跟著關鍵字class以及模板類實例的名字。
例:
#include “Graphics.h”template class Graphics<int>;//顯式實例聲明
顯式實例化模板類時,它的所有成員也被顯式實例化。
?
七、模板類的特化及部分特化
1.模板類的特化
先看下面的例子:
template<class type>Class Graphics{public: void out(type figure){…}};Class Rect{…};
如果模板實參是Rect類型,我們不希望使用模板類Graphics的通用成員函數定義,來實例化成員函數out(),我們希望專門定義Graphics<Rect>::out()實例,讓它使用Rect里面的成員函數。
為此,我們可以通過一個顯示特化定義,為模板類實例的一個成員提供一個特化定義。
格式:template<> 成員函數特化定義
下面為模板類實例Graphics<Rect>的成員函數out()定義了顯式特化:
Template<> void Graphics<Rect>::out(Rect figure){…}
注意:
①只有當通用模板類被聲明后,它的顯式特化才可以被定義。
②若定義了一個模板類特化,則必須定義與這個特化相關的所有成員函數或靜態數據成員,此時模板類特化的成員定義不能以符號template<>作為打頭。(template<>被省略)
③模板類不能夠在某些文件中根據通用模板定義被實例化,而在其他文件中卻針對同一組模板實參被特化。
2.模板類部分特化
如果模板有一個以上的模板參數,則有些人就可能希望為一個特定的模板實參或者一組模板實參特化類模板,而不是為所有的模板參數特化該類模板。即,希望提供這樣一個模板:它仍然是一個通用的模板,只不過某些模板參數已經被實際的類型或值取代。通過使用模板類部分特化,可以實現這一點。
例:
template<int hi,int wid>Class Graphics{…};template<int hi>//模板類的部分特化Class Graphics<hi,90>{…};
格式:template<模板參數表>
注意:
①部分特化的模板參數表只列出模板實參仍然未知的那些參數。
②模板類部分特化是被隱式實例化的。編譯器選擇“針對該實例而言最為特化的模板定義”進行實例化,當沒有特化可被使用時,才使用通用模板定義。
例:Graphics<24,90> figure;
它即能從通用模板類定義被實例化,也能從部分特化的定義被實例化,但編譯器選擇的是部分特化來實例化模板。
③模板類部分特化必須有它自己對成員函數、靜態數據成員和嵌套類的定義。
?
八、名字空間和模板類
模板類定義也可以被放在名字空間中。例如:
Namespace cplusplus_primer{template<class type>Class Graphics{…};template<class type>type create(){…}}當模板類名字Graphics被用在名字空間之外時,它必須被名字空間名cplusplus_primer限定修飾,或者通過一個using聲明或指示符被引入。例如:
Void main(){using cplusplus_primer::Graphics;Graphics<int> *pg=new Graphics<int>;}注意:在名字空間中聲明類模板也會影響該模板類及其成員的特化和部分特化聲明的方式,模板類或模板類成員的特化聲明必須被聲明在定義通用模板的名字空間中(可以在名字空間之外定義模板特化)。
一個關于隊列的例子,下面將其代碼整理如下:
#include "iostream.h"template <class Type> class QueueItem;template <class Type>class Queue {public:friend ostream& operator<<(ostream &os,const Queue<Type> &q);Queue() : front( 0 ), back ( 0 ) { }~Queue(){}void add( const Type & );bool is_empty() const{return front == 0;}Type remove();private:QueueItem<Type> *front;QueueItem<Type> *back;};template <class Type>class QueueItem{public:QueueItem(Type val){item=val;next=0;}friend class Queue<Type>;friend ostream& operator<<(ostream &os,const Queue<Type> &q);friend ostream& operator<<(ostream &os,const QueueItem<Type> &qi);private:Type item;QueueItem *next;};template <class Type>void Queue<Type>::add(const Type &val){QueueItem<Type> *pt =new QueueItem<Type>(val);if ( is_empty() )front = back = pt;else{back->next = pt;back = pt;}}template <class Type>Type Queue<Type>::remove(){if ( is_empty() ){cerr << "remove() on empty queue \n";exit(-1);}QueueItem<Type> *pt = front;front = front->next;Type retval = pt->item;delete pt;return retval;}template <class Type>ostream& operator<<(ostream &os, const Queue<Type> &q) //輸出隊列成員{os << "< ";QueueItem<Type> *p;for ( p = q.front; p; p = p->next )os << *p << “ ;//用到了Queue和QueueItem的私有成員,因此需將此運算符重//載函數聲明為Queue和QueueItem的友元,書上沒有將此函數聲明為QueueItemos << “ >”;//的友元。return os;}template <class Type>ostream& operator<< ( ostream &os, const QueueItem<Type> &qi ){os << qi.item;//用到了QueueItem的私有成員,因此需將此運算符重載函數聲明//為QueueItem的友元return os;}void main(){Queue<int> qi;cout << qi << endl;int ival;for ( ival = 0; ival < 10; ++ival )qi.add( ival );cout << qi << endl;int err_cnt = 0;for ( ival = 0; ival < 10; ++ival ) {int qval = qi.remove();if ( ival != qval ) err_cnt++;}cout << qi << endl;if ( !err_cnt )cout << "!! queue executed ok\n";else cout << “?? queue errors: " << err_cnt << endl;}
運行結果
作者:Out Man
出處:http://www.cnblogs.com/assemble8086/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留作者信息,且在文章頁面明顯位置給出原文連接。
總結
- 上一篇: camera module cont
- 下一篇: 看了 72 位图灵奖得主成就,才发现我对