C++之模板进阶
模板進階
文章目錄
- 模板進階
- 非類型模板參數
- 非類型模板參數缺省值
- 模板的特化
- 概念
- 函數模板的特化
- 類模板的特化
- 全特化
- 偏特化
- 模板分離編譯
- 什么是分離編譯
- 模板總結
- 優點
- 缺點
沒了解過模板的讀者,先學習模板初階: C++之模板初階
通過模板我們可以實現泛型編程,模板分為函數模板和類模板,下面我們就說點模板進階的一些東西。
非類型模板參數
模板參數分類類型形參與非類型形參。
類型形參:出現在模板參數列表中,跟在class或者typename之類的參數類型名稱。
非類型形參:就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用。
舉個例子,比如我們寫個靜態棧結構:
#define N 10 template<class T>//類型模板參數 class Stack { private:T _a[N];size_t _top; }; int main() {Stack<int> st1;//大小為10Stack<int> st2;}我們想要改棧的大小就改宏就可以了,但是我們有兩個棧呢?一個棧的大小想要10,另一個棧想要1000的大小,這樣就不能滿足多個棧的需求了,除非再定義一個類模板,但是這樣代價太大,在C++模板當中有一個非類型的模板參數概念:
template<class T,size_t N>//T是類型模板參數,N是非類型模板參數,N是一個常量 class Stack { private:T _a[N];size_t _top; }; int main() {Stack<int,100> st1;//100Stack<int,20000> st2;//20000 }我們這樣就可以通過傳參完成每個棧想要的大小需求了,在template<class T,size_t N>當中,T是類型模板參數,這里的N是非類型模板參數,這里的N是一個常量
我們呢可以這樣傳參嗎?
int main() {static int n;cin>>n;Stack<int,n> st;//error,非類型模板參數不能是變量return 0; }這樣是錯誤的,非類型模板參數不能是變量
在STL中的容器當中,C++11新增了array這個容器,array這個容器就是類似這樣的結構,它使用了非類型的模板參數:
template<class T,size_t N> class Array { private:T _a[N]; }array是一個大小固定的容器
但是array這個容器不建議使用,為什么呢?
函數調用會建立棧幀,數組過大,可能會造成棧溢出,用vector的話,空間不夠就增容,比較靈活,增容是在堆區開辟空間,而堆區時進行動態開辟的地方,它的空間較大,知道需要的數據大小直接使用vector中的resize就好了,沒必要使用array這個容器,這里可以知道C++11增加的array容器基本沒有什么用,它的缺點大于它的優點。
C++缺點之一:后期C++11等等標準增加了不少雞肋的語法,讓語言變得臃腫,學習成本增加,一些剛需的東西,姍姍來遲,甚至還沒來(網絡庫)。
非類型模板參數缺省值
模板參數都可以給缺省值,模板參數給缺省值和函數參數給缺省值是完全類似的,可以全缺省,也可以半缺省(必須從右往左連續缺省)
比如:
//模板參數都可以給缺省值 //模板參數給缺省值和函數參數給缺省值是完全類似的 //可以全缺省 //也可以半缺省 -- 必須從右往左連續缺省 template<class T,size_t N = 10> class Array { private:T _a[N]; } int main() {Array<int> a1;Array<int,20> a2;return 0; }需要注意的是,如果全都是缺省值時不能這樣創建對象:
Array a1;全部都是缺省值,我們可以不傳參數,但是我們知道Array是個模板,模板也是有類型的,我們需要這樣:
Array<> a1;注意:
模板的特化
概念
通常情況下,使用模板可以實現一些與類型無關的代碼,但對于一些特殊類型的可能會得到一些錯誤的結果,比如:
template<class T> bool IsEqual(const T& left,const T& right) { return left==right; } int main() {cout<<IsEqual(1,2)<<endl;char p1[] = "hello";char p2[] = "hello";cout<<IsEqual(p1,p2)<<endl;//數組名是指針常量return 0; }這個模板,用來比較整形可以使用,但是我們用來比較字符串呢?這樣就出問題了。
我們想一想可能可以這樣解決:
template<class T> bool IsEqual(const T& left,const T& right) {if(T == const char*){return strcmp(left,right)==0; //可是語法不支持}else{return left==right; } }這樣貌似也可以實現字符串的比較,但是這個是語法不支持的,所以不能這樣。
模板的特化,針對某些類型進行特殊化處理,我們可以這樣寫:
bool IsEqual(const char*& left,const char*& right) {return strcmp(left,right)==0; } int main() {cout<<IsEqual(1,2)<<endl;char p1[] = "hello";char p2[] = "hello";cout<<IsEqual(p1,p2)<<endl;//數組名是指針常量return 0; }我們調式過后,發現這里不會進這個函數,因為數組名是指針常量,則這里的const修飾的是*left,是left指向的內容不能修改,而不是left不能修改,這里屬于權限放大了
需要這樣改,這樣就可以進去了:
//模板的特化,針對某些類型進行特殊化處理 bool IsEqual(const char*& const left,const char*& const right) {return strcmp(left,right)==0; }也可以這樣改,將引用去掉:
bool IsEqual(const char* left,const char* right) {return strcmp(left,right)==0; }函數模板的特化
函數模板的特化步驟:
當我們交換的類型為vector時,此時用模板函數進行交換代價太大了,一次拷貝構造+兩次賦值重載,所以我們可以這樣寫:
//函數模板的特化 template<> void Swap<vector<int>>(vector<int>& a,vector<int>& b) {a.swap(b); }這就是函數模板的特化,有點類似于指定類型進行顯式實例化
當然也可以這樣,利用模板的匹配原則,進行特殊化處理:
//模板的匹配原則,進行特殊化處理 void Swap(vector<int>& a,vector<int>& b) {a.swap(b); }類模板的特化
全特化
全特化即是將模板參數列表中所有的參數都確定化。
template<class T1,class T2> class Data { public:Data() { cout << "Data<T1,T2>"<<endl; } private:T1 _d1;T2 _d2; }; //全特化 template<> class Data<double, double> { public:Data() { cout << "Data<double,double>" << endl; } private:T1 _d1;T2 _d2; };int main() {Data<int,int> d1;Data<double,double> d2;return 0; }偏特化
偏特化:任何針對模版參數進一步進行條件限制設計的特化版本。比如對于以下模板類:
偏特化有兩種表現方式:
- 部分特化,將模板參數類表中的一部分參數特化。
- 參數更進一步的限制,偏特化并不僅僅是指特化部分參數,而是針對模板參數更進一步的條件限制所設計出來的一個特化版本。
模板分離編譯
什么是分離編譯
一個程序(項目)由若干個源文件共同實現,而每個源文件單獨編譯生成目標文件,最后將所有目標文件鏈接起來形成單一的可執行文件的過程稱為分離編譯模式。
首先上結論:模板不支持分離編譯
我們正常寫模板是需要聲明和定義放在一起的,是因為模板不支持分離編譯:
//.h文件 template<class T> void F(const T& x) {cout<<"void F(const T& x)"<<endl; }下面我們來驗證不支持分離編譯的原因是什么:
首先在.h文件中寫模板的聲明:
template<class T> void F(const T& x);//聲明在.cpp中寫模板的定義:
#include"Func.h" template<class T> void F(const T& x)//定義 {cout << "void F(const T& x)" << endl; }在test.cpp中測試:
#include"Func.h" int main() {F(1); }此時出現了鏈接錯誤,為什么我們平時使用的普通函數不會報錯,而模板函數會報鏈接錯誤的呢?
首先我們有這三個文件:
Func.h Func.cpp test.cpp
程序生成可執行程序的過程是編譯和鏈接,編譯階段又分為預處理、編譯、匯編三個階段:
1、預處理
預處理階段進行頭文件展開、宏替換、條件編譯、去注釋
預處理之后生成的文件是Func.i、test.i,Func.cpp和test.cpp分別變成了:
Func.i
template<class T> void F(const T& x); void F(const T& x) {cout<<"void F(const T& x)"<<endl; }Test.i
template<class T> void F(const T& x); int main() {F(1); }2、編譯
編譯階段進行語法檢查,語義分析,符號匯總等等,最后生成匯編代碼
對應生成的文件是Func.s和test.s
3、匯編
匯編階段是把匯編代碼轉成二進制機器碼,并且生成符號表(定義在本文件中的函數有明確的地址,沒有定義在本文件中的函數,符號表中還沒有地址)
對應生成的文件是Func.o和test.o
4、鏈接
在鏈接階段把類型test.o里面F和Print這樣沒有函數地址的地方,拿名字去其他目標文件中去找,找到以后填到符號表。再把目標文件合并到一起,生成可執行程序
模板的實例化是在編譯階段要做的事情,在編譯階段,Func.i生成Func.s時并不知道T是什么類型,實例化的指令test.i文件里才知道,所以并沒有實例化,而在鏈接之前它們不進行交匯,各自干各自的事情,Func.i生成Func.s沒有實例化生成,所以在鏈接時候不會找到F函數模板生成的實例化函數,就發生了鏈接錯誤。
解決方案一:
在Func.cpp文件中顯式指定實例化
template void F(const int& x);缺陷:用一個類型就得顯式實例化一個,非常麻煩
解決方案二:
不分離編譯。聲明和定義或者直接定義在.h中
對于類也是一樣的:
Func.h
#include<iostream> using namespace std; template<class T> void F(const T& x)//定義 {cout << "void F(const T& x)" << endl; } template<class T> class Stack { public:Stack();~Stack(); private:T* _a;int _top;int _capacity; };Func.cpp
#include"Func.h" template<class T> Stack<T>::Stack() {_a = new T[10];_top = 0;_capacity = 10; } template<class T> Stack<T>::~Stack() {delete[] _a;_a = nullptr; }test.cpp
#include"Func.h" int main() {Stack<int> st; }此時運行程序也會發生鏈接錯誤:
我們的解決方法和函數模板是完全類似的:
要么在Func.cpp文件中顯式指定實例化,要么不進行分離編譯
對于類模板,還有一個概念:按需實例化
比如我們還另外寫了push函數
#include<iostream> using namespace std; template<class T> void F(const T& x)//定義 {cout << "void F(const T& x)" << endl; } template<class T> class Stack { public:Stack(){_a = new T[10];_top = 0;_capacity = 10;}template<class T>~Stack(){delete[] _a;_a = nullptr;}void push(const T& x){_a[_top] = x;_top++;} private:T* _a;int _top;int _capacity; };但是我們在test.cpp當中不使用棧的push操作:
#include"Func.h" int main() {Stack<int> st;return 0; }此時我們故意將push函數弄出個語法錯誤,比如去掉分號:
void push(const T& x) {_a[_top] = x_top++; }push有語法問題,沒有檢測出來,編譯沒有報錯
原因:
模板如果沒有實例化,編譯器不會去檢查模板內部語法錯誤,我們實例化了棧這個類,對類模板是按需實例化,調用了哪個成員函數就實例化誰
當我們使用push成員函數時:
此時就會報錯了。
模板總結
優點
- 模板復用了代碼,節省資源,更快的迭代開發,C++的標準模板庫(STL)因此而產生
- 增強了代碼的靈活性
缺點
- 模板會導致代碼膨脹問題,也會導致編譯時間變長
- 出現模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤
總的來說模板的優點是遠大于缺點的
總結
- 上一篇: Excel表格转成PDF后页数变多有空白
- 下一篇: excel表格转pdf格式的方法介绍