C++:模板
模板(Template)指C++程序設(shè)計(jì)設(shè)計(jì)語言中采用類型作為參數(shù)的程序設(shè)計(jì),支持通用程序設(shè)計(jì)。C++ 的標(biāo)準(zhǔn)庫提供許多有用的函數(shù)大多結(jié)合了模板的觀念,如STL以及IO Stream。模板是C++支持參數(shù)化多態(tài)的工具,使用模板可以使用戶為類或者函數(shù)聲明一種一般模式,使得類中的某些數(shù)據(jù)成員或者成員函數(shù)的參數(shù)、返回值取得任意類型(這樣可以避免受類型限制,可以為多種類型的變量參數(shù),提供統(tǒng)一的函數(shù)模板等)。使用模板的目的就是能夠讓程序員編寫與類型無關(guān)的代碼。
模板是c++支持參數(shù)化多態(tài)的工具,使用模板可以使用戶為類或者函數(shù)聲明一種一般模式,使得類中的某些數(shù)據(jù)成員或者成員函數(shù)的參數(shù)、返回值取得任意類型。因此可以說,模板是一種對(duì)類型進(jìn)行參數(shù)化的工具,通常有兩種形式:函數(shù)模板和類模板;1.函數(shù)模板 針對(duì)僅參數(shù)類型不同的函數(shù);2.類模板針對(duì)僅數(shù)據(jù)成員和成員函數(shù)類型不同的類。PS:模板的聲明或定義只能在全局,命名空間或類范圍內(nèi)進(jìn)行。不能再局部范圍,函數(shù)內(nèi)進(jìn)行。
template<class T>? 和? template<typename T>?都可以用來定義函數(shù)模板和類模板,在使用上,他們倆沒有本質(zhì)的區(qū)別。(class用于定義類,在模板引入c++后,最初定義模板的方法為:template,這里class關(guān)鍵字表明T是一個(gè)類型。后來為了避免class在這兩個(gè)地方的使用可能給人帶來混淆,所以引入了typename這個(gè)關(guān)鍵字。它的作用同class一樣表明后面的符號(hào)為一個(gè)類型。)PS:其中template和class是關(guān)鍵字,class可以用typename 關(guān)鍵字代替,在這里typename 和class沒區(qū)別
1----- 函數(shù)模板? 格式如下:
template< class 形參名,class 形參名,......> 返回類型 函數(shù)名(參數(shù)列表)? ?{ 函數(shù)體 }
template <class 形參名,class 形參名,......> 返回類型 函數(shù)名(參數(shù)列表){函數(shù)體}
<>括號(hào)中的參數(shù)叫模板形參,模板形參和函數(shù)形參很相像,模板形參不能為空。一但聲明了模板函數(shù)就可以用模板函數(shù)的形參名聲明類中的成員變量和成員函數(shù),即可以在該函數(shù)中使用內(nèi)置類型的地方都可以使用模板形參名。模板形參需要調(diào)用該模板函數(shù)時(shí)提供的模板實(shí)參來初始化模板形參,一旦編譯器確定了實(shí)際的模板實(shí)參類型就稱他實(shí)例化了函數(shù)模板的一個(gè)實(shí)例。比如swap的模板函數(shù)形式為:
template <class T> void swap(T& a, T& b){},
當(dāng)調(diào)用這樣的模板函數(shù)時(shí)類型T就會(huì)被被調(diào)用時(shí)的類型所代替,比如swap(a,b)其中a和b是int 型,這時(shí)模板函數(shù)swap中的形參T就會(huì)被int 所代替,模板函數(shù)就變?yōu)閟wap(int &a, int &b)。而當(dāng)swap(c,d)其中c和d是double類型時(shí),模板函數(shù)會(huì)被替換為swap(double &a, double &b),這樣就實(shí)現(xiàn)了函數(shù)的實(shí)現(xiàn)與類型無關(guān)的代碼。
注意:對(duì)于函數(shù)模板而言不存在 h(int,int) 這樣的調(diào)用,不能在函數(shù)調(diào)用的參數(shù)中指定模板形參的類型,對(duì)函數(shù)模板的調(diào)用應(yīng)使用實(shí)參推演來進(jìn)行,即只能進(jìn)行 h(2,3) 這樣的調(diào)用,或者先定義再使用例如int a, b; h(a,b)。
2----- 類模板? 格式如下:
template< class 形參名,class 形參名,......> class 類名 {...};
舉個(gè)例子:template <class T> class A { public:? ?T a; T b;???T hy(T c, T &d); };
在類A中聲明了兩個(gè)類型為T的成員變量a和b,還聲明了一個(gè)返回類型為T帶兩個(gè)參數(shù)類型為T的函數(shù)hy。
template<class 形參名,class 形參名,…>class 類名{ ...類聲明體 };
類模板和函數(shù)模板都是以template開始后接模板形參列表組成,模板形參不能為空,一但聲明了類模板就可以用類模板的形參名聲明類中的成員變量和成員函數(shù),即可以在類中使用內(nèi)置類型的地方都可以使用模板形參名來聲明。如下,表示定義一個(gè)名為A的類模板,其中帶類型參數(shù)T。
template<class T> class A{public: T a;T b; T hy(T c, T &d);};
2.1 類模板對(duì)象的創(chuàng)建:
比如一個(gè)模板類A,則使用類模板創(chuàng)建對(duì)象的方法為A m;在類A后面跟上一個(gè)<>尖括號(hào)并在里面填上相應(yīng)的類型,這樣的話類A中凡是用到模板形參的地方都會(huì)被int 所代替。當(dāng)類模板有兩個(gè)模板形參時(shí)創(chuàng)建對(duì)象的方法為A<int, double> m;類型之間用逗號(hào)隔開。
對(duì)于類模板,模板形參的類型必須在類名后的尖括號(hào)中明確指定。比如A<2> m;用這種方法把模板形參設(shè)置為int是錯(cuò)誤的(編譯錯(cuò)誤:error C2079: ‘a(chǎn)’ uses undefined class ‘A’),類模板形參不存在實(shí)參推演的問題(PS:此處不同于函數(shù)模板)。也就是說不能把整型值2推演為int 型傳遞給模板形參。要把類模板形參調(diào)置為int 型必須這樣指定A m。
與函數(shù)模板不同的是:函數(shù)模板的實(shí)例化是由編譯程序在處理函數(shù)調(diào)用時(shí)自動(dòng)完成的,而類模板的實(shí)例化必須由程序員在程序中顯式地指定,
其實(shí)例化的一般形式是:
類名 <數(shù)據(jù)類型 1(或數(shù)據(jù)),數(shù)據(jù)類型 2(或數(shù)據(jù))…> 對(duì)象名
例如:Smemory<int> mol;
表示將類模板Smemory的類型參數(shù)T全部替換成int 型,從而創(chuàng)建一個(gè)具體的類,并生成該具體類的一個(gè)對(duì)象mol。
2.2?在類模板外部定義成員函數(shù)的方法為:
template<模板形參列表> 函數(shù)返回類型 類名<模板形參名>::函數(shù)名(參數(shù)列表){函數(shù)體}
/*
在類模板的外部定義類成員函數(shù)的一般形式是:template <類型名 參數(shù)名1,類型名參數(shù)名2,…>函數(shù)返回值類型 類名<參數(shù)名 1 參數(shù)名 2,…>::成員函數(shù)名(形參表){函數(shù)體}
*/
//例如:
template <class T>void Smemory<T>::mput(T x){…}
表示定義一個(gè)類模板Smemory的成員函數(shù),函數(shù)名為mput,形參x的類型是T,函數(shù)無返回值。
類模板是一個(gè)類家族的抽象,它只是對(duì)類的描述,編譯程序不為類模板(包括成員函數(shù)定義)創(chuàng)建程序代碼,但是通過對(duì)類模板的實(shí)例化可以生成一個(gè)具體的類以及該具體類的對(duì)象。
比如有兩個(gè)模板形參T1,T2的類A中含有一個(gè)void h()函數(shù),則定義該函數(shù)的語法為:
template<class T1,class T2> void A<T1,T2>::h(){}。
注意:當(dāng)在類外面定義類的成員時(shí)template后面的模板形參應(yīng)與要定義的類的模板形參一致。
5、再次提醒注意:模板的聲明或定義只能在全局,命名空間或類范圍內(nèi)進(jìn)行。即不能在局部范圍,函數(shù)內(nèi)進(jìn)行,比如不能在main函數(shù)中聲明或定義一個(gè)模板
下面舉例說明類模板的定義和使用方法。 ? ? 例:類模板的定義和使用
/*
說明:類模板Smemory帶一個(gè)類型參數(shù)T,T是代表數(shù)據(jù)類型的參數(shù),在類型參數(shù)前面必須加關(guān)鍵字class,用
class表示類型參數(shù)的類型。在實(shí)例化時(shí),對(duì)應(yīng)類型參數(shù)T必須是具體的數(shù)據(jù)類型名,這里建立第一個(gè)對(duì)象時(shí)數(shù)
據(jù)類型名是int,表示將類模板Smemory中的所有類型參數(shù)T都替換成int型, 從而創(chuàng)建一個(gè)具體的類及其對(duì)象
mo1;在建立第二個(gè)對(duì)象時(shí)數(shù)據(jù)類型名是char,表示將模板Smemory中的所有類型參數(shù)T都替換成char,從而創(chuàng)
建一個(gè)具體的類及其對(duì)象mo2。
*/
#include <iostream.h>#include <conio.h>const int SIZE = 8;template <class T>class Smemory //定義類模板Smemory
{T data[SIZE]; //類型為T,長度為SIZE的數(shù)組data[]為數(shù)據(jù)成員int count;public:Smemory( ){count = 0;}void mput(T x); //mput()函數(shù)的參數(shù)x的類型是TT mget( ); //聲明返回值類型為T的成員函數(shù)mget()
};template <class T>void Smemory<T>::mput(T x) //定義成員函數(shù)mput(),函數(shù)的參數(shù)類型為T,該函數(shù)用于為數(shù)據(jù)成員 data數(shù)組的各個(gè)元素賦值{if(count == 8){cout << "Memory is full";return;}data[count] = x;count++;
}template <class T>T Smemory<T>::mget( ) //定義成員函數(shù)mget(),函數(shù)的返回類型為T,該函數(shù)用于取出數(shù)據(jù)成員 data數(shù)組的各個(gè)元素
{if(count == 0){cout << "Memory is empty";return 0;}count--;return data[count];}void main( ){Smemory<int> mo1;//將Smemory實(shí)例化,并創(chuàng)建對(duì)象mo1int i;char ch = 'A'; Smemory<char> mo2; //將Smemory實(shí)例化,并創(chuàng)建對(duì)象mo2for(i = 0; i < 8; i++){mo1.mput(i);//調(diào)用成員函數(shù)mput()mo2.mput(ch);ch++; //調(diào)用成員函數(shù)mput()}cout << "Get mo1 => ";for(i = 0; i < 8; i++)cout << mo1.mget( );//調(diào)用成員函數(shù)mget()cout << "\nGet mo2 => ";for(i = 0; i < 8; i++)cout << mo2.mget( ); //調(diào)用成員函數(shù)mget()getch(); }
參考博客:https://blog.csdn.net/u010984552/article/details/51678781
以下是擴(kuò)展:
3.模板的非類型形參
1 、非類型模板形參:模板的非類型形參也就是內(nèi)置類型形參,如template<class T, int a> class B{};其中int a就是非類型的模板形參。
2、 非類型形參在模板定義的內(nèi)部是常量值,也就是說非類型形參在模板的內(nèi)部是常量。
3、 非類型模板的形參只能是整型,指針和引用,像double,String, String **這樣的類型是不允許的。但是double &,double *,對(duì)象的引用或指針是正確的。
4、 調(diào)用非類型模板形參的實(shí)參必須是一個(gè)常量表達(dá)式,即他必須能在編譯時(shí)計(jì)算出結(jié)果。
5 、注意:任何局部對(duì)象,局部變量,局部對(duì)象的地址,局部變量的地址都不是一個(gè)常量表達(dá)式,都不能用作非類型模板形參的實(shí)參。全局指針類型,全局變量,全局對(duì)象也不是一個(gè)常量表達(dá)式,不能用作非類型模板形參的實(shí)參。
6、 全局變量的地址或引用,全局對(duì)象的地址或引用const類型變量是常量表達(dá)式,可以用作非類型模板形參的實(shí)參。
7 、sizeof表達(dá)式的結(jié)果是一個(gè)常量表達(dá)式,也能用作非類型模板形參的實(shí)參。
8 、當(dāng)模板的形參是整型時(shí)調(diào)用該模板時(shí)的實(shí)參必須是整型的,且在編譯期間是常量,比如template <class T, int a> class A{};如果有int b,這時(shí)A<int, b> m;將出錯(cuò),因?yàn)閎不是常量,如果const int b,這時(shí)A<int, b> m;就是正確的,因?yàn)檫@時(shí)b是常量。
9 、非類型形參一般不應(yīng)用于函數(shù)模板中,比如有函數(shù)模板template<class T, int a> void h(T b){},若使用h(2)調(diào)用會(huì)出現(xiàn)無法為非類型形參a推演出參數(shù)的錯(cuò)誤,對(duì)這種模板函數(shù)可以用顯示模板實(shí)參來解決,如用h<int, 3>(2)這樣就把非類型形參a設(shè)置為整數(shù)3。顯示模板實(shí)參在后面介紹。
10、 非類型模板形參的形參和實(shí)參間所允許的轉(zhuǎn)換
1、允許從數(shù)組到指針,從函數(shù)到指針的轉(zhuǎn)換。如:template <int *a> class A{}; int b[1]; ?A 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 class A{}; const short b=2; A m; 即從short到int 的提升轉(zhuǎn)換
4、整值轉(zhuǎn)換。如:template class A{}; ? A<3> m; 即從int 到unsigned int的轉(zhuǎn)換。
5、常規(guī)轉(zhuǎn)換。
4.類模板的默認(rèn)模板類型形參
1、可以為類模板的類型形參提供默認(rèn)值,但不能為函數(shù)模板的類型形參提供默認(rèn)值。函數(shù)模板和類模板都可以為模板的非類型形參提供默認(rèn)值。
2、類模板的類型形參默認(rèn)值形式為:template<class T1, class T2=int> class A{};為第二個(gè)模板類型形參T2提供int型的默認(rèn)值。
3、類模板類型形參默認(rèn)值和函數(shù)的默認(rèn)參數(shù)一樣,如果有多個(gè)類型形參則從第一個(gè)形參設(shè)定了默認(rèn)值之后的所有模板形參都要設(shè)定默認(rèn)值,比如templateclass A{};就是錯(cuò)誤的,因?yàn)門1給出了默認(rèn)值,而T2沒有設(shè)定。
4、在類模板的外部定義類中的成員時(shí)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(){}。
5.模板的實(shí)例化
總結(jié)一下,C++只有模板顯式實(shí)例化(explicit instantiation),隱式實(shí)例化(implicit instantiation),特化(specialization,也譯作具體化,偏特化)。首先考慮如下模板函數(shù)代碼:
[cpp] view plaincopyprint?
template <typename T>
void swap(T &a, T &b){
...
}
1.隱式實(shí)例化
我們知道,模板函數(shù)不是真正的函數(shù)定義,他只是如其名提供一個(gè)模板,模板只有在運(yùn)行時(shí)才會(huì)生成相應(yīng)的實(shí)例,隱式實(shí)例化就是這種情況:
[cpp] view plaincopyprint?
int main(){ .... swap<int>(a,b); ....
}
它會(huì)在運(yùn)行到這里的時(shí)候才生成相應(yīng)的實(shí)例,很顯然的影響效率
這里順便提一下swap(a,b);中的是可選的,因?yàn)榫幾g器可以根據(jù)函數(shù)參數(shù)類型自動(dòng)進(jìn)行判斷,也就是說如果編譯器不不能自動(dòng)判斷的時(shí)候這個(gè)就是必要的;
2.顯式實(shí)例化
前面已經(jīng)提到隱式實(shí)例化可能影響效率,所以需要提高效率的顯式實(shí)例化,顯式實(shí)例化在編譯期間就會(huì)生成實(shí)例,方法如下:
[cpp] view plaincopyprint?
template void swap<int>(int &a,int &b);
這樣就不會(huì)影響運(yùn)行時(shí)的效率,但編譯時(shí)間隨之增加。
3.特化
這個(gè)swap可以處理一些基本類型如long int double,但是如果想處理用戶自定義的類型就不行了,特化就是為了解決這個(gè)問題而出現(xiàn)的:
[cpp] view plaincopyprint?
template <> void swap<job>(job a,job b){...}
其中job是用戶定義的類型.
6.模板的特化(具體化)和偏特化
類模板:
測試代碼如下:#include <iostream>
using namespace std;
template<typename T1,typename T2>
class Test{
public:Test(T1 i,T2 j):a(i),b(j){cout<<"模板類"<<endl;}
private:T1 a;T2 b;
};
template<> //全特化,由于是全特化,參數(shù)都指定了,參數(shù)列表故為空。
class Test<int ,char>{
public:Test(int i,char j):a(i),b(j){cout<<"全特化"<<endl;}
private:int a;int b;
};
template<typename T2> //由于只指定了一部分參數(shù),剩下的未指定的需在參數(shù)列表中,否則報(bào)錯(cuò)。
class Test<char,T2>{
public:Test(char i,T2 j):a(j),b(j){cout<<"個(gè)數(shù)偏特化"<<endl;}
private:char a;T2 b;
};
template<typename T1,typename T2> //這是范圍上的偏特化
class Test<T1*,T2*>{
public:Test(T1* i,T2* j):a(i),b(j){cout<<"指針偏特化"<<endl;}
private:T1* a;T2* b;
};
template<typename T1,typename T2>//同理這也是范圍上的偏特化
class Test<T1 const,T2 const>{
public:Test(T1 i,T2 j):a(i),b(j){cout<<"const偏特化"<<endl;}
private:T1 a;T2 b;
};
int main()
{int a;Test<double,double> t1(0.1,0.2);Test<int,char> t2(1,'A');Test<char,bool> t3('A',true);Test<int*,int*> t4(&a,&a);Test<const int,const int> t5(1,2);return 0;
}
運(yùn)行結(jié)果圖:
函數(shù)模板:
而對(duì)于函數(shù)模板,卻只有全特化,不能偏特化:
#include <iostream>
using namespace std;
//模板函數(shù)
template<typename T1,typename T2>
void fun(T1 a,T2 b){cout<<"模板函數(shù)"<<endl;
}
//全特化
template<>
void fun(int a,char b){cout<<"全特化"<<endl;
}
//函數(shù)不存在偏特化,以下代碼是錯(cuò)誤的
/*
template<typename T2>
void fun(char a,T2 b){cout<<"偏特化"<<ednl;
}
*/
int main()
{int a=0;char b='A';fun(a,a);fun(a,b);return 0;
}
運(yùn)行結(jié)果:
7.模板類的繼承
模板類的繼承包括四種:
1.(普通類繼承模板類)
2.(模板類繼承了普通類(非常常見))
3.(類模板繼承類模板)
4.(模板類繼承類模板,即繼承模板參數(shù)給出的基類)
其中,普通類繼承模板類比較簡單,如
1 template<class T>
2 class TBase{
3 T data;
4 ……
5 };
6 class Derived:public TBase<int>{
7 ……
8 };
模板類繼承普通類:
1 class TBase{
2 ……
3 };
4 template<class T>
5 class TDerived:public TBase{
6 T data;
7 ……
8 };
類模板繼承類模板:
1 template<class T>2 class TBase{3 T data1;4 ……5 };6 template<class T1,class T2>7 class TDerived:public TBase<T1>{8 T2 data2;9 ……
10 };
模板類繼承模板參數(shù)給出的基類
——繼承哪個(gè)基類由模板參數(shù)決定
#include<iostream>
using namespace std;class BaseA{
public:BaseA(){cout<<"BaseA founed"<<endl;}
};class BaseB{
public:BaseB(){cout<<"BaseB founed"<<endl;}
};template<typename T, int rows>
class BaseC{
private:T data;
public:BaseC():data(rows){cout<<"BaseC founed "<< data << endl;}
};template<class T>
class Derived:public T{
public:Derived():T(){cout<<"Derived founed"<<endl;}
};void main()
{Derived<Base A> x;// BaseA作為基類Derived<Base B> y;// BaseB作為基類Derived<Base C<int, 3> > z; // BaseC<int,3>作為基類
模板實(shí)例化問題:
? ? ? ? 在我們使用類模板時(shí),只有當(dāng)代碼中使用了類模板的一個(gè)實(shí)例的名字,而且上下文環(huán)境要求必須存在類的定義時(shí),這個(gè)類模板才被實(shí)例化。
1.聲明一個(gè)類模板的指針和引用,不會(huì)引起類模板的實(shí)例化,因?yàn)闆]有必要知道該類的定義。
2.定義一個(gè)類類型的對(duì)象時(shí)需要該類的定義,因此類模板會(huì)被實(shí)例化。
3.在使用sizeof()時(shí),它是計(jì)算對(duì)象的大小,編譯器必須根據(jù)類型將其實(shí)例化出來,所以類模板被實(shí)例化.
4.new表達(dá)式要求類模板被實(shí)例化。
5.引用類模板的成員會(huì)導(dǎo)致類模板被編譯器實(shí)例化。
6.需要注意的是,類模板的成員函數(shù)本身也是一個(gè)模板。標(biāo)準(zhǔn)C++要求這樣的成員函數(shù)只有在被調(diào)用或者取地址的時(shí)候,才被實(shí)例化。用來實(shí)例化成員函數(shù)的類型,就是其成員函數(shù)要調(diào)用的那個(gè)類對(duì)象的類型
?
總結(jié)
- 上一篇: C++ 随笔
- 下一篇: C++ 容器1 vector