C++模板全方位深入解讀
1.泛型編程
如何實(shí)現(xiàn)一個(gè)通用的交換函數(shù)?
這點(diǎn)函數(shù)重載可以做到,比如一下Swap函數(shù)的重載,分別重載了倆種不同參數(shù)類(lèi)型的Swap
void Swap(int& x, int& y) { int tmp = x; x = y; y = tmp; } void Swap(char& x, char& y) { char tmp = x; x = y; y = tmp; }
但是這也帶來(lái)了幾點(diǎn)不好的地方:
1.重載的函數(shù)僅僅是類(lèi)型不同,代碼的復(fù)用率比較低,只要有新類(lèi)型出現(xiàn),就需要增加對(duì)應(yīng)的函數(shù)
2.代碼的可維護(hù)性比較低,一個(gè)出錯(cuò)可能所有的重載均出錯(cuò)
那么有什么好的解決方法嗎?我們能否告訴編譯器一個(gè)模子,讓編譯器根據(jù)不同類(lèi)型利用該模子自己去生成相應(yīng)的代碼呢?
當(dāng)然能,這就是泛型編程,即編寫(xiě)與類(lèi)型無(wú)關(guān)的通用代碼,這是代碼復(fù)用的一種手段。而模板是泛型編程的基礎(chǔ)。模板分為兩種,函數(shù)模板和類(lèi)模板。
2.函數(shù)模板
概念
函數(shù)模板代表了一個(gè)函數(shù)家族,該函數(shù)模板與類(lèi)型無(wú)關(guān),在使用時(shí)被參數(shù)化,根據(jù)實(shí)參類(lèi)型產(chǎn)生函數(shù)的特定類(lèi)型版本。
函數(shù)模板的格式
template<typename T1, typename T2,......,typename Tn>
即:返回值類(lèi)型 函數(shù)名(參數(shù)列表){}
其中typename可以改成class(不能用struct)
例:
template <typename T> void Swap(T& x, T& y) { T tmp = x; x = y; y = tmp; }
函數(shù)模板的原理
函數(shù)模板是一個(gè)藍(lán)圖,它本身并不是函數(shù),是編譯器用使用方式產(chǎn)生特定具體類(lèi)型函數(shù)的模具。所以其實(shí)模板就是將本來(lái)應(yīng)該我們做的重復(fù)的事情交給了編譯器。
在編譯器編譯階段,對(duì)于模板函數(shù)的使用,編譯器需要根據(jù)傳入的實(shí)參類(lèi)型來(lái)推演生成對(duì)應(yīng)類(lèi)型的函數(shù)以供調(diào)用。比如:當(dāng)用double類(lèi)型使用函數(shù)模板時(shí),編譯器通過(guò)對(duì)實(shí)參類(lèi)型的推演,將T確定為double類(lèi)型,然后產(chǎn)生一份專(zhuān)門(mén)處理double類(lèi)型的代碼,對(duì)于字符類(lèi)型也是如此。
函數(shù)模板的實(shí)例化
用不同類(lèi)型的參數(shù)使用函數(shù)模板時(shí),稱(chēng)為函數(shù)模板的實(shí)例化。模板參數(shù)的實(shí)例化分為兩種:隱式實(shí)例化和顯式實(shí)例化。
隱式實(shí)例化
讓編譯器自己推演函數(shù)參數(shù)的類(lèi)型。
需要注意的是隱式實(shí)例化的參數(shù)一定要匹配,否則可能產(chǎn)生分歧導(dǎo)致編譯器無(wú)法識(shí)別。比如:
template<typename T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, b1 = 20; double a2 = 10.0, b2 = 20.0; Add(a1, b2); return 0; }
編譯器報(bào)錯(cuò),該語(yǔ)句不能通過(guò)編譯,因?yàn)闊o(wú)法確定T是int還是double。
如何處理?有兩種方式:
1.強(qiáng)制類(lèi)型轉(zhuǎn)換!但值得注意的是,強(qiáng)轉(zhuǎn)會(huì)產(chǎn)生臨時(shí)變量,臨時(shí)變量是具有常性的,需要const修飾一下!
2.使用顯式實(shí)例化
顯式實(shí)例化
在函數(shù)名后的< >中指定模板參數(shù)的實(shí)際類(lèi)型。
template<typename T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, b1 = 20; double a2 = 10.0, b2 = 20.0; cout<<Add<int>(a1, b2)<<endl; return 0; }
模板參數(shù)的匹配原則
1.一個(gè)非模板函數(shù)可以和一個(gè)同名的函數(shù)模板同時(shí)存在,而且該函數(shù)模板還可以被實(shí)例化為這個(gè)非模板函數(shù)。
2.對(duì)于非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動(dòng)時(shí)會(huì)優(yōu)先調(diào)用非模板函數(shù)而不會(huì)從該模板產(chǎn)生出一個(gè)實(shí)例。如果模板可以產(chǎn)生一個(gè)具有更好匹配的函數(shù), 那么將選擇模板
3.模板函數(shù)不允許自動(dòng)類(lèi)型轉(zhuǎn)換,但普通函數(shù)可以進(jìn)行自動(dòng)類(lèi)型轉(zhuǎn)換
- 如果有定義出來(lái)的函數(shù),且類(lèi)型完全匹配調(diào)用時(shí)實(shí)參類(lèi)型,則執(zhí)行定義出來(lái)的函數(shù).如果定義出來(lái)的函數(shù),不符合,則執(zhí)行模板推演.
- 這部分沒(méi)啥難度,不再舉例說(shuō)明,總的來(lái)說(shuō),對(duì)于函數(shù)調(diào)用的優(yōu)先級(jí)就是:完全匹配 >模板匹配 >轉(zhuǎn)換匹配。
3.類(lèi)模板
(1) 類(lèi)模板的定義格式
template<class T1, class T2, ..., class Tn> class 類(lèi)模板名 { // 類(lèi)內(nèi)成員定義 };
注意:
類(lèi)模板中函數(shù)放在類(lèi)外進(jìn)行定義時(shí),需要加模板參數(shù)列表;
template <typename T> class Stack { public: Stack(int capacity = 4) :_a(new T[capacity]) ,_top(0) ,_capacity(capacity) {} ~Stack() { delete[] _a; _top = _capacity = 0; } void Push(T x); private: T* _a; int _top; int _capacity; }; // 注意:類(lèi)模板中函數(shù)放在類(lèi)外進(jìn)行定義時(shí),需要加模板參數(shù)列表 template <typename T> void Stack<T>::Push(T x) { if (_top == _capacity) { _capacity *= 2; T* tmp = (T*)realloc(_a, sizeof(int) * _capacity); if (tmp == nullptr) { cout << "realloc fail" << endl; exit(-1); } _a = tmp; } _a[_top++] = x; }
對(duì)于普通類(lèi),類(lèi)名就是類(lèi)型;對(duì)于類(lèi)模板,類(lèi)名不是類(lèi)型,類(lèi)型是Class < T >
(2) 類(lèi)模板的實(shí)例化
類(lèi)模板實(shí)例化與函數(shù)模板實(shí)例化不同,類(lèi)模板實(shí)例化需要在類(lèi)模板名字后跟 < >,然后將實(shí)例化的類(lèi)型放在<>中即可,類(lèi)模板名字不是真正的類(lèi),而實(shí)例化的結(jié)果才是真正的類(lèi)。
int main() { // Stack只是類(lèi)名,不是類(lèi)型,Stack<int>才是類(lèi)型 Stack<int> s1; Stack<char> s2; return 0; }
4.非類(lèi)型模板參數(shù)
類(lèi)型參數(shù):就是在模板的參數(shù)列表中在class后面加上參數(shù)的類(lèi)型名稱(chēng)。
非類(lèi)型參數(shù):就是用一個(gè)常量作為類(lèi)(函數(shù))模板的一個(gè)參數(shù),在類(lèi)(函數(shù))模板中可將該參數(shù)當(dāng)成常量來(lái)使用。
注意兩點(diǎn):
1.浮點(diǎn)數(shù)、類(lèi)對(duì)象以及字符串是不允許作為非類(lèi)型模板參數(shù)的。
2.非類(lèi)型的模板參數(shù)必須在編譯期就能確認(rèn)結(jié)果。
template<class T =int, size_t N = 10> class array { private: T _array[N]; size_t _size; }
5.模板特化
(1)函數(shù)模板的特化
當(dāng)針對(duì)某一情景或者某一類(lèi)型,函數(shù)模板無(wú)法滿足要求,模板需要有特殊的處理,這個(gè)時(shí)候就需要用到模板的特化。
比如咱們要比較兩個(gè)字符串是否相同:
template<class T> bool IsEqual(T str1, T str2) { return str1 == str2; } int main() { char str1[] = "hello"; char str2[] = "hello"; if (IsEqual(str1, str2)) cout << "true"; else cout << "false"; }
上述代碼輸出false, 不滿足咱們的要求, 因?yàn)檎{(diào)用函數(shù)IsEqual()時(shí)傳遞過(guò)去的是兩個(gè)char*類(lèi)型,他們兩個(gè)比較的不是字符串的內(nèi)容,而是指針的地址,所以返回false。
此時(shí)模板特化派上用場(chǎng)了:如果要比較char*, 可以用strcmp來(lái)對(duì)這個(gè)情況進(jìn)行特殊處理
template<> bool IsEqual<char*>(char* str1, char* str2) { return strcmp(str1, str2) == 0; }
此時(shí)就返回true, 符合預(yù)期了。
函數(shù)模板的特化步驟:
- 必須要先有一個(gè)基礎(chǔ)的函數(shù)模板
- 關(guān)鍵字template后面接一對(duì)空的尖括號(hào)<>
- 函數(shù)名后跟一對(duì)尖括號(hào),尖括號(hào)中指定需要特化的類(lèi)型
- 函數(shù)形參表: 必須要和模板函數(shù)的基礎(chǔ)參數(shù)類(lèi)型完全相同
(2)類(lèi)模板的特化
類(lèi)也是同理,如果需要有特殊情景也需要特化處理
以如下類(lèi)舉例(后邊全特化、偏特化都針對(duì)它):
template<class T1, class T2> class test { public: test() { cout << "test<T1, T2>" << endl; } private: T1 _x; T2 _y; };
全特化
全特化即是將模板參數(shù)列表中所有的參數(shù)都確定化。
例如:
這里對(duì)test<int,double>版本特化
template<> class test<int, double> { public: test() { cout << "test<int, double>" << endl; } private: int _x; double _y; }; int main() { test<double, double> t1; test<int, double> t2; }
偏特化
偏特化即是任何針對(duì)模版參數(shù)進(jìn)一步進(jìn)行條件限制設(shè)計(jì)的特化版本
偏特化有兩種表現(xiàn)方式,一種是部分參數(shù)特化,一種是參數(shù)修飾特化
部分參數(shù)特化
這里對(duì)第二個(gè)參數(shù)特化,只要第二個(gè)參數(shù)是double就會(huì)調(diào)用對(duì)應(yīng)特化版本
template<class T1> class test<T1, double> { public: test() { cout << "test<T1, double>" << endl; } private: T1 _x; double _y; }; int main() { test<double, double> t1; test<int, double> t2; }
參數(shù)修飾特化
比如用指針或者引用來(lái)修飾類(lèi)型,也可以進(jìn)行特化
template<class T1, class T2> class test<T1*, T2*> { public: test() { cout << "test<T1*, T2*>" << endl; } private: T1* _x; T2* _y; }; int main() { test<int*, double*> t; }
6.模板的分離編譯
對(duì)于一個(gè)代碼量比較多的項(xiàng)目,通常都會(huì)采用聲明與定義分離的方法,比如在頭文件進(jìn)行聲明,在源文件完成代碼的實(shí)現(xiàn),最后通過(guò)鏈接的方法鏈接成單一的可執(zhí)行文件。但是C++的編譯器卻不支持模板的分離編譯,一旦進(jìn)行分離編譯,就會(huì)出現(xiàn)鏈接錯(cuò)誤。
問(wèn)題分析
//頭文件a.h template<class T> bool IsEqual(const T& str1, const T& str2); ------------- //源文件a.cpp template<class T> bool IsEqual(const T& str1, const T& str2) { return str1 == str2; } -------------- //test.c #include<iostream> #include"a.h" using namespace std; int main() { cout << IsEqual(3, 5); cout << IsEqual('a', 'b'); }
這里看上去是沒(méi)有問(wèn)題的,但是涉及到了模板的實(shí)例化規(guī)則。
當(dāng)主函數(shù)調(diào)用這個(gè)函數(shù)的時(shí)候他就會(huì)去頭文件中找到函數(shù)的聲明,再通過(guò)聲明找到a.h中的實(shí)現(xiàn)。
但是對(duì)于模板卻并不會(huì)這樣,因?yàn)樯弦徽抡f(shuō)過(guò),模板的實(shí)例化只會(huì)在其第一次使用的時(shí)候才會(huì)進(jìn)行,例如這里IsEqual(3, 5),他就會(huì)去頭文件中尋找,但是頭文件中只有聲明,沒(méi)有定義,無(wú)法將其實(shí)例化。他又想通過(guò)找到a.cpp中的函數(shù)定義來(lái)進(jìn)行實(shí)例化,但是遺憾的是,a.cpp中只有IsEqual(const T& str1, const T& str2)的定義,沒(méi)有IsEqual(const int & str1, const int T& str2),因?yàn)樵赼.cpp中并沒(méi)有使用到該類(lèi)型的實(shí)例,所以自然也不會(huì)為其實(shí)例化出來(lái),這時(shí)test.cpp中就根本無(wú)法找到這個(gè)函數(shù)的實(shí)現(xiàn),就導(dǎo)致了鏈接失敗。
解決方法:
這個(gè)問(wèn)題其實(shí)沒(méi)有什么完美的解決方法
- 將聲明和定義放到同一個(gè)頭文件中。
- 類(lèi)模板顯式實(shí)例化。
到此這篇關(guān)于C++模板全方位深入解讀的文章就介紹到這了,更多相關(guān)C++模板內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
OpenCV圖像特征提取之Shi-Tomasi角點(diǎn)檢測(cè)算法詳解
Harris角點(diǎn)檢測(cè)算法就是對(duì)角點(diǎn)響應(yīng)函數(shù)R進(jìn)行閾值處理,Shi-Tomasi原理幾乎和Harris一樣的,只不過(guò)最后計(jì)算角點(diǎn)響應(yīng)的公式發(fā)生了變化。本文將和大家詳細(xì)說(shuō)說(shuō)Shi-Tomasi角點(diǎn)檢測(cè)算法的原理與實(shí)現(xiàn),需要的可以參考一下2022-09-09FFmpeg實(shí)戰(zhàn)之分離出PCM數(shù)據(jù)
PCM(Pulse?Code?Modulation,脈沖編碼調(diào)制)音頻數(shù)據(jù)是未經(jīng)壓縮的音頻采樣數(shù)據(jù)裸流,它是由模擬信號(hào)經(jīng)過(guò)采樣、量化、編碼轉(zhuǎn)換成的標(biāo)準(zhǔn)數(shù)字音頻數(shù)據(jù)。本文將通過(guò)FFmpeg實(shí)現(xiàn)分離PCM數(shù)據(jù),感興趣的可以了解一下2023-02-02C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)系列篇二叉樹(shù)的遍歷
本章將會(huì)詳細(xì)講解二叉樹(shù)遍歷的四種方式,分別為前序遍歷、中序遍歷、后續(xù)遍歷和層序遍歷。在學(xué)習(xí)遍歷之前,會(huì)先帶大家回顧一下二叉樹(shù)的基本概念2022-02-02Windows下VScode實(shí)現(xiàn)簡(jiǎn)單回聲服務(wù)的方法
回聲服務(wù)端可以將客戶(hù)端傳來(lái)的信息,再原封不動(dòng)地發(fā)送給客戶(hù)端,因而得名 epoch 服務(wù)。接下來(lái)通過(guò)本文給大家介紹Windows下VScode實(shí)現(xiàn)簡(jiǎn)單回聲服務(wù)的方法,感興趣的朋友一起看看吧2021-08-08C語(yǔ)言詳解熱門(mén)考點(diǎn)結(jié)構(gòu)體內(nèi)存對(duì)齊
C?數(shù)組允許定義可存儲(chǔ)相同類(lèi)型數(shù)據(jù)項(xiàng)的變量,結(jié)構(gòu)是?C?編程中另一種用戶(hù)自定義的可用的數(shù)據(jù)類(lèi)型,它允許你存儲(chǔ)不同類(lèi)型的數(shù)據(jù)項(xiàng),本篇讓我們來(lái)了解C?的結(jié)構(gòu)體內(nèi)存對(duì)齊2022-04-04VSCode與Keil聯(lián)合開(kāi)發(fā)STM32的流程
這篇文章主要介紹了VSCode與Keil聯(lián)合開(kāi)發(fā)STM32的流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02