一篇文章帶你了解C++模板編程詳解
模板初階
泛型編程
在計(jì)算機(jī)程序設(shè)計(jì)領(lǐng)域,為了避免因數(shù)據(jù)類型的不同,而被迫重復(fù)編寫大量相同業(yè)務(wù)邏輯的代碼,人們發(fā)展的泛型及泛型編程技術(shù)。什么是泛型呢?實(shí)質(zhì)上就是不使用具體數(shù)據(jù)類型(例如 int、double、float 等),而是使用一種通用類型來進(jìn)行程序設(shè)計(jì)的方法,該方法可以大規(guī)模的減少程序代碼的編寫量,讓程序員可以集中精力用于業(yè)務(wù)邏輯的實(shí)現(xiàn)。泛型也是一種數(shù)據(jù)類型,只不過它是一種用來代替所有類型的“通用類型”
我們通常如何實(shí)現(xiàn)一個(gè)通用的交換函數(shù)呢?
void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } void Swap(char& left, char& right) { char temp = left; left = right; right = temp; } ......
Swap函數(shù)能實(shí)現(xiàn)各種類型的變量交換,但是只要類型不同就需要重新寫一個(gè)
使用函數(shù)重載雖然可以實(shí)現(xiàn),但是有一下幾個(gè)不好的地方:
- 重載的函數(shù)僅僅只是類型不同,代碼的復(fù)用率比較低,只要有新類型出現(xiàn)時(shí),就需要增加對(duì)應(yīng)的函數(shù)
- 代碼的可維護(hù)性比較低,一個(gè)出錯(cuò)可能所有的重載均出錯(cuò),那能否告訴編譯器一個(gè)模版,讓編譯器根據(jù)不同的類型利用該模版來生成代碼呢?
可以的,C++語法中有了模板:
函數(shù)模板
函數(shù)模板概念
所謂函數(shù)模板,實(shí)際上是建立一個(gè)通用函數(shù),它所用到的數(shù)據(jù)的類型(包括返回值類型、形參類型、局部變量類型)可以不具體指定,而是用一個(gè)虛擬的類型來代替(實(shí)際上是用一個(gè)標(biāo)識(shí)符來占位),等發(fā)生函數(shù)調(diào)用時(shí)再根據(jù)傳入的實(shí)參來逆推出真正的類型。 這個(gè)通用函數(shù)就稱為 函數(shù)模板(Function Template) 。函數(shù)模板代表了一個(gè)函數(shù)家族,該函數(shù)模板與類型無關(guān),在使用時(shí)被參數(shù)化,根據(jù)實(shí)參類型產(chǎn)生函數(shù)的特定類型版本。
函數(shù)模板格式
template<typename T1, typename T2,…,typename Tn>
返回值類型 函數(shù)名(參數(shù)列表){}
template<typename T> //或者 template<class T> void Swap(T& x1, T& x2) { T temp = left; left = right; right = temp; }
T1,T2等等是什么類型現(xiàn)在也不確定,一會(huì)用的時(shí)候才能確定
注意:
typename是用來定義模板參數(shù)關(guān)鍵字,也可以使用class
函數(shù)模板的原理
函數(shù)模板本身并不是函數(shù),是編譯器根據(jù)調(diào)用的參數(shù)類型產(chǎn)生特定具體類型函數(shù)的模具,所以其實(shí)模板就是將本來應(yīng)該我們做的重復(fù)的事情交給了編譯器,我們看下面的例子:
template<class T> void Swap(T& x, T& y) { T temp = x; x = y; y = temp; } int main() { int a = 1; int b = 2; Swap(a, b); char A = 'a'; char B = 'b'; Swap(A,B); return 0; }
在編譯器編譯階段,對(duì)于模板函數(shù)的使用,編譯器需要根據(jù)傳入的實(shí)參類型來推演生成對(duì)應(yīng)類型的函數(shù)以供調(diào)用。比如:當(dāng)用int類型使用函數(shù)模板時(shí),編譯器通過對(duì)實(shí)參類型的推演,將T確定為int類型,然
后產(chǎn)生一份專門處理int類型的代碼,對(duì)于字符類型也是如此。
然而當(dāng)我們?cè)趯懥撕瘮?shù)時(shí),不會(huì)進(jìn)入模板函數(shù)里,沒有寫具體的函數(shù)時(shí),就會(huì)進(jìn)入模板函數(shù)里,我們看下面的例子:
void Swap(int& x, int& y) { int temp = x; x = y; y = temp; } template<class T> void Swap(T& x, T& y) { T temp = x; x = y; y = temp; } int main() { int a = 1; int b = 2; Swap(a, b); char A = 'a'; char B = 'b'; Swap(A,B); return 0; }
我們進(jìn)行調(diào)式:
我們可以看到int類型的交換函數(shù)我們寫了,調(diào)用時(shí)調(diào)用的是我們寫的,而char類型的我們沒寫,就用了模板。
那么這里調(diào)用的是模板函數(shù)嗎?
不是的,實(shí)際上這里會(huì)有兩個(gè)過程
1、模板推演,推演T的具體類型是什么
2、推演出T的具體類型后實(shí)例化生成具體的函數(shù)
上面的代碼實(shí)例化生成了下面的函數(shù):
void Swap(char& x, char& y) { char temp = x; x = y; y = temp; }
真正調(diào)用的還是兩個(gè)函數(shù),但是其中的一個(gè)函數(shù)不是我們自己寫的,而是我們給了編譯器一個(gè)模板,然后編譯器進(jìn)行推演在編譯之前實(shí)例化生成三個(gè)對(duì)應(yīng)的函數(shù),模板是給編譯器用的,編譯器充當(dāng)了寫函數(shù)的工具:
可以看到這里是調(diào)用了Swap<char>函數(shù)
在C++當(dāng)中,其實(shí)內(nèi)置類型也可以像自定義類型那樣這樣初始化:
int a(1); int(2);//匿名
void Swap(T& x1, T& x2) { T temp(x1); x1 = x2; x2 = x1; }
所以模板還可以這樣寫,可以使內(nèi)置類型和自定義類型兼容:
void Swap(T& x1, T& x2) { T temp(x1); x1 = x2; x2 = x1; }
我們來具體看一看函數(shù)模板的實(shí)例化:
函數(shù)模板的實(shí)例化
用不同類型的參數(shù)使用函數(shù)模板時(shí),稱為函數(shù)模板的實(shí)例化。模板參數(shù)實(shí)例化分為:隱式實(shí)例化和顯式實(shí)例化。
隱式實(shí)例化:讓編譯器根據(jù)實(shí)參推演模板參數(shù)的實(shí)際類型
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2); Add(d1, d2); // 此時(shí)有兩種處理方式:1. 用戶自己來強(qiáng)制轉(zhuǎn)化 2. 使用顯式實(shí)例化 Add(a1, d2); return 0; }
該語句是不能夠通過編譯的,因?yàn)樵诰幾g期間,當(dāng)編譯器看到該實(shí)例化時(shí),用a1去推T是int,而用d2去推是double,但是模板參數(shù)列表里只有一個(gè)T,編譯器不能明確該T是int還是double,T是不明確的,所以編譯器會(huì)報(bào)錯(cuò)
那么怎么處理呢?
解決方式:
1、調(diào)用者自己強(qiáng)制轉(zhuǎn)換
//實(shí)參去推演形參的類型 Add(a1, (int)d2); Add((double)a1,d2);
這里可以將d2先強(qiáng)制類型轉(zhuǎn)換,然后再進(jìn)行推演;或者將a1先強(qiáng)制類型轉(zhuǎn)換再進(jìn)行推演
2、使用顯式實(shí)例化
//實(shí)參不需要去推演形參的類型,顯式實(shí)例化指定T的類型 Add<int>(a1, d2); Add<double>(a1,d2);
這種方式是顯式實(shí)例化指定T的類型
顯式實(shí)例化在哪種場(chǎng)景可用呢?看下面的這種場(chǎng)景:
class A { A(int a=0):_a(a) {} private: int _a; }; template<class T> T func(int x) { T a(x); return a; } int main() { func<A>(1); func<int>(2); return 0; }
有些函數(shù)模板里面參數(shù)中沒用模板參數(shù),函數(shù)體內(nèi)才有用到模板參數(shù),此時(shí)就無法通過參數(shù)去推演T的類型,這時(shí)只能顯示實(shí)例化
上面我們提了一點(diǎn)模板參數(shù)的匹配原則,下面我們具體看看模板參數(shù)的匹配原則:
模板參數(shù)的匹配原則
一個(gè)非模板函數(shù)可以和一個(gè)同名的函數(shù)模板同時(shí)存在,此時(shí)如果調(diào)用地方參數(shù)與非模板函數(shù)完全匹配,則會(huì)調(diào)用非模板函數(shù)
int Add(int left, int right) { return left + right; } // 通用加法函數(shù) template<class T> T Add(T left, T right) { return left + right; } int main() { Add(1,2);//調(diào)用自己的函數(shù) return 0; }
Add(1,2)參數(shù)是int類型,而我們有現(xiàn)成的int參數(shù)的Add函數(shù),所以有現(xiàn)成的就用現(xiàn)成的,編譯器也會(huì)偷懶
那么如果我們想讓這里調(diào)用必須用模板呢?顯式實(shí)例化:
Add<int>(1,2);
這樣編譯器就強(qiáng)制會(huì)用模板去實(shí)例化函數(shù)
一個(gè)非模板函數(shù)可以和一個(gè)同名的函數(shù)模板同時(shí)存在,此時(shí)如果調(diào)用地方參數(shù)與非模板函數(shù)不完全匹配,則會(huì)優(yōu)先使用模板實(shí)例化函數(shù)
int Add(int left, int right) { return left + right; } // 通用加法函數(shù) template<class T> T Add(T left, T right) { return left + right; } int main() { Add(1.1,2.2);//使用模板實(shí)例化函數(shù) return 0; }
模板匹配原則總結(jié):
有現(xiàn)成完全匹配的,那就直接調(diào)用,沒有現(xiàn)成調(diào)用的,實(shí)例化模板生成,如果有需要轉(zhuǎn)換類型才能匹配的函數(shù)(也就是不完全匹配),那么它會(huì)優(yōu)先選擇去實(shí)例化模板生成。
優(yōu)先級(jí):
完全匹配>模板>轉(zhuǎn)換類型匹配
類模板
類模板的定義格式
template<class T1, class T2, ..., class Tn> class 類模板名 { //類內(nèi)成員定義 };
我們來看一個(gè)類模板的使用場(chǎng)景:
typedef int STDateType; class Stack { private: STDateType* _a; int _top; int _capacity; }; int main() { Stack st1; Stack st2; return 0; }
這是我們定義的棧數(shù)據(jù)結(jié)構(gòu),我們創(chuàng)建了兩個(gè)棧對(duì)象,但是現(xiàn)在st1和st2的存儲(chǔ)數(shù)據(jù)的類型都是int,要是想轉(zhuǎn)換數(shù)據(jù)類型呢?
typedef double STDateType;
我們這樣就轉(zhuǎn)換了,但是我們要是想st1為int,st2為double呢:
Stack st1;//int Stack st2;//double
此時(shí)需要寫多個(gè)類,名字還得不一樣,如下:
typedef int STDateType1; typedef double STDateType2; class IntStack { private: STDateType1* _a; int _top; int _capacity; }; class DoubleStack { private: STDateType2* _a; int _top; int _capacity; };
這樣太麻煩了,那么什么辦法可以解決呢?類模板可以解決:
//類模板 template<class T> class Stack { private: T* _a; int _top; int _capaticy; }; int main() { //類模板的使用都是顯式實(shí)例化 Stack<double> st1; Stack<int> st2; return 0; }
注意:Stack不是具體的類,是編譯器根據(jù)被實(shí)例化的類型生成具體類的模具
類模板的實(shí)例化
//類模板 template<class T> class Stack { public: Stack(int capacity = 4) :_a(new T(capacity)) ,_top(0) ,_capacity(capacity) {} ~Stack() { delete[] _a; _a = nullptr; _top = _capacity = 0; } void Push(const T& x) { //... } private: T* _a; int _top; int _capaticy; }; int main() { //類模板的使用都是顯式實(shí)例化 Stack<double> st1; Stack<int> st2; return 0; }
注意:類模板的使用都是顯式實(shí)例化
假設(shè)我們想類里面聲明和類外面定義成員函數(shù)呢?
//類模板 template<class T> class Stack { public: Stack(int capacity = 4) :_a(new T(capacity)) ,_top(0) ,_capacity(capacity) {} ~Stack() { delete[] _a; _a = nullptr; _top = _capacity = 0; } //假設(shè)我們想類里面聲明和定義分離呢? void Push(const T& x); private: T* _a; int _top; int _capaticy; }; //在類外面定義 template<class T> void Stack<T>::Push(const T& x); { //... } int main() { //類模板的使用都是顯式實(shí)例化 Stack<TreeNode*> st1; Stack<int> st2; return 0; }
//在類外面定義 template<class T> void Stack<T>::Push(const T& x); { //... }
在類外面定義我們必須要加模板的關(guān)鍵字,以及需要在實(shí)現(xiàn)的函數(shù)前面表明域Stack<T>。普通類,類名就是類型,對(duì)于類模板,類名不是類型,類型是Stack<T>,需要寫指定
注意:
模板不支持把聲明寫到.h,定義寫到.cpp,這種聲明和定義分開實(shí)現(xiàn)的方式,會(huì)出現(xiàn)鏈接錯(cuò)誤
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C++中CopyFile和MoveFile函數(shù)使用區(qū)別的示例分析
這篇文章主要介紹了C++中CopyFile和MoveFile函數(shù)使用區(qū)別的示例分析,CopyFile表示將文件A拷貝到B,如果B已經(jīng)存在則覆蓋,MoveFile表示將文件A移動(dòng)到。對(duì)此感興趣的可以來了解一下2020-07-07C/C++ Qt TreeWidget 嵌套節(jié)點(diǎn)操作使用
本文主要介紹了TreeWidget的如何使用,實(shí)現(xiàn)對(duì)樹形框多節(jié)點(diǎn)的各種操作,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11詳解C++編程中的sizeof運(yùn)算符與typeid運(yùn)算符
這篇文章主要介紹了C++編程中的sizeof運(yùn)算符與typeid運(yùn)算符,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-01-01c++實(shí)現(xiàn)逐行讀取配置文件寫入內(nèi)存的示例
這篇文章主要介紹了c++實(shí)現(xiàn)逐行讀取配置文件寫入內(nèi)存的示例,需要的朋友可以參考下2014-05-05C語言數(shù)組學(xué)習(xí)之特殊矩陣的壓縮存儲(chǔ)
矩陣在計(jì)算機(jī)圖形學(xué)、工程計(jì)算中都占有舉足輕重的地位,本文將討論如何將矩陣更有效地存儲(chǔ)在內(nèi)存中,并且能夠方便地提取矩陣中的元素。感興趣的同學(xué)可以了解一下2021-12-12用C++類實(shí)現(xiàn)單向鏈表的增刪查和反轉(zhuǎn)操作方法
下面小編就為大家?guī)硪黄肅++類實(shí)現(xiàn)單向鏈表的增刪查和反轉(zhuǎn)操作方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04詳解MFC/C++調(diào)用易語言的整數(shù)型和文本型與VS2010互動(dòng)
在本篇文章里我們給大家分享了MFC/C++調(diào)用易語言的整數(shù)型和文本型與VS2010互動(dòng)相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們可以參考下。2018-11-11