前言:c++
在C++ template中已經詳細的經過實踐說明了c++ template的用法,也在typename 和class在template中的區別中解釋了template中typename和class的區別。算法
這一文結合本身的想法作一個總結。數組
一、 函數模板和模板函數函數
函數模板是一個抽象畫的函數,區別於函數的重載。指針
如函數的重載,多個函數除了數據類型不一樣,而函數算法 相同時,能夠用函數模板。對象
定義形式:繼承
template<class 類型形參名, class 類型形參名>編譯器
返回類型 函數名(函數形參表) {string
函數體;io
}
template是模板定義的關鍵字,不作說明
模板類型用class,這裏和typename是同樣的,具體看typename 和class在template中的區別
函數返回類型能夠是模板類型形參名
函數形參表中形參形式分爲:引用型參數和非引用型參數。引用型參數直接修改實參自己。
在調用函數模板時,編譯系統會根據實參的類型生成一個對應的函數,這就是模板函數。
模板函數由編譯系統在發現具體的函數調用時生成相對應的程序代碼,是實際的函數定義。因此,模板函數是函數模板的實例化。
在函數調用的時候,實參的參數類型必須和模板中的數據參數類型徹底一致,才能正確的實例化,纔會有正確的模板函數。
例如,
template<class T>swap(T a, T b) {
...
...
}
在調用swap的時候,兩個實參的類型必須都是T,swap(1, 2); 或者swap(1.2, 2.1);都是能夠的,可是不能是swap(1, 2.1); 一個是int類型,一個是float類型。
二、類模板
定義形式:
template <class 類型形參名, class 類型形參名>
class className {
類體;
};
對於類模板的類型形參後面說明,來看一下類模板中的成員函數的實現形式:
template <類型形參表>
返回類型 className<類型形參名>::functionName(函數形參表) {
函數體;
}
template後面的類型形參表和類模板定義的時候同樣
className後面的類型形參名就是類模板定義中的類型形參名,不加class或typename等關鍵字
函數的形參表就是形參,可能會用到類型形參
好比有兩個模板形參T1,T2的類A中含有一個void h()函數,則定義該函數的語法爲:
template<class T1, class T2> void A<T1,T2>::h(){}
類模板實例:
className<類型實參表> object;
這裏className後面的是類型的實參表了,一樣是上面定義的模板類A,形參名爲T1和T2,可是在實例的時候應該寫爲:
A<int, int> a;
這樣對象a中的類型都是int型替換。
對於類模板,模板的形參必須在類名後用尖括號明確指定,這跟函數模板不同的地方。
注意:
模板的聲明或定義只能在全局、命名空間或者類範圍內進行。不能放到函數或者局部範圍內進行。
三、模板形參
模板的形參分三種:類型形參、非類型形參、模板形參。
類型形參
在這以前講到的模板形參都是類型形參,用關鍵字class或者typename聲明。
實例化都是根據實參的真正類型。
非類型形參
非類型形參又稱內置類型形參。例如templage<class T, int a>class A{}; 其中int a就是非類型形參。
對於非類型形參,在模板內部是以常量形式存在,因此又稱爲內置類型形參。
非類型形參只能是整型、指針和引用。double、string等是不容許的,但double *、double &和對象的引用或指針是能夠的。
調用非類型形參的實參必須是常量表達式,也就是說在編譯的時候必須計算出結果。
注意:任何局部對象,局部變量,局部對象的地址,局部變量的地址都不是一個常量表達式,都不能用做非類型模板形參的實參。全局指針類型,全局變量,全局對象也不是一個常量表達式,不能用做非類型模板形參的實參。
全局變量的地址或引用,全局對象的地址或引用const類型變量是常量表達式,能夠用做非類型模板形參的實參。
sizeof表達式的結果是一個常量表達式,也能用做非類型模板形參的實參。
當模板的形參是整型時調用該模板時的實參必須是整型的,且在編譯期間是常量,好比template <class T, int a> class A{};若是有int b,這時A<int, b> m;將出錯,由於b不是常量,若是const int b,這時A<int, b> m;就是正確的,由於這時b是常量。
非類型形參通常不該用於函數模板中,好比有函數模板template<class T, int a> void h(T b){},若使用h(2)調用會出現沒法爲非類型形參a推演出參數的錯誤,對這種模板函數能夠用顯示模板實參來解決,如用h<int, 3>(2)這樣就把非類型形參a設置爲整數3。顯示模板實參在後面介紹。
非類型模板形參的形參和實參間所容許的轉換
容許從數組到指針,從函數到指針的轉換。如:template <int *a> class A{}; int b[1]; A<b> m;即數組到指針的轉換
const修飾符的轉換。如:template<const int *a> class A{}; int b; A<&b> m; 即從int *到const int *的轉換。
提高轉換。如:template<int a> class A{}; const short b=2; A<b> m; 即從short到int 的提高轉換
整值轉換。如:template<unsigned int a> class A{}; A<3> m; 即從int 到unsigned int的轉換。
常規轉換。
模板形參
模板形參就是模板的參數是個類模板模板。
形式以下:
template<class T, template <class U> class ParameterName>
上面模板形參中標記的class不能用typename替換
模板形參並不用於函數模板中
四、默認模板參數
指定模板中形參類型爲默認值,這樣在實例的時候可使用默認參數。
例如:
template<class T1, class T2 = int>
class Test4 {
public:
Test4();
Test4(T1, T2);
~Test4();
void test(T1, T2);
private:
T1 value1;
T2 value2;
};
指定第二個形參爲int型,若是默認狀況下第二個參數爲int,實例obj的時候,能夠這樣:
Test4<float> obj;
obj.test(5.0, 6);
編譯器會自動將匹配到正確的模板函數,若是與默認參數的類型不匹配的時候會編譯報錯。例如,默認參數改成int*或者char*。
注意:
默認模板參數不適用於模板函數,包括全局函數和類成員函數。
若是模板形參爲默認參數,那後面的形參都必須設置默認值。
類外定義成員函數的時候,默認參數應該省略
五、模板聲明、定義、實例化的概念
聲明
聲明就是讓編譯器知道有這樣的一個函數或者類,一個模板形式爲
template<class T> void test(T);
template<class T> class A;
這就是模板的聲明,後面沒有函數體或者類體,注意A後面的分號。
定義
定義跟普通的函數定義、類定義是同樣的。
注意類模板的定義方式,其實除了加上了template<class T>前綴,和className<T>指定域,和普通類並無區別。
例如,普通類A能夠定義爲:
A::A(){}
A::~A(){}
void A::test(){}
換成類模板應該改成
template<class T>
A<T>::A(){}
template<class T>
A<T>::~A(){}
template<class T>
void A<T>::test(T){}
實例化
實例化是在模板調用的時候,例如A<T> obj;
若是建立了這樣的實例,在下次再次條用一樣的模板實例的時候,是不會建立新的實例。例如,A<int> obj;就建立了一個int型的實例,下次在建立另外一個A<int> obj2;的時候是不會建立新實例。
對於指針或者引用,以後在真正指向相關的對象的時候纔會實例化。例如A *m; 或 A &n;並不會實例化,可是m = new A():就會實例化。
下面會在實參推演的過程當中,說明實例化的其餘注意事項。
六、實參推演
模板的實例化是在模板調用的時候,例如,
template<class T> void swap(T x, T y){}
在調用swap(3, 2); 的時候會根據實參推演出swap( int, int);而且創建實例。
固然,這個實例創建好後再次調用swap(2, 3); 是不會在創建實例,會使用已經有的。
對於模板,實例化會創建實例,可是並不會出現類型轉化。例如,
template<class T>void h(T x){}
void main() {
int a = 2;
short b = 3;
h(a);
h(b);
}
最開始使用h(a); 會創建一個實例,類型爲int。在使用h(b); 的時候會再次創建一個實例,類型爲short。並不會像普通函數那樣存在類型轉換。
編譯器容許下面實參到模板形參的轉換:
(1)數組到指針的轉換
template<class T> void h(T *x){}
int a[] = {1, 2, 3};
h(a);
能夠看到模板形參爲指針類型,實參爲數組類型。編譯器容許數組到指針的轉換,這個時候會實例化一個h(int *);的實例,T會被轉換爲int,函數體中的T會被int替換。換言之,若是已經存在了一個h(int *)的實例,這個時候的數組調用時不會產生新的實例,會直接使用h(int *);
(2)限制修飾符轉換
即把const或volatile限定符加到指針上。好比template<class T> void h(const T* a){},int b=3; h(&b);雖然實參&b與形參const T*不徹底匹配,但由於容許限制修飾符的轉換,結果就把&b轉換成const int *。而類形型參T被轉換成int。若是模板形參是非const類型,則不管實參是const類型仍是非const類型調用都不會產生新的實例。
(3)到一個基類的轉換(基類爲一個模板類)
例如,
template<class T1>class A{};
template<class T1> class B:public A<T1>{};
template<class T2> void h(A<T2>& m){}
在main函數中有B<int> n; h(n);函數調用的子類對象n與函數的形參A<T2>不徹底匹配,但容許到一個基類的轉換。
在這裏轉換的順序爲,首先把子類對象n轉換爲基類對象A<int>,而後再用A<int>去匹配函數的形參A<T2>&,因此最後T2被轉換爲int,也就是說函數體中的T將被替換爲int。
七、顯示實例化
隱式實例化
例若有模板函數
template<class T> void h(T a){}
h(2)這時h函數的調用就是隱式實例化,既參數T的類型是隱式肯定的。
函數模板顯示實例化
語法是:
template 函數反回類型 函數名<實例化的類型> (函數形參表);
注意這是聲明語句,要以分號結束。例如,
template void h<int> (int a);
這樣就建立了一個h函數的int 實例。
再若有模板函數
template<class T> T h( T a){}
注意這裏h函數的反回類型爲T,顯示實例化的方法爲template int h<int>(int a); 把h模板函數實例化爲int 型。
注意:
對於給定的函數模板實例,顯示實例化聲明在一個文件中只能出現一次。
在顯示實例化聲明所在的文件中,函數模板的定義必須給出,若是定義不可見,就會發生錯誤。
不能在局部範圍類顯示實例化模板,實例化模板應放在全局範圍內,即不能在main函數等局部範圍中實例化模板。由於模板的聲明或定義不能在局部範圍或函數內進行。
八、顯示模板實參
適用於函數模板,即在調用函數時顯示指定要調用的時參的類型。
格式:
在調用模板函數的時候在函數名後用<>尖括號括住要顯示錶示的類型
例如,有模板函數
template<class T> void h(T a, T b){}
則h<double>(2, 3.2)就把模板形參T顯示實例化爲double類型。
顯示模板實參用於同一個模板形參的類型不一致的狀況。
對於上面的模板,h(2, 3.2)的調用會出錯,由於兩個實參類型不一致,第一個爲int 型,第二個爲double型。而用h<double>(2, 3.2)就是正確的,雖然兩個模板形參的類型不一致但這裏把模板形參顯示實例化爲double類型,這樣的話就容許進行標準的隱式類型轉換,即這裏把第一個int 參數轉換爲double類型的參數。
顯示模板實參用於函數模板的返回類型中。
例若有模板函數
template<class T1, class T2, class T3> T1 h(T2 a, T3 b){}
則語句int a=h(2,3)或h(2,4)就會出現模板形參T1沒法推導的狀況。而語句int h(2,3)也會出錯。用顯示模板實參就參輕鬆解決這個問題,好比h<int, int, int>(2,3)即把模板形參T1實例化爲int 型,T2和T3也實例化爲int 型。
顯示模板實參應用於模板函數的參數中沒有出現模板形參的狀況。
例如template<class T>void h(){}若是在main函數中直接調用h函數如h()就會出現沒法推演類型形參T的類型的錯誤,這時用顯示模板實參就不會出現這種錯誤,調用方法爲h<int>(),把h函數的模板形參實例化爲int 型,從而避免這種錯誤。
顯示模板實參用於函數模板的非類型形參。
例如,
template<class T,int a> void h(T b){}
而調用h(3)將出錯,由於這個調用沒法爲非類型形參推演出正確的參數。這時正確調用這個函數模板的方法爲h<int, 3>(4),首先把函數模板的類型形參T推演爲int 型,而後把函數模板的非類型形參int a用數值3來推演,把變量a設置爲3,而後再把4傳遞給函數的形參b,把b設置爲4。注意,由於int a是非類型形參,因此調用非類型形參的實參應是編譯時常量表達式,否則就會出錯。
在使用顯示模板實參時,咱們只能省略掉尾部的實參。
例如,
template<class T1, class T2, class T3> T1 h(T2 a, T3 b){}
在顯示實例化時h<int>(3, 3.4)省略了最後兩個模板實參T2和T3,T2和T3由調用時的實參3和3.4隱式肯定爲int 型和double型,而T1被顯示肯定爲int 型。h<int, , double><2,3.4>是錯誤的,只能省略尾部的實參。
顯示模板實參最好用在存在二義性或模板實參推演不能進行的狀況下。
九、模板特例化
template<class T>void h(T a){}
這個函數體中的功能使用全部類型,可是若是int型比較特殊,不須要這裏的函數體。這樣就須要對模板進行特殊化。
函數模板特例化格式:
template<> 返回類型 函數名<要特化的類型>(參數列表) {函數體}
顯示特化以template<>開頭,代表要顯示特化一個模板
在函數名後<>用尖括號括住要特化的類型版本。
對於上面的函數模板,其int 類型的特化版本爲template<> void h<int>(int a){}
當出現int 類型的調用時就會調用這個特化版本,而不會調用通用的模板,好比h(2),就會調用int 類型的特化版本。
若是能夠從實參中推演出模板的形參,則能夠省略掉顯示模板實參的部分。
例如:template<> void h(int a){}。注意函數h後面沒有<>符號,即顯示模板實參部分。
對於反回類型爲模板形參時,調用該函數的特化版本必需要用顯示模板實參調用,若是不這樣的話就會出現其中一個形參沒法推演的狀況。例如,
template<class T1,class T2,class T3> T1 h(T2 a,T3 b){}
有幾種特化狀況:
狀況一:
template<> int h<int,int>(int a, in b){}
該狀況下把T1,T2,T3的類型推演爲int 型。在主函數中的調用方式應爲h<int>(2,3)。
狀況二:
template<> int h(int a, int b){}
這裏把T2,T3推演爲int 型,而T1爲int 型,但在調用時必須用顯示模板實參調用,且在<>尖括號內必須指定爲int 型,否則就會調用到通用函數模板,如h<int>(2,3)就會調用函數模板的特化版本,而h(2,3)調用會出錯。h<double>(2,3)調用則會調用到通用的函數模板版本。
下面幾種狀況的特化版本是錯誤的,例如,
template<> T1 h(int a,int b){}
這種狀況下T1會成爲不能識別的名字,於是出現錯誤。
template<> int h<double>(int a,int b){}
在這種狀況下返回類型爲int 型,把T1肯定爲int 而尖括號內又把T1肯定爲double型,這樣就出現了衝突。
具備相同名字和相同數量返回類型的非模板函數(即普通函數),也是函數模板特化的一種狀況,這種狀況將在後面參數匹配問題時講解。
類模板特例化格式:
template<> class 類名<要特化的類型> {類體};
例如,
template<class T1,class T2> class A{};
特例化爲:
template<> class A<int, int>{};
在類特化的外部定義成員的方法
例如
template<class T> class A{public: void h();};
類A特化爲
template<> class A<int>{public: void h();};
在類外定義特化的類的成員函數h的方法爲:
void A<int>::h(){}
在外部定義類特化的成員時應省略掉template<>。
類的特化版本應與類模板版本有相同的成員定義,若是不相同的話那麼當類特化的對象訪問到類模板的成員時就會出錯。
由於當調用類的特化版本建立實例時建立的是特化版本的實例,不會建立類模板的實例,特化版本若是和類的模板版本的成員不同就有可能出現這種錯誤。好比:模板類A中有成員函數h()和f(),而特化的類A中沒有定義成員函數f(),這時若是有一個特化的類的對象訪問到模板類中的函數f()時就會出錯,由於在特化類的實例中找不到這個成員。
類模板的部分特化
好比有類模板
template<class T1, class T2> class A{};
則部分特化的格式爲
template<class T1> class A<T1, int>{};
將模板形參T2特化爲int 型,T1保持不變。
部分特化以template開始,在<>中的模板形參是不用特化的模板形參,在類名A後面跟上要特化的類型。
若是要特化第一個模板形參T1,則格式爲
template<class T2> class A<int, T2>{};
部分特化的另外一用法是:
template<class T1> class A<T1,T1>{};
將模板形參T2也特化爲模板形參T1的類型。
在類部分特化的外面定義類成員的方法
例若有部分特化類
template<class T1> class A<T1,int>{public: void h();};
則在類外定義的形式爲:
template<class T1> void A<T1,int>::h(){}
注意當在類外面定義類的成員時template 後面的模板形參應與要定義的類的模板形參同樣,這裏就與部分特化的類A的同樣template<class T1>。
其餘說明:
(1)能夠對模板的特化版本只進行聲明,而不定義。好比template<> void h<int>(int a);注意,聲明時後面有個分號。
(2)在調用模板實例以前必需要先對特化的模板進行聲明或定義。
一個程序不容許同一模板實參集的同一模板既有顯示特化又有實例化。例如,
template<class T> void h(T a){}
在h(2)以前沒有聲明該模板的int 型特化版本,而是在調用該模板後定義該模板的int 型特化版本,這時程序不會調用該模板的特化版本,而是調用該模板產生一個新的實例。這裏就有一個問題,究竟是調用由h(2)產生的實例版本呢仍是調用程序中的特化版本。
(3)由於模板的聲明或定義不能在局部範圍或函數內進行。因此特化類模板或函數模板都應在全局範圍內進行。
(4)在特化版本中模板的類型形參是不可見的。例如,
template<> void h<int,int>(int a,int b){T1 a;}
就會出現錯誤,在這裏模板的類型形參T1在函數模板的特化版本中是不可見的,因此在這裏T1是未知的標識符,是錯誤的。
十、模板與繼承
子類並不會從通用的模板基類繼承而來,只能從模板基類的某一實例繼承而來。
繼承方式一:
template<class T1>class B:public A<int>
{類體};
繼承自A某一實例,這裏是A的int型實例
繼承方式二:
template<class T1>class B:public A<T1>
{類體};
在實例化B的時候會用一樣的類型實例化基類A
繼承方式三:
class B:public A<int> {類體}; 類B不是模板類