天天看點

c語言中的std模版用法,C++ 模闆詳解

模闆是C++支援參數化多态的工具,使用模闆可以使使用者為類或者函數聲明一種一般模式,使得類中的某些資料成員或者成員函數的參數、傳回值取得任意類型。

模闆是一種對類型進行參數化的工具;

通常有兩種形式:函數模闆和類模闆;

函數模闆針對僅參數類型不同的函數;

類模闆針對僅資料成員和成員函數類型不同的類。

使用模闆的目的就是能夠讓程式員編寫與類型無關的代碼。比如編寫了一個交換兩個整型int 類型的swap函數,這個函數就隻能實作int 型,對double,字元這些類型無法實作,要實作這些類型的交換就要重新編寫另一個swap函數。使用模闆的目的就是要讓這程式的實作與類型無關,比如一個swap模闆函數,即可以實作int 型,又可以實作double型的交換。模闆可以應用于函數和類。下面分别介紹。

注意:模闆的聲明或定義隻能在全局,命名空間或類範圍内進行。即不能在局部範圍,函數内進行,比如不能在main函數中聲明或定義一個模闆。

一、函數模闆通式

1、函數模闆的格式:

template 傳回類型 函數名(參數清單)

{

函數體

}

其中template和class是關見字,class可以用typename 關見字代替,在這裡typename 和class沒差別,<>括号中的參數叫模闆形參,模闆形參和函數形參很相像,模闆形參不能為空。一但聲明了模闆函數就可以用模闆函數的形參名聲明類中的成員變量和成員函數,即可以在該函數中使用内置類型的地方都可以使用模闆形參名。模闆形參需要調用該模闆函數時提供的模闆實參來初始化模闆形參,一旦編譯器确定了實際的模闆實參類型就稱他執行個體化了函數模闆的一個執行個體。比如swap的模闆函數形式為:

template void swap(T& a, T& b){},

當調用這樣的模闆函數時類型T就會被被調用時的類型所代替,比如swap(a,b)其中a和b是int 型,這時模闆函數swap中的形參T就會被int 所代替,模闆函數就變為swap(int &a, int &b)。而當swap(c,d)其中c和d是double類型時,模闆函數會被替換為swap(double &a, double &b),這樣就實作了函數的實作與類型無關的代碼。

2、注意:對于函數模闆而言不存在 h(int,int) 這樣的調用,不能在函數調用的參數中指定模闆形參的類型,對函數模闆的調用應使用實參推演來進行,即隻能進行h(2,3) 這樣的調用,或者int a, b; h(a,b)。

函數模闆的示例示範将在下文中涉及!

二、類模闆通式

1、類模闆的格式為:

template class 類名

{ ... };

類模闆和函數模闆都是以template開始後接模闆形參清單組成,模闆形參不能為空,一但聲明了類模闆就可以用類模闆的形參名聲明類中的成員變量和成員函數,即可以在類中使用内置類型的地方都可以使用模闆形參名來聲明。比如

template class A{public: T a; T b; T hy(T c, T &d);};

在類A中聲明了兩個類型為T的成員變量a和b,還聲明了一個傳回類型為T帶兩個參數類型為T的函數hy。

2、類模闆對象的建立:比如一個模闆類A,則使用類模闆建立對象的方法為A m;在類A後面跟上一個<>尖括号并在裡面填上相應的類型,這樣的話類A中凡是用到模闆形參的地方都會被int 所代替。當類模闆有兩個模闆形參時建立對象的方法為A m;類型之間用逗号隔開。

3、對于類模闆,模闆形參的類型必須在類名後的尖括号中明确指定。比如A<2> m;用這種方法把模闆形參設定為int是錯誤的(編譯錯誤:error C2079: 'a' uses undefined class 'A'),類模闆形參不存在實參推演的問題。也就是說不能把整型值2推演為int 型傳遞給模闆形參。要把類模闆形參調置為int 型必須這樣指定A m。

4、在類模闆外部定義成員函數的方法為:

template 函數傳回類型 類名::函數名(參數清單){函數體},

比如有兩個模闆形參T1,T2的類A中含有一個void h()函數,則定義該函數的文法為:

template void A::h(){}。

注意:當在類外面定義類的成員時template後面的模闆形參應與要定義的類的模闆形參一緻。

5、再次提醒注意:模闆的聲明或定義隻能在全局,命名空間或類範圍内進行。即不能在局部範圍,函數内進行,比如不能在main函數中聲明或定義一個模闆。

三、模闆的形參

有三種類型的模闆形參:類型形參,非類型形參和模闆形參。

1、類型形參

1.1 、類型模闆形參:類型形參由關見字class或typename後接說明符構成,如template void h(T a){};其中T就是一個類型形參,類型形參的名字由使用者自已确定。模闆形參表示的是一個未知的類型。模闆類型形參可作為類型說明符用在模闆中的任何地方,與内置類型說明符或類類型說明符的使用方式完全相同,即可以用于指定傳回類型,變量聲明等。

作者原版:1.2、不能為同一個模闆類型形參指定兩種不同的類型,比如templatevoid h(T a, T b){},語句調用h(2, 3.2)将出錯,因為該語句給同一模闆形參T指定了兩種類型,第一個實參2把模闆形參T指定為int,而第二個實參3.2把模闆形參指定為double,兩種類型的形參不一緻,會出錯。(針對函數模闆)

作者原版:1.2針對函數模闆是正确的,但是忽略了類模闆。下面将對類模闆的情況進行補充。

本人添加1.2補充版(針對于類模闆)、當我們聲明類對象為:A a,比如templateT g(T a, T b){},語句調用a.g(2, 3.2)在編譯時不會出錯,但會有警告,因為在聲明類對象的時候已經将T轉換為int類型,而第二個實參3.2把模闆形參指定為double,在運作時,會對3.2進行強制類型轉換為3。當我們聲明類的對象為:A a,此時就不會有上述的警告,因為從int到double是自動類型轉換。

示範示例1:

TemplateDemo.h

#ifndefTEMPLATE_DEMO_HXX#defineTEMPLATE_DEMO_HXXtemplateclassA{public:Tg(Ta,Tb);A();};#endifTemplateDemo.cpp#include#include"TemplateDemo.h"templateA::A(){}templateTA::g(Ta,Tb){returna+b;}voidmain(){Aa;cout<

編譯結果:

--------------------Configuration: TemplateDemo - Win32 Debug--------------------

Compiling...

TemplateDemo.cpp

G:\C++\CDaima\TemplateDemo\TemplateDemo.cpp(12) : warning C4244: 'argument' : conversion from 'const double' to 'int', possible loss of data

TemplateDemo.obj - 0 error(s), 1 warning(s)

運作結果: 5

我們從上面的測試示例中可以看出,并非作者原作中的那麼嚴密!此處僅是本人跟人測試結果!請大家本着實事求是的态度,自行驗證!

2、非類型形參

2.1 、非類型模闆形參:模闆的非類型形參也就是内置類型形參,如template class B{};其中int a就是非類型的模闆形參。

2.2、 非類型形參在模闆定義的内部是常量值,也就是說非類型形參在模闆的内部是常量。

2.3、 非類型模闆的形參隻能是整型,指針和引用,像double,String, String **這樣的類型是不允許的。但是double &,double *,對象的引用或指針是正确的。

2.4、 調用非類型模闆形參的實參必須是一個常量表達式,即他必須能在編譯時計算出結果。

2.5 、注意:任何局部對象,局部變量,局部對象的位址,局部變量的位址都不是一個常量表達式,都不能用作非類型模闆形參的實參。全局指針類型,全局變量,全局對象也不是一個常量表達式,不能用作非類型模闆形參的實參。

2.6、 全局變量的位址或引用,全局對象的位址或引用const類型變量是常量表達式,可以用作非類型模闆形參的實參。

2.7 、sizeof表達式的結果是一個常量表達式,也能用作非類型模闆形參的實參。

2.8 、當模闆的形參是整型時調用該模闆時的實參必須是整型的,且在編譯期間是常量,比如template class A{};如果有int b,這時A m;将出錯,因為b不是常量,如果const int b,這時A m;就是正确的,因為這時b是常量。

2.9 、非類型形參一般不應用于函數模闆中,比如有函數模闆template void h(T b){},若使用h(2)調用會出現無法為非類型形參a推演出參數的錯誤,對這種模闆函數可以用顯示模闆實參來解決,如用h(2)這樣就把非類型形參a設定為整數3。顯示模闆實參在後面介紹。

2.10、 非類型模闆形參的形參和實參間所允許的轉換

1、允許從數組到指針,從函數到指針的轉換。如:template class A{}; int b[1]; A

m;即數組到指針的轉換

2、const修飾符的轉換。如:template class A{}; int b; A m;   即從int *到const int *的轉換。

3、提升轉換。如:template class A{}; const short b=2; A

m; 即從short到int 的提升轉換

4、整值轉換。如:template class A{};   A<3> m; 即從int 到unsigned int的轉換。

5、正常轉換。

非類型形參示範示例1:

由使用者自己親自指定棧的大小,并實作棧的相關操作。

TemplateDemo.h

#ifndefTEMPLATE_DEMO_HXX#defineTEMPLATE_DEMO_HXXtemplateclassStack{//MAXSIZE由使用者建立對象時自行設定private:Telems[MAXSIZE];// 包含元素的數組intnumElems;// 元素的目前總個數public:Stack();//構造函數voidpush(Tconst&);//壓入元素voidpop();//彈出元素Ttop()const;//傳回棧頂元素boolempty()const{// 傳回棧是否為空returnnumElems==0;}boolfull()const{// 傳回棧是否已滿returnnumElems==MAXSIZE;}};templateStack::Stack():numElems(0){// 初始時棧不含元素// 不做任何事情}templatevoidStack::push(Tconst&elem){if(numElems==MAXSIZE){throwstd::out_of_range("Stack<>::push(): stack is full");}elems[numElems]=elem;// 附加元素++numElems;// 增加元素的個數}templatevoidStack::pop(){if(numElems<=0){throwstd::out_of_range("Stack<>::pop(): empty stack");}--numElems;// 減少元素的個數}templateTStack::top()const{if(numElems<=0){throwstd::out_of_range("Stack<>::top(): empty stack");}returnelems[numElems-1];// 傳回最後一個元素}#endif

TemplateDemo.cpp

#include#include#include#include#include"TemplateDemo.h"intmain(){try{Stackint20Stack;// 可以存儲20個int元素的棧Stackint40Stack;// 可以存儲40個int元素的棧Stack<:string>stringStack;// 可存儲40個string元素的棧// 使用可存儲20個int元素的棧int20Stack.push(7);std::cout<::pop<>: empty stackreturn0;}catch(std::exceptionconst&ex){std::cerr<

c語言中的std模版用法,C++ 模闆詳解

非類型形參示範示例2:

TemplateDemo01.h

#ifndefTEMPLATE_DEMO_O1#defineTEMPLATE_DEMO_01templateclassCompareDemo{public:intcompare(constT&,constT&);};templateintCompareDemo::compare(constT&a,constT&b){if((a-b)>0)return1;elseif((a-b)<0)return-1;elsereturn0;}#endif

TemplateDemo01.cpp

#include#include"TemplateDemo01.h"voidmain(){CompareDemocd;cout<

運作結果:-1

TemplateDemo01.cpp

#include#include"TemplateDemo01.h"voidmain(){CompareDemocd;cout<

運作結果: 1

TemplateDemo01.h

#ifndefTEMPLATE_DEMO_O1#defineTEMPLATE_DEMO_01templateclassCompareDemo{public:intcompare(T&,T&);};templateintCompareDemo::compare(T&a,T&b){if((a-b)>0)return1;elseif((a-b)<0)return-1;elsereturn0;}#endif

TempalteDemo01.cpp

#include#include"TemplateDemo01.h"voidmain(){CompareDemocd;inta=2,b=3;cout<

非類型形參示範示例3:

TemplateDemo02.cpp

#includetemplateconstT&max(constT&a,constT&b){returna>b?a:b;}voidmain(){cout<(2.1,2.2)<(2.1,2.2)<

運作結果:

c語言中的std模版用法,C++ 模闆詳解

cout<(2.1,2.2)<

此語句會出現警告:

--------------------Configuration: TemplateDemo02 - Win32 Debug--------------------

Compiling...

TemplateDemo02.cpp

G:\C++\CDaima\TemplateDemo02\TemplateDemo02.cpp(11) :

warning C4244: 'argument' : conversion from 'const double' to 'const int', possible loss of data

G:\C++\CDaima\TemplateDemo02\TemplateDemo02.cpp(11) :

warning C4244: 'argument' : conversion from 'const double' to 'const int', possible loss of data

TemplateDemo02.obj - 0 error(s), 2 warning(s)

四、類模闆的預設模闆類型形參

1、可以為類模闆的類型形參提供預設值,但不能為函數模闆的類型形參提供預設值。函數模闆和類模闆都可以為模闆的非類型形參提供預設值。

2、類模闆的類型形參預設值形式為:template class A{};為第二個模闆類型形參T2提供int型的預設值。

3、類模闆類型形參預設值和函數的預設參數一樣,如果有多個類型形參則從第一個形參設定了預設值之後的所有模闆形參都要設定預設值,比如templateclass A{};就是錯誤的,因為T1給出了預設值,而T2沒有設定。

4、在類模闆的外部定義類中的成員時template 後的形參表應省略預設的形參類型。比如template class A{public: void h();}; 定義方法為template void A::h(){}。

定義類模闆類型形參:

示範執行個體1:

TemplateDemo.h

#ifndefTEMPLATE_DEMO_HXX#defineTEMPLATE_DEMO_HXXtemplateclassA{public:Tg(Ta,Tb);A();};#endif

TemplateDemo.cpp

#include#include"TemplateDemo.h"templateA::A(){}templateTA::g(Ta,Tb){returna+b;}voidmain(){Aa;cout<

運作結果: 5

類模闆的預設模闆類型形參示例1:

TemplateDemo03.h

#ifndefTEMPLATE_DEMO_03#defineTEMPLATE_DEMO_03//定義帶預設類型形參的類模闆。這裡把T2預設設定為int型。templateclassCeilDemo{public:intceil(T1,T2);};//在類模闆的外部定義類中的成員時template 後的形參表應省略預設的形參類型。templateintCeilDemo::ceil(T1a,T2b){returna>>b;}#endif

TemplateDemo03.cpp

#include#include"TemplateDemo03.h"voidmain(){CeilDemocd;cout<

運作結果:  2

在類模闆的外部定義類中的成員時template 後的形參表應省略預設的形參類型,如果沒有省略,不會出現編譯錯誤而是提出警告:

--------------------Configuration: TemplateDemo03 - Win32 Debug--------------------

Compiling...

TemplateDemo03.cpp

g:\c++\cdaima\templatedemo03\templatedemo03.h(12) :

warning C4519: default template arguments are only allowed on a class template; ignored

TemplateDemo03.obj - 0 error(s), 1 warning(s)

原作者:類模闆類型形參預設值和函數的預設參數一樣,如果有多個類型形參則從第一個形參設定了預設值之後的所有模闆形參都要設定預設值,比如templateclass A{};就是錯誤的,因為T1給出了預設值,而T2沒有設定。

類模闆的預設模闆類型形參示例2:

TemplateDemo03.h

#ifndefTEMPLATE_DEMO_03#defineTEMPLATE_DEMO_03templateclassCeilDemo{public:intceil(T1,T2,T3);};templateintCeilDemo::ceil(T1a,T2b,T3c){returna+b+c;}#endif

TemplateDemo03.cpp

#include#include"TemplateDemo03.h"voidmain(){CeilDemocd;cout<

運作結果:9

TemplateDemo03.h

#ifndefTEMPLATE_DEMO_03#defineTEMPLATE_DEMO_03templateclassCeilDemo{public:doubleceil(T1,T2,T3);};templatedoubleCeilDemo::ceil(T1a,T2b,T3c){returna+b+c;}#endif

TemplateDemo03.cpp

#include#include"TemplateDemo03.h"voidmain(){CeilDemocd;cout<

編譯錯誤:

--------------------Configuration: TemplateDemo03 - Win32 Debug--------------------

Compiling...

TemplateDemo03.cpp

g:\c++\cdaima\templatedemo03\templatedemo03.h(12) :

error C2244: 'CeilDemo::ceil' : unable to resolve function overload

g:\c++\cdaima\templatedemo03\templatedemo03.cpp(6) :

error C2065: 'cd' : undeclared identifier

g:\c++\cdaima\templatedemo03\templatedemo03.cpp(6) :

error C2228: left of '.ceil' must have class/struct/union type

Error executing cl.exe.

TemplateDemo03.obj - 3 error(s), 0 warning(s)

從上面的例子我們可以看出,當我們試圖把T2和T3定義為double類型就會出現錯誤(T1預設定義的是int類型)。那是不是我們按照作者所說把T2和T3也設定為預設值double,是否還會出現錯誤?看下面的示例:

類模闆的預設模闆類型形參示例4:

TemplateDemo03.h

#ifndefTEMPLATE_DEMO_03#defineTEMPLATE_DEMO_03templateclassCeilDemo{public:doubleceil(T1,T2,T3);};templatedoubleCeilDemo::ceil(T1a,T2b,T3c){returna+b+c;}#endif

TemplateDemo03.cpp

#include#include"TemplateDemo03.h"voidmain(){CeilDemocd;cout<

編譯錯誤:

--------------------Configuration: TemplateDemo03 - Win32 Debug--------------------

Compiling...

TemplateDemo03.cpp

g:\c++\cdaima\templatedemo03\templatedemo03.h(12) :

error C2244: 'CeilDemo::ceil' : unable to resolve function overload

g:\c++\cdaima\templatedemo03\templatedemo03.cpp(6) :

error C2065: 'cd' : undeclared identifier

g:\c++\cdaima\templatedemo03\templatedemo03.cpp(6) :

error C2228: left of '.ceil' must have class/struct/union type

Error executing cl.exe.

TemplateDemo03.obj - 3 error(s), 0 warning(s)

從結果我們可以看出,和上例是一樣的錯誤。從執行個體中我們可以總結如下:類模闆如果有多個類型形參,如果使用類型形參預設值則盡量放在參數清單的末尾,而且預設的參數類型必須相同。如果從第一個形參設定了預設值之後的所有模闆形參都要設定和第一個形參同類型的預設值。(聲明:本人也是剛接觸C++,以上隻是我經過執行個體示範對原作者提出的一些質疑,可能我的示例有不到之處,還望大神們不吝賜教,共同完善此部落格,給像我一樣的菜鳥提供一個學習的平台!)

接下來驗證"不能為函數模闆的類型形參提供預設值":

類模闆的預設模闆類型形參示例5:

TemplateDemo04.cpp

#includetemplateT1sum(T1a,T2b,T3c=int){returna+b+c;}voidmain(){cout<(1.1,2.1,3)<

編譯錯誤:

--------------------Configuration: TemplateDemo04 - Win32 Debug--------------------

Compiling...

TemplateDemo04.cpp

g:\c++\cdaima\templatedemo04\templatedemo04.cpp(4) :

error C2062: type 'int' unexpected

Error executing cl.exe.

TemplateDemo04.obj - 1 error(s), 0 warning(s)

更改之後的 TemplateDemo.cpp

TemplateDemo.cpp

#includetemplateT1sum(T1a,T2b,T3c){returna+b+c;}voidmain(){cout<(1.1,3,257)<

運作結果:   261.1

原作者示範執行個體如下:

類模闆非類型形參示例

//模闆的聲明或定義隻能在全局,命名空間或類範圍内進行。即不能在局部範圍,函數内進行,比如不能在main函數中聲明或定義一個模闆。//類模闆的定義templateclassA{public:Tg(Ta,Tb);A();};//定義帶有一個類模闆類型形參T的類AtemplateclassB{public:voidg();};//定義帶有兩個類模闆類型形參T1,T2的類B//定義類模闆的預設類型形參,預設類型形參不适合于函數模闆。templateclassD{public:voidg();};//定義帶預設類型形參的類模闆。這裡把T2預設設定為int型。//templateclass E{}; //錯誤,為T1設了預設類型形參則T1後面的所有形參都必須設定認默值。//以下為非類型形參的定義//非類型形參隻能是整型,指針和引用,像double,String, String **這樣的類型是不允許的。但是double &,double *對象的引用或指針是正确的。templateclassCi{public:voidg();};//定義模闆的非類型形參,形參為整型templateclassCip{public:voidg();};template*m>classCc{public:voidg();};//定義模闆的模闆類型形參,形參為int型的類A的對象的指針。templateclassCd{public:voidg();};//定義模闆的非類型形參,形參為double類型的引用。classE{};templateclassCe{};//非類型模闆形參為對象的引用。//以下非類型形參的聲明是錯誤的。//templateclass Cc{}; //錯誤,對象不能做為非類型形參,非類型模闆形參的類型隻能是對象的引用或指針。//templateclass Cc{}; //錯誤,非類型模闆的形參不能是double類型,可以是double的引用。//template m>class Cc{}; //錯誤,非類型模闆的形參不能是對象,必須是對象的引用或指針。這條規則對于模闆型參也不例外。//在類模闆外部定義各種類成員的方法,//typeid(變量名).name()的作用是提取變量名的類型,如int a,則cout<A::A(){cout<TA::g(Ta,Tb){cout<voidB::g(){cout<voidCi::g(){cout<voidCip::g(){cout< *m>voidCc::g(){cout<voidCd::g(){cout<voidD::g(){cout< void D::g(){cout<mw;A *pec=&mw;Eme;//main函數開始intmain(){// templatevoid h(){} //錯誤,模闆的聲明或定義隻能在全局,命名空間或類範圍内進行。即不能在局部範圍,函數内進行。//A<2> m; //錯誤,對類模闆不存在實參推演問題,類模闆必須在尖括号中明确指出其類型。//類模闆調用執行個體Ama;//輸出"class A goucao int"建立int型的類模闆A的對象ma。Bmb;mb.g();//輸出"class B g() int int"建立類模闆B的對象mb,并把類型形參T1和T2設計為int//非類型形參的調用//調用非類型模闆形參的實參必須是一個常量表達式,即他必須能在編譯時計算出結果。任何局部對象,局部變量,局部對象的位址,局部變量的位址都不是一個常量表達式,都不能用作非類型模闆形參的實參。全局指針類型,全局變量,全局對象也不是一個常量表達式,不能

用作非類型模闆形參的實參。//全局變量的位址或引用,全局對象的位址或引用const類型變量是常量表達式,可以用作非類型模闆形參的實參。//調用整型int型非類型形參的方法為名為Ci,聲明形式為template class CiCi//正确,數值R是一個int型常量,輸出"class Ci g() int"constinta2=3;Cimci1;mci1.g();//正确,因為a2在這裡是const型的常量。輸出"class Ci g() int"//Ci mci; //錯誤,int型變量a是局部變量,不是一個常量表達式。//Ci mci; //錯誤,全局int型變量e也不是一個常量表達式。//調用int&型非類型形參的方法類名為Cip,聲明形式為templateclass CipCipmcip;//正确,對全局變量的引用或位址是常量表達式。//Cip mcip1; //錯誤,局部變量的引用或位址不是常量表達式。//調用double*類型的非類形形參類名為Cd,聲明形式為templateclass CdCdmcd;//正确,全局變量的引用或位址是常量表達式。//Cd mcd1; //錯誤,全局變量指針不是常量表達式。//double dd=3.3; //錯誤,局部變量的位址不是常量表達式,不能用作非類型形參的實參//Cd mcd; //錯誤,非類型形參雖允許一些轉換,但這個轉換不能實作。//調用模闆類型形參對象A *的方法類名為Cc,聲名形式為template* m> class CcCcmcc;mcc.g();//正确,全局對象的位址或者引用是常量表達式//Cc mcc; //錯誤,局部變量的位址或引用不是常量表達式。//Cc mcc2; //錯誤,全局對象的指針不是常量表達式。//調用非類型形參E&對象的引用的方法類名為Ce。聲明形式為template class CeEme1;//Ce mce1; //錯誤,局部對象不是常量表達式Cemce;//正确,全局對象的指針或引用是常量表達式。//非類型形參的轉換示例,類名為Ci//非類型形參允許從數組到指針,從函數到指針的轉換,const修飾符的轉換,提升轉換,整值轉換,正常轉換。constshorts=3;Cimci4//正确,雖然short型和int不完全比對,但這裡可以将short型轉換為int型