目錄
非類型模板參數
類模板的特化
分類
函數模板的特化
模板分離編譯
問題
解決方法
1)不對模板定義進行分離或對模板進行特例化;
2)將聲明和定義放在同一個文件
總結
關于C++模板的使用在《C++類和對象》中有介紹,本篇博客主要幫助讀者對C++模板的一些特殊用法進行總結。
std::string的模擬實現-CSDN博客文章瀏覽閱讀881次,點贊40次,收藏38次。通過模擬實現string可以幫助讀者理解string成員函數的底層邏輯,讓讀者更準確地使用string中的成員函數。https://blog.csdn.net/2401_87944878/article/details/145715615
非類型模板參數
模板參數分為兩種:類型模板參數和非類型模板參數。
類型模板參數:跟在class和typename后的類型。
非類型模板參數:用一個常量作為參數,即不再使用class或typename來定義的參數。
template<class T,size_t N=10>
class Seqlist
{
public:private:T _arr[N];size_t size;
};
如上圖所示:T是一個類型模板參數,用于替代各自類型;而N就是一個非類型模板參數,用于記錄說要開辟的靜態順序表的大小,默認為10。
簡而言之:就是有具體類型的參數就是非類型模板參數。
但是同樣非類型模板參數也有一些要求:
1)參數應被當作常量使用。
2)參數類型必須是整形(bool,int,char,enum),指針,引用。(C++20添加了部分其他類型);
類模板的特化
在一些情況下,我們又是要對類的部分類型進行特殊化處理,即T在不同類型下,可能會出現不同的需求(函數實現),此時就需要引入類模板的特例化。
template<class T1,class T2>
class Date
{
public:
///
/// .....實現類成員函數
///
private:T1 _a;T2 _b;
};
上面代碼是非特化模板,T1和T2可以表示任意類型。在編譯階段才去實例化為具體類型。
template<>
class Date<int, int>
{
public:
///
/// ......實現類成員函數
///
private:int _a;int _b;
};
以上代碼是對Date類進行特例化,指明是<int ,int >類型。類模板特例化后相當于一個新的"類",但是這個類也不能脫離非特例化類獨自出現,因為相當于新"類",所以需要自己再手動實現其功能(函數)。
那么對于特例化模板參數和非特例化模板參數來說,編譯器會對非特例化模板參數進行實例化來實現類???
實際上是不會的:如果會那我們對模板特例化就沒有了意義,同時編譯器已經識別了類型匹配,它會偷懶直接去掉現成的,而不是去耗時耗力的實例化。
Date<int, int> d1; //去直接調用特例化的模板類
Date<char, int> d2; //將模板參數實例化后調用
分類
類模板的特化分為兩種:全特化和偏特化。
都是字面意思:全特化就是全部模板參數都有指定;偏特化指的是部分模板參數被指定或者對某一類型進行限制。
//非特例化
template<class T1,class T2>
class test
{
public:private:T1 _a;T2 _b;
};
全特例化:不用給模板參數
//全特例化
template<>
class test<int, char>
{
public:private:int _a;char _b;
};
偏特例化:部分參數指定
//偏特例化
template<class T2>
class test<int,T2>
{
public:private:int _a;T2 _b;
};
偏特例化:對類型進行限制;
此處演示特例化為指針,也可以限制為引用...
//偏特例化:對類型進行限制
template<class T1, class T2>
class test<T1*,T2*> //將其參數限制為指針
{
public:private:T1* _a;T2* _b;
};
當然也可以對類型進行混合限制。
//偏特例化:對類型進行限制
template<class T1, class T2>
class test<T1&, T2*> //第一個參數限制為引用,第二個參數限制為指針
{
public:private:T1& _a;T2* _b;
};
?注意:以上代碼的成員函數均沒有寫出,但是要知道的是:特化的類成員,類函數需要自己寫來進行特殊化處理。
函數模板的特化
與類模板一樣,函數模板也能進行特例化。
下面實現一個比較的函數。
template<class T>
bool Less(T x, T y)
{return x < y;
}
以上是實現一個各種類型的交換函數。但是當實參是指針的時候,比較就會比較的是指針的地址,這不是我們想要的結果,所以進行特化。
template<>
bool Less<int*>(int* x, int* y)
{return *x < *y;
}
template<class T>
bool Less(T* x, T* y)
{return *x < *y;
}
模板分離編譯
問題
模板分離編譯是一個很不推薦的寫法,不提倡將函數的聲明和定義分離到兩個文件中去。
以下代碼實現一個仿函數來實現小于比較。
"test.h"頭文件
namespace less
{template<class T>class Less{public:bool operator()(T x,T y);};
}
test.cpp文件
template<class T>
bool less::Less<T>::operator()(T x, T y)
{return x < y;
}
main函數文件調用
using namespace less;
Less<int> less;
int a = 10;
int b = 33;
cout << less(a, b) << endl;
可以看到將類的函數的聲明和定義分離,但是調用后卻報錯了。
可以看到此處運行時不是編譯錯誤,而是連接錯誤,編譯器沒有找到指定的函數,為什么呢??
編譯器沒有找到函數意味著在test.cpp文件中函數的地址沒有進入函數表。
原因在于:在編譯期間,每個文件都是分開編譯的,main函數中只包含頭文件,檢查語法時編譯器在頭文件中找到了仿函數的聲明認為有這個函數,但是test.cpp文件中的仿函數是模板函數,相當于一個圖紙,沒有實例化所以不會在test,cpp函數表中生成函數地址,test.cpp文件函數表中也就沒有仿函數地址,鏈接時就會報錯了。
解決方法
1)不對模板定義進行分離或對模板進行特例化;
此處對上面代碼中的模板進行特例化。
頭文件顯示實例化
template<class T>
class Less
{
public:bool operator()(T x,T y);
};template<>
class Less<int>
{
public:bool operator()(int x, int y);
};
test.cpp文件
bool less::Less<int>::operator()(int x, int y)
{return x < y;
}
2)將聲明和定義放在同一個文件
在C++的庫中,常常使用"xxxx.hpp"或"xxxx.h"文件來存放類模板函數的聲明和定義。
?補充
typename和class的區別
typename和class都是模板類型的聲明,在大多數情況下其沒有本質區別,但是typename有指明類型的意思。以下是只能用typename的特例。
template<class T,class Container=deque<T>>
class Print
{
public:void test(){Container::const_iterator it = _con.begin();}private:Container _con;
};
以上代碼,類成員函數test中定義了一個it的迭代器,看上去沒有任何問題,但是編譯時卻報錯了。
可以看到此處出現了語法錯誤,編譯器無法確定Continer::const_iterator是什么。
為什么編譯器無法判斷其是什么呢???
原因:Container沒有實例化所以編譯器不知道這個容器里面定義了什么。那Container::a,其中a可以是什么???a可以是一個靜態變量,一個內部類,也能是一種類型等等,編譯器無法識別其具體是什么,所以此處需要指明其是類型,即添加typename的前綴。
typename Container::const_iterator it = _con.begin();
關于array
在C++中添加了一個數組的類array,實際上其與數組沒有很大的區別,唯一的區別可能就是array比arr對于越界的檢查更嚴格。
總結
模板既有優點也有缺點。
優點:1)模板服用代碼,節省資源,更快的迭代開發;
? ? ? ? ? ?2)增強了代碼的靈活性。
缺點:1)導致代碼膨脹問題,編譯時間增加;
? ? ? ? ? ?2)出錯時,報錯信息非常凌亂,不易定位錯誤位置。