C++模板的那丢丢事儿
一、模板的引入
首先,請你認真思考一個問題,如何編寫一個通用的加法函數(shù)呢?
1.對于我來說,首先第一反應(yīng)是寫一個宏函數(shù)來實現(xiàn)此功能,如:
#define ADD(x,y) ((x) + (y))那么,這樣寫的缺點是什么呢?
(1)它沒有參數(shù)檢測,這個原因致使它能夠完成該要求,同時也造就了它致命的缺陷,不能進行參數(shù)檢測,故安全性不高。
(2)它不能像函數(shù)一樣進行調(diào)試,而僅僅是在編譯期間進行簡單的參數(shù)替換,況且如果代碼過長,會大幅增加代碼量。
(3)宏函數(shù)可能會有副作用,在不該替換的時候進行替換,假設(shè)括號沒加全。
(4)宏函數(shù)只能處理整數(shù)或枚舉類型的數(shù)據(jù),對于其他追加字符串,或其他外置類型的數(shù)據(jù)處理不了。
#include<iostream> using namespace std;#define ADD(x,y) ((x) + (y))int main() {cout<<ADD(1,3)<<endl;cout<<ADD(1.3,2.5)<<endl;cout<<ADD('A','G')<<endl;system("pause");return 0; }運行結(jié)果:
? ? ? ? ? ? ? ? ? ? ? ? ? ??
錯誤證明:
? ? ? ? ? ? ? ? ? ? ? ?
2、運用前面提到的函數(shù)重載解決通用加法函數(shù),針對每個所需相同行為的不同類型重新實現(xiàn)它。
?
#include<iostream> using namespace std;int Add(const int &_iLeft, const int &_iRight) {return (_iLeft + _iRight); } float Add(const float &_fLeft, const float &_fRight) {return (_fLeft + _fRight); }int main() {cout<<Add(1,3)<<endl;cout<<Add(1.3f,2.5f)<<endl;system("pause");return 0; }運行結(jié)果:
? ? ? ? ? ? ??
這種方法的缺點:
(1)只要有新類型出現(xiàn),就要重新添加對應(yīng)函數(shù)。
(2)除類型外,所有函數(shù)的函數(shù)體都相同,代碼的復(fù)用率不高
(3)如果函數(shù)只是返回值類型不同,函數(shù)重載不能解決
(4)一個方法有問題,所有的方法都有問題,不好維護。
3、通過多態(tài)實現(xiàn),使用公共基類,將需要用到的虛函數(shù)代碼放在公共的基礎(chǔ)類里面,通過基類的對象指針進行調(diào)用,派生類可重寫也可不重寫。
class B { public:virtual int add(int _x,int _y){return (_x+_y);}virtual float add(float _x,float _y){return (_x+_y);} }; class INT_ADD:public B {}; class FLOAT_ADD:public B {};int main() {B *b;INT_ADD i;FLOAT_ADD f;b = &i;cout<<b->add(1,3)<<endl;b = &f;cout<<b->add(1.8f,3.6f)<<endl;system("pause");return 0; }缺點:
(1)借助基類虛函數(shù)來編寫適合各種類型的加法函數(shù),只要有新類型出現(xiàn),就要重新添加對應(yīng)函數(shù),代碼利用率不高;
(2)對于以后實現(xiàn)的許多派生類,都必須調(diào)用各自某個特定的基類虛函數(shù),代碼維護更加困難
4、通過模板解決。
?模板的分類:模板分為函數(shù)模板和類模板,他們分別允許用戶構(gòu)造模板函數(shù)和模板類。
既然上面的幾種方法都有各自的缺陷,于是就引入了模板的概念,使用我們的模板解決上述問題,那將是最好不過了,下面用函數(shù)模板實現(xiàn)通用加法函數(shù)。
template <typename T> T ADD(T x,T y) {return (x+y); } int main() {cout<<ADD(1,3)<<endl;cout<<ADD(1.3,2.5)<<endl;system("pause");return 0; }優(yōu)點:利用模板機制可以顯著減少冗余信息,能大幅度的節(jié)約程序代碼,進一步提高面向?qū)ο蟪绦虻目芍赜眯院涂删S護性。
二、函數(shù)模板
函數(shù)模板:
? ? ? 所謂函數(shù)模板,實際上是建立了一個通用函數(shù),其函數(shù)返回類型和形參類型不具體指定,用一個虛擬的類型來代表,這個通用函數(shù)就稱為函數(shù)模板(注意它不是真正的函數(shù))。代表了一個函數(shù)家族,該函數(shù)與類型無關(guān),在使用時被參數(shù)化,根據(jù)實參類型產(chǎn)生函數(shù)的特定類型版本。
(一)函數(shù)模板聲明的一般格式:
(二)函數(shù)模板的使用
(1)模板函數(shù)的實例化:
?當我們調(diào)用一個函數(shù)模板時,編譯器(通常)用函數(shù)實參來為我們推斷模板實參,此模板實參來為我們實例化一個特定版本的函數(shù)。當編譯器實例化一個模板時,它使用實際的模板實參代替對應(yīng)的模板參數(shù)來創(chuàng)建模板的一個新“實例”,此過程稱為模板的實例化
函數(shù)模板的實例化有兩種調(diào)用方式,即顯式實例化和隱式實例化。
?
template <typename T> T ADD(T x,T y) {return (x+y); } int main() {cout<<ADD(1,3)<<endl;//隱式實例化cout<<ADD<float>(1.3f,2.5f)<<endl;//顯式實例化system("pause");return 0; }自定義類型不能直接用模板函數(shù)進行實例化,除非自己實現(xiàn)重載。
注意:模板被編譯了兩次:
a>實例化之前,檢查模板代碼本身,查看是否出現(xiàn)語法錯誤,如:遺漏分號
b>在實例化期間,檢查模板代碼,查看是否所有的調(diào)用都有效,如:實例化類型不支持 ? ? 某些函數(shù)調(diào)用
(2)模板函數(shù)的使用規(guī)則
a>模板函數(shù)也可以定義為內(nèi)聯(lián)函數(shù)
template <typename T> inline T ADD(T x,T y) {return (x+y); }注意:inline關(guān)鍵字必須放在模板形參表之后,返回值之前,不能放在template之前
b>模板函數(shù)的不足之處:不能自動進行類型轉(zhuǎn)換,由于需要生成代碼,所以編譯速度慢
? ? ? ? ? ? ? ? ? ??
要解決上述類型不同的問題,有以下三種方法:
1,采用顯式實例化方式
template <typename T> T ADD(T x,T y) {return (x+y); } int main() {cout<<ADD<int>(1,'d');system("pause");return 0; }2,將其中一個參數(shù)進行強制類型轉(zhuǎn)換
template <typename T> T ADD(T x,T y) {return (x+y); } int main() {cout<<ADD(1,(int)'d');system("pause");return 0; }3,多帶一個參數(shù)模板(在類型中允許使用多個類型參數(shù))
template <typename T,typename G> T ADD(T x,G y) {return (x+y); } int main() {cout<<ADD(1,'d');system("pause");return 0; }d>模板參數(shù)
<1>【實參推演】
? ? ? ?從函數(shù)實參確定模板形參類型和值的過程稱為模板實參推斷,即函數(shù)模板實例化過程中,編譯器用函數(shù)實參來為我們推斷模板實參的過程。
注意:多個類型形參的實參必須完全匹配
<2>【類型形參轉(zhuǎn)換】
編譯器只會執(zhí)行兩種轉(zhuǎn)換:
1、const轉(zhuǎn)換:接收const引用或者const指針的函數(shù)可以分別用非const對象的引用或者指針來調(diào)用。
template <typename T>T ADD(const T& x,const T& y)
{return (x+y);
}
int main()
{int a = 10;int b = 20;int &ra = a;int &rb = b;cout<<"a + b ="<<ADD(ra,rb)<<endl;system("pause");return 0;
}
2、數(shù)組或函數(shù)到指針的轉(zhuǎn)換:如果模板形參不是引用類型,則對數(shù)組或函數(shù)類型的實參應(yīng)用常規(guī)指針轉(zhuǎn)換。數(shù)組實參將當做指向其第一個元素的指針,函數(shù)實參當做指向函數(shù)類型的指針。
template <typename T> T ADD(const T* x,const T* y) {return (*x + *y); } int main() {int a[5] = {0};int b[5] = {5,4,3,2,1};cout<<"a + b = "<<ADD(a,b)<<endl;system("pause");return 0; }<3>模板參數(shù)的分類
函數(shù)模板有兩種類型參數(shù):模板參數(shù)和調(diào)用參數(shù)
模板形參:類型形參和非類型形參
?
模板形參名字只能在模板形參之后到模板聲明或定義的末尾之間使用,遵循名字屏蔽規(guī)則,即我們常用的就近原則。
typedef int T;//將int類型重命名為Ttemplate <typename T>//函數(shù)模板 void Funtest(T t) {cout<<"t type = "<<typeid(t).name()<<endl;//打印形參t的類型 }T gloab;//定義全局變量gloabint main() {Funtest(10);cout<<"gloab type = "<<typeid(gloab).name()<<endl;system("pause");return 0; }代碼分析:
總結(jié):
模板形參說明
1、模板形參表使用<>括起來
2、和函數(shù)參數(shù)表一樣,跟多個參數(shù)時必須用逗號隔開,類型可以相同也可以不相同
3、模板形參表不能為空
4、模板形參可以是類型形參,也可以是非類型新參,類型形參跟在class和typename后
5、模板類型形參可作為類型說明符用在模板中的任何地方,與內(nèi)置類型或自定義類型
使用方法完全相同,可用于指定函數(shù)形參類型、返回值、局部變量和強制類型轉(zhuǎn)換
6、模板形參表中,class和typename具有相同的含義,可以互換,使用typename更加直觀。
但關(guān)鍵字typename是作為C++標準加入到C++中的,舊的編譯器可能不支持。
e>函數(shù)模板的重載
1、同一般函數(shù)一樣,函數(shù)模板也可以重載。
template<typename T> T Max(const T& left, const T& right) {return left>right? left:right; } template<typename T> T Max(const T& a, const T& b, const T& c) {return Max(Max(a, b), c); }; int main() { cout<<Max(1,2)<<endl; cout<<Max(1.2,3.4,5.6)<<endl; system("pause"); return 0; }2、模板函數(shù)與同名的非模板函數(shù)可以重載。 ??
???????在這種情況下,函數(shù)的調(diào)用順序是:首先尋找一個參數(shù)完全匹配的非模板函數(shù),如果找到了就調(diào)用它此種情況下,編譯器并不會為相應(yīng)的函數(shù)模板產(chǎn)生匹配的模板函數(shù),如果想讓編譯器為模板函數(shù)生成代碼,則必須顯示實例化,且生成的代碼與普通函數(shù)不是同一份代碼;若沒有找到參數(shù)完全匹配的非模板函數(shù),則尋找函數(shù)模板,將其實例化產(chǎn)生一個匹配的模板函
?
int Max(const int& left, const int & right)//普通函數(shù)
{return left>right? left:right;
}
template<typename T>
T Max(const T& left, const T& right)
{return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{return Max(Max(a, b), c);
};
int main()
{cout<<Max(10, 20, 30)<<endl;cout<<Max<>(10, 20)<<endl;cout<<Max(10, 20)<<endl;cout<<Max(10, 20.12)<<endl;cout<<Max<int>(10.0, 20.0)<<endl;cout<<Max(10.0, 20.0)<<endl;system("pause");return 0;
}
注意:函數(shù)的所有重載版本的聲明都應(yīng)該位于該函數(shù)被調(diào)用位置之前。
【說明】
1、一個非模板函數(shù)可以和一個同名的函數(shù)模板同時存在,而且該函數(shù)模板還可以被實例
化為這個非模板函數(shù)。
2、對于非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動時會優(yōu)先調(diào)動非模板
函數(shù)而不會從該模板產(chǎn)生出一個實例。如果模板可以產(chǎn)生一個具有更好匹配的函數(shù),
那么將選擇模板。
3、顯式指定一個空的模板實參列表,該語法告訴編譯器只有模板才能來匹配這個調(diào)用,
而且所有的模板參數(shù)都應(yīng)該根據(jù)實參演繹出來。
4、模板函數(shù)不允許自動類型轉(zhuǎn)換,但普通函數(shù)可以進行自動類型轉(zhuǎn)換。
f>模板函數(shù)特化
1、定義:
有時候并不總是能夠?qū)懗鰧λ锌赡鼙粚嵗念愋投甲詈线m的模板,在某些情況下,通用模板定義對于某個類型可能是完全錯誤的,或者不能編譯,或者做一些錯誤的事情,這時候就需要對模板函數(shù)進行特化(即具體化)。
template <typename T> int Compare(T s1, T s2) {if(s1<s2)return -1;else if(s1>s2)return 1;else return 0; } int main() {char* str1 = "abcd";char* str2 = "ghf";cout<<Compare(str1,str2)<<endl;system("pause");return 0; }?
template <typename T> int Compare(T s1, T s2) {if(s1<s2)return -1;else if(s1>s2)return 1;else return 0; } template<>//模板函數(shù)的特化 int Compare<const char*>(const char* s1,const char* s2) {return strcmp(s1,s2); } int main() {const char* str1 = "abcd";const char* str2 = "ghf";cout<<Compare(str1,str2)<<endl;system("pause");return 0; }?
2、模板函數(shù)特化形式如下:
1>關(guān)鍵字template后面接一對空的尖括號<>
2>再接模板名和一對尖括號,尖括號中指定這個特化定義的模板形參
3>函數(shù)形參表
4>函數(shù)體
template<>
返回值 函數(shù)名<Type>(參數(shù)列表)
{
? ? ?// 函數(shù)體
}
其次,需要注意的是給函數(shù)模板傳參時一定要與特化參數(shù)列表中的參數(shù)格式一模一樣,否則有可能即使寫了特化,運行過程中也不會調(diào)用
template <typename T> int Compare(T s1, T s2) {if(s1<s2)return -1;else if(s1>s2)return 1;else return 0; } template<>//模板函數(shù)的特化 int Compare<const char*>(const char* s1,const char* s2)//注意參數(shù)類型為const char* {return strcmp(s1,s2); } int main() {char* str1 = "abcd";//定義實參類型為char*char* str2 = "ghf";//定義實參類型為char*cout<<Compare(str1,str2)<<endl;system("pause");return 0; }??
注意:
1.在模板特化版本的調(diào)用中,實參類型必須與特化版本函數(shù)的形參類型完全匹配,
如果不匹配,編譯器將為實參模板定義中實例化一個實例。
2.特化不能出現(xiàn)在模板實例的調(diào)用之后,應(yīng)該在頭文件中包含模板特化的聲明,然
后使用該特化版本的每個源文件包含該頭文件。
三、類模板
?
與函數(shù)模板類似,模板類也是模板,必須以關(guān)鍵字template開頭,后接模板形參表。
【普通順序表】
【模板類格式】
template<class 形參名1, class 形參名2, ...class 形參名n>
class 類名
{ ... };
template<typename T>
class SeqList
{
private :
T* _data ;
int _size ;
int _capacity ;
};
?
// 以模板方式實現(xiàn)動態(tài)順序表
template<typename T> class SeqList { public : SeqList(); ~ SeqList(); private : int _size ; int _capacity ; T* _data ; }; template <typename T> SeqList <T>:: SeqList() : _size(0) , _capacity(10) , _data(new T[ _capacity]) {} template <typename T> SeqList <T>::~ SeqList() { delete [] _data ; } void test1 () { SeqList<int > sl1; SeqList<double > sl2; }【模板類的實例化】
只要有一種不同的類型,編譯器就會實例化出一個對應(yīng)的類。
SeqList<int > sl1;
SeqList<double > sl2;
當定義上述兩種類型的順序表時,編譯器會使用int和double分別代替模板形參,重新編寫SeqList類,最后創(chuàng)建名為SeqList<int>和SeqList<double>的類
?
【非類型的類模板參數(shù)】
// 靜態(tài)順序表 //template<typename T, size_t MAX_SIZE> template <typename T, size_t MAX_SIZE = 10> //帶缺省模板參數(shù) class SeqList { public : SeqList(); private : T _array [MAX_SIZE]; int _size ; }; template <typename T, size_t MAX_SIZE> SeqList <T, MAX_SIZE>::SeqList() : _size(0) {} void Test() { SeqList<int> s1; SeqList<int , 20> s2; }注意:浮點數(shù)和類對象是不允許作為非類型模板參數(shù)的
【類模板的特化】
全特化
template <typename T> class SeqList { public :SeqList();~ SeqList(); private :int _size ;int _capacity ;T* _data ; }; template<typename T> SeqList <T>:: SeqList(): _size(0), _capacity(10), _data(new T[ _capacity]) {cout<<"SeqList<T>" <<endl; } template<typename T> SeqList <T>::~ SeqList() {delete[] _data ; } template <> class SeqList <int> { public :SeqList(int capacity);~ SeqList(); private :int _size ;int _capacity ;int* _data ; };// 特化后定義成員函數(shù)不再需要模板形參
SeqList <int>:: SeqList(int capacity) : _size(0) , _capacity(capacity ) , _data(new int[ _capacity]) { cout<<"SeqList<int>" <<endl; }偏特化(局部特化)
template <typename T1, typename T2> class Data { public :Data(); private :T1 _d1 ;T2 _d2 ; }; template <typename T1, typename T2> Data<T1 , T2>::Data() {cout<<"Data<T1, T2>" <<endl; } // 局部特化第二個參數(shù) template <typename T1> class Data <T1, int> { public :Data(); private :T1 _d1 ;int _d2 ; }; template <typename T1> Data<T1 , int>::Data() {cout<<"Data<T1, int>" <<endl; }偏特化并不僅僅是指特化部分參數(shù),而是針對模板參數(shù)更進一步的條件限
制所設(shè)計出來的一個特化版本。
// 局部特化兩個參數(shù)為指針類型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
T1* _d3 ;
T2* _d4 ;
};
template <typename T1, typename T2>
Data<T1 *, T2*>:: Data()
{
cout<<"Data<T1*, T2*>" <<endl;
}
// 局部特化兩個參數(shù)為引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public :
Data(const T1& d1, const T2& d2);
private :
const T1 & _d1;
const T2 & _d2;
T1* _d3 ;
T2* _d4 ;
};
template <typename T1, typename T2>
Data<T1 &, T2&>:: Data(const T1& d1, const T2& d2)
: _d1(d1 )
, _d2(d2 )
{
cout<<"Data<T1&, T2&>" <<endl;
}
void test2 ()
{
Data<double , int> d1;
Data<int , double> d2;
Data<int *, int*> d3;
Data<int&, int&> d4(1, 2);
}
模板的全特化和偏特化都是在已定義的模板基礎(chǔ)之上,不能單獨存在。
【模板的分離編譯】
解決辦法:
1. 在模板頭文件 xxx.h 里面顯示實例化->模板類的定義后面添
加 template class SeqList<int >; 一般不推薦這種方法,一方面老編譯器可能不支持,另一方
面實例化依賴調(diào)用者。(不推薦)
2. 將聲明和定義放到一個文件 "xxx.hpp" 里面,推薦使用這種方法。
模板總結(jié)
【優(yōu)點】
模板復(fù)用了代碼,節(jié)省資源,更快的迭代開發(fā),C++的標準模板庫(STL)因此而產(chǎn)生。
增強了代碼的靈活性。
【缺點】
模板讓代碼變得凌亂復(fù)雜,不易維護,編譯代碼時間變長。
出現(xiàn)模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤。
?
總結(jié)
以上是生活随笔為你收集整理的C++模板的那丢丢事儿的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: map,multimap,unorder
- 下一篇: C++之类型萃取技巧