C++適用入門同學(xué)的模板講解
1. 泛型編程
C++下一個針對C語言不足而設(shè)計的語法就叫作模板,模板的這種思想叫作泛型編程。
泛型編程的意思是:
我們以前C語言寫代碼是不是都是針對一種類型,比如我們寫個Swap,寫個排序,我們換一個類型,程序都得掛。
而泛型編程不再是針對某種類型,能夠適應(yīng)廣泛的類型。泛型編程用的一個東西就叫作模板。
博主先帶大家來舉一個簡單的范例:
如何實現(xiàn)一個通用的交換函數(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; } ......
即使C++有函數(shù)重載,寫一個Swap
是不是也要寫很多的代碼呀!但是這些代碼都是相似的,int
要寫一個,double
要寫一個,char
要寫一個,日期類
要寫一個,自定義類型
都得寫一個,全部都得寫一個。有意思沒?
ok,C++就給出了一個模板,讓編譯器根據(jù)不同的實參類型自動推導(dǎo)生成相應(yīng)的一系列類似的代碼。
模板是將一個事物的結(jié)構(gòu)規(guī)律予以固定化、標(biāo)準(zhǔn)化的成果,它體現(xiàn)的是結(jié)構(gòu)形式的標(biāo)準(zhǔn)化。
模板分為函數(shù)模板和類模板:
2. 函數(shù)模板
2.1 函數(shù)模板概念
函數(shù)模板代表了一個函數(shù)家族,該函數(shù)模板與類型無關(guān),在使用時被參數(shù)化,根據(jù)實參類型產(chǎn)生函數(shù)的特定類型版本。
2.2 函數(shù)模板格式
注意:typename是用來定義模板參數(shù)關(guān)鍵字,也可以使用class(切記:不能使用struct代替class)
template<typename T1, typename T2,......,typename Tn>
返回值類型 函數(shù)名(參數(shù)列表){}
template<typename T> void Swap( T& left, T& right) { T temp = left; left = right; right = temp; }
然后我們可以這樣調(diào)用:
int main() { int a = 0, b = 1; double c = 2.2, d = 3.3; Swap(a, b); Swap(c, d); printf("a=%d b=%d\n", a, b); printf("c=%lf d=%lf\n", c, d); return 0; }
愛思考的同學(xué)可能就會問這個函數(shù)模板到底是怎么做到的呢?
大家猜一猜這兩個Swap
調(diào)用的是不是同一個函數(shù)?
ok,博主告訴大家,它們調(diào)用的肯定不是同一個函數(shù)。雖然它們看起是來調(diào)用了同一個函數(shù)(這是編譯器優(yōu)化的結(jié)果),但是它們調(diào)用的是模板自動生成的相應(yīng)函數(shù)。
2.3 函數(shù)模板的原理
函數(shù)模板是一個藍(lán)圖,它本身并不是函數(shù),是編譯器用使用方式產(chǎn)生特定具體類型函數(shù)的模具。
所以其實模板就是將本來應(yīng)該我們做的重復(fù)的事情交給了編譯器。
模板的實例化:
在編譯器編譯階段,對于模板函數(shù)的使用,編譯器需要根據(jù)傳入的實參類型來推演生成對應(yīng)類型的函數(shù)以供調(diào)用。比如:當(dāng)用double類型使用函數(shù)模板時,編譯器通過對實參類型的推演,將T確定為double類型,然后產(chǎn)生一份專門處理double類型的代碼,對于字符類型也是如此。
其實swap
這個常用的小函數(shù),C++的庫里面有提供。大家想一想為什么C語言不提供,因為C語言沒辦法提供,這是C語言語法的缺陷。
2.4 函數(shù)模板的實例化
用不同類型的參數(shù)使用函數(shù)模板時,稱為函數(shù)模板的實例化。模板參數(shù)實例化分為:隱式實例化和顯式實例化。
2.4.1顯式實例化
講到這,博主問大家一個問題:函數(shù)模板一定就是推演的嗎?不一定。
假設(shè),我們寫了一個沒有參數(shù)或者沒有用模板參數(shù)的func
函數(shù),我們該怎么調(diào)用?
template<class T> T* func(int n) { return new T[n]; } int main() { int* p1 = func(10); double* p2 = func(10); return 0; }
我們這樣調(diào)用,編譯器能不能知道T
的類型?是不是肯定不行呀:
這個時候函數(shù)模板就需要顯示實例化:在函數(shù)名后的<>
中指定模板參數(shù)的實際類型。
模板參數(shù)很多用法和函數(shù)參數(shù)是很像的,也可以有缺省參數(shù)。只是說模板參數(shù)傳遞的是類型,函數(shù)參數(shù)傳遞的是對象值。
2.4.2 隱式實例化
讓編譯器根據(jù)實參推演模板參數(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); }
那如果我Add(a1, d2);
這樣調(diào)用會怎么樣?大家看:
編譯器這個時候為難了,你到底想讓T
是int
呢?還是T
是double
呢?
那怎么辦,這個時候是不是就無法調(diào)用了。那我就想這樣調(diào)用怎么辦呢?
ok,也有。
此時有兩種處理方式:
1. 用戶自己來強(qiáng)制轉(zhuǎn)化
2. 使用顯式實例化
2.5 模板參數(shù)的匹配原則
這兩個函數(shù)能不能同時存在?
一個非模板函數(shù)可以和一個同名的函數(shù)模板同時存在,而且該函數(shù)模板還可以被實例化為這個非模板函數(shù)。
// 專門處理int的加法函數(shù) int Add(int left, int right) { return left + right; } // 通用加法函數(shù) template<class T> T Add(T left, T right) { return left + right; }
對于非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動時會優(yōu)先調(diào)用非模板函數(shù)而不會從該模板產(chǎn)生出一個實例。如果模板可以產(chǎn)生一個具有更好匹配的函數(shù), 那么將選擇模板。
也可以指定調(diào)用模板:
模板函數(shù)不允許自動類型轉(zhuǎn)換,但普通函數(shù)可以進(jìn)行自動類型轉(zhuǎn)換
所以當(dāng)一個匹配既沒有非模板函數(shù),也沒有函數(shù)模板可以匹配到的時候,會嘗試通過自動類型轉(zhuǎn)換調(diào)用到非模板函數(shù)(前提是可以轉(zhuǎn)換為非模板函數(shù)的參數(shù)類型)
3. 類模板
我們以前C語言要解決類型更換的問題用的是typedef
,但是typedef
不能解決全部的問題。
假設(shè)我們用以前的方法寫一個棧:
typedef int STDataType; class Stack { public: Stack(int capacity = 0) { _a = new STDataType[capacity]; _capacity = capacity; _top = 0; } ~Stack() { cout << "~Stack()" << endl; delete[] _a; _capacity = 0; _top = 0; } void Push(STDataType x) {} private: STDataType* _a; int _top; int _capacity; }; int main() { Stack st1; // int Stack st2; // double return 0; }
C語言typedef
的真正缺陷在于:如果我在一個程序里面定義了一個棧,那么不可能同時存在兩個存儲類型不同的棧。
C++由此提供了一個類模板。
3.1 類模板的定義格式
template<class T1, class T2, ..., class Tn> class 類模板名 { // 類內(nèi)成員定義 };
此時我們寫一個棧的模板:
template<class T> class Stack { public: Stack(int capacity = 0) { _a = new T[capacity]; _capacity = capacity; _top = 0; } ~Stack() { cout << "~Stack()" << endl; delete[] _a; _capacity = 0; _top = 0; } void Push(const T& x) {} private: T* _a; int _top; int _capacity; };
類模板和函數(shù)模板不一樣:函數(shù)模板可以自動推導(dǎo)。類模板不能推導(dǎo),因為如果定義對象時構(gòu)造函數(shù)沒有傳參或者構(gòu)造函數(shù)沒有參數(shù),那就麻煩了,所以類模板得在調(diào)用的時候指定類型。
注意:Push
的傳參不能傳值,因為存儲的類型不一樣,如果是自定義類型,還可能會考慮深拷貝的問題,代價就太大了,所以我們最好傳引用,引用實體不改變就加const
。
3.2 類模板的實例化
類模板實例化與函數(shù)模板實例化不同,類模板實例化需要在類模板名字后跟<>,然后將實例化的類型放在<>中即可,類模板名字不是真正的類,而實例化的結(jié)果才是真正的類。
int main() { //Stack是類名,Stack<int>才是類型 Stack<int> st1; // int st1.Push(1); Stack<double> st2; // double st2.Push(2.2); return 0; }
st1
和st2
是兩個類,類型分別是Stack<int>
、 Stack<double>
。
4 模板分離編譯
4.1 模板的分離編譯
//聲明 template<class T> void Swap(T& left, T& right); template<class T> class Vector//Vector不是具體的類,是編譯器根據(jù)被實例化的類型生成具體類的模具,其實就是順序表 { public: Vector(size_t capacity = 10); private: T* _pData; size_t _size; size_t _capacity; }; //定義 template<class T > void Swap(T& left, T& right) { T temp = left; left = right; right = temp; } template<class T> Vector<T>::Vector(size_t capacity) : _pData(new T[capacity]) , _size(0) , _capacity(capacity) {}
模板分離編譯和普通的函數(shù)分離編譯還是不同的,最大的不同是:雖然聲明的時候給了模板參數(shù),但是定義的時候還要聲明一次,不然編譯器不知道T是哪來的。
但是這里還有一個問題:模板不支持聲明和定義分別放在xxx.h和xxx.cpp中!會出現(xiàn)鏈接錯誤!
來,我們來玩一下:
為什么模板分離編譯會鏈接不上?
ok,其實這里和模板的實例化有關(guān)系。
大家想一想編譯器把template.i
處理成template.s
,能干什么事情?
編譯器是不是應(yīng)該把類和函數(shù)編譯了放在符號表里面去?但是大家想一想,編譯器能不能處理?
ok,編譯器根本無從下手,其實templa.i編譯之后是空的,template.o和符號表也是空的,全都是空的。因為編譯器沒辦法知道模板參數(shù)T是啥!
大家再看調(diào)用的地方能不能編譯過,調(diào)用的地方?jīng)]啥毛病,能過。但是調(diào)用Swap和實例化對象調(diào)用構(gòu)造函數(shù)時會去call
相應(yīng)函數(shù)的地址(正常是鏈接時拿修飾過的函數(shù)名去符號表里找這個地址)。但是符號表是空的呀,所以模板分離編譯,鏈接時找不到這些函數(shù)模板調(diào)用的地址。
4.2 解決方法
第一種方法:(推薦使用)
將聲明和定義放到一個文件 “xxx.hpp” (.h+.cpp合在一起這樣命名更規(guī)范)里面或者xxx.h其實也是可以的。
這樣頭文件展開的時候,就是聲明和定義一起在test.cpp
中展開了,這樣test.cpp
在編譯的時候直接就把模板實例化了,調(diào)用的時候也是直接call
函數(shù)的地址了,不用鏈接的時候去找了。
第二種方法:模板定義的位置顯式實例化。這種方法不實用,不推薦使用。
到此這篇關(guān)于C++適用入門同學(xué)的模板講解的文章就介紹到這了,更多相關(guān)C++模板內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于C++中數(shù)據(jù)16進(jìn)制輸出的方法
本文主要介紹了關(guān)于C++中數(shù)據(jù)16進(jìn)制輸出的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管
這篇文章主要介紹了使用c語言輕松實現(xiàn)動態(tài)內(nèi)存管,本文章內(nèi)容詳細(xì),具有很好的參考價值,希望對大家有所幫助,需要的朋友可以參考下2023-01-01openCV中meanshift算法查找目標(biāo)的實現(xiàn)
本文主要介紹了openCV中meanshift算法查找目標(biāo)的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11