C++適用入門同學(xué)的模板講解
1. 泛型編程
C++下一個(gè)針對(duì)C語(yǔ)言不足而設(shè)計(jì)的語(yǔ)法就叫作模板,模板的這種思想叫作泛型編程。
泛型編程的意思是:
我們以前C語(yǔ)言寫代碼是不是都是針對(duì)一種類型,比如我們寫個(gè)Swap,寫個(gè)排序,我們換一個(gè)類型,程序都得掛。
而泛型編程不再是針對(duì)某種類型,能夠適應(yīng)廣泛的類型。泛型編程用的一個(gè)東西就叫作模板。
博主先帶大家來(lái)舉一個(gè)簡(jiǎn)單的范例:
如何實(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;
}
......
即使C++有函數(shù)重載,寫一個(gè)Swap是不是也要寫很多的代碼呀!但是這些代碼都是相似的,int要寫一個(gè),double要寫一個(gè),char要寫一個(gè),日期類要寫一個(gè),自定義類型都得寫一個(gè),全部都得寫一個(gè)。有意思沒(méi)?
ok,C++就給出了一個(gè)模板,讓編譯器根據(jù)不同的實(shí)參類型自動(dòng)推導(dǎo)生成相應(yīng)的一系列類似的代碼。
模板是將一個(gè)事物的結(jié)構(gòu)規(guī)律予以固定化、標(biāo)準(zhǔn)化的成果,它體現(xiàn)的是結(jié)構(gòu)形式的標(biāo)準(zhǔn)化。
模板分為函數(shù)模板和類模板:

2. 函數(shù)模板
2.1 函數(shù)模板概念
函數(shù)模板代表了一個(gè)函數(shù)家族,該函數(shù)模板與類型無(wú)關(guān),在使用時(shí)被參數(shù)化,根據(jù)實(shí)參類型產(chǎn)生函數(shù)的特定類型版本。
2.2 函數(shù)模板格式
注意:typename是用來(lái)定義模板參數(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;
}

愛(ài)思考的同學(xué)可能就會(huì)問(wèn)這個(gè)函數(shù)模板到底是怎么做到的呢?
大家猜一猜這兩個(gè)Swap調(diào)用的是不是同一個(gè)函數(shù)?

ok,博主告訴大家,它們調(diào)用的肯定不是同一個(gè)函數(shù)。雖然它們看起是來(lái)調(diào)用了同一個(gè)函數(shù)(這是編譯器優(yōu)化的結(jié)果),但是它們調(diào)用的是模板自動(dòng)生成的相應(yīng)函數(shù)。

2.3 函數(shù)模板的原理
函數(shù)模板是一個(gè)藍(lán)圖,它本身并不是函數(shù),是編譯器用使用方式產(chǎn)生特定具體類型函數(shù)的模具。
所以其實(shí)模板就是將本來(lái)應(yīng)該我們做的重復(fù)的事情交給了編譯器。
模板的實(shí)例化:

在編譯器編譯階段,對(duì)于模板函數(shù)的使用,編譯器需要根據(jù)傳入的實(shí)參類型來(lái)推演生成對(duì)應(yīng)類型的函數(shù)以供調(diào)用。比如:當(dāng)用double類型使用函數(shù)模板時(shí),編譯器通過(guò)對(duì)實(shí)參類型的推演,將T確定為double類型,然后產(chǎn)生一份專門處理double類型的代碼,對(duì)于字符類型也是如此。
其實(shí)swap這個(gè)常用的小函數(shù),C++的庫(kù)里面有提供。大家想一想為什么C語(yǔ)言不提供,因?yàn)镃語(yǔ)言沒(méi)辦法提供,這是C語(yǔ)言語(yǔ)法的缺陷。

2.4 函數(shù)模板的實(shí)例化
用不同類型的參數(shù)使用函數(shù)模板時(shí),稱為函數(shù)模板的實(shí)例化。模板參數(shù)實(shí)例化分為:隱式實(shí)例化和顯式實(shí)例化。
2.4.1顯式實(shí)例化
講到這,博主問(wèn)大家一個(gè)問(wèn)題:函數(shù)模板一定就是推演的嗎?不一定。
假設(shè),我們寫了一個(gè)沒(méi)有參數(shù)或者沒(méi)有用模板參數(shù)的func函數(shù),我們?cè)撛趺凑{(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的類型?是不是肯定不行呀:

這個(gè)時(shí)候函數(shù)模板就需要顯示實(shí)例化:在函數(shù)名后的<>中指定模板參數(shù)的實(shí)際類型。

模板參數(shù)很多用法和函數(shù)參數(shù)是很像的,也可以有缺省參數(shù)。只是說(shuō)模板參數(shù)傳遞的是類型,函數(shù)參數(shù)傳遞的是對(duì)象值。

2.4.2 隱式實(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);
}
那如果我Add(a1, d2);這樣調(diào)用會(huì)怎么樣?大家看:

編譯器這個(gè)時(shí)候?yàn)殡y了,你到底想讓T是int呢?還是T是double呢?
那怎么辦,這個(gè)時(shí)候是不是就無(wú)法調(diào)用了。那我就想這樣調(diào)用怎么辦呢?
ok,也有。
此時(shí)有兩種處理方式:
1. 用戶自己來(lái)強(qiáng)制轉(zhuǎn)化
2. 使用顯式實(shí)例化

2.5 模板參數(shù)的匹配原則
這兩個(gè)函數(shù)能不能同時(shí)存在?
一個(gè)非模板函數(shù)可以和一個(gè)同名的函數(shù)模板同時(shí)存在,而且該函數(shù)模板還可以被實(shí)例化為這個(gè)非模板函數(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;
}
對(duì)于非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動(dòng)時(shí)會(huì)優(yōu)先調(diào)用非模板函數(shù)而不會(huì)從該模板產(chǎn)生出一個(gè)實(shí)例。如果模板可以產(chǎn)生一個(gè)具有更好匹配的函數(shù), 那么將選擇模板。

也可以指定調(diào)用模板:

模板函數(shù)不允許自動(dòng)類型轉(zhuǎn)換,但普通函數(shù)可以進(jìn)行自動(dòng)類型轉(zhuǎn)換
所以當(dāng)一個(gè)匹配既沒(méi)有非模板函數(shù),也沒(méi)有函數(shù)模板可以匹配到的時(shí)候,會(huì)嘗試通過(guò)自動(dòng)類型轉(zhuǎn)換調(diào)用到非模板函數(shù)(前提是可以轉(zhuǎn)換為非模板函數(shù)的參數(shù)類型)
3. 類模板
我們以前C語(yǔ)言要解決類型更換的問(wèn)題用的是typedef,但是typedef不能解決全部的問(wèn)題。
假設(shè)我們用以前的方法寫一個(gè)棧:
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語(yǔ)言typedef的真正缺陷在于:如果我在一個(gè)程序里面定義了一個(gè)棧,那么不可能同時(shí)存在兩個(gè)存儲(chǔ)類型不同的棧。
C++由此提供了一個(gè)類模板。
3.1 類模板的定義格式
template<class T1, class T2, ..., class Tn>
class 類模板名
{
// 類內(nèi)成員定義
};
此時(shí)我們寫一個(gè)棧的模板:
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òng)推導(dǎo)。類模板不能推導(dǎo),因?yàn)槿绻x對(duì)象時(shí)構(gòu)造函數(shù)沒(méi)有傳參或者構(gòu)造函數(shù)沒(méi)有參數(shù),那就麻煩了,所以類模板得在調(diào)用的時(shí)候指定類型。
注意:Push的傳參不能傳值,因?yàn)榇鎯?chǔ)的類型不一樣,如果是自定義類型,還可能會(huì)考慮深拷貝的問(wèn)題,代價(jià)就太大了,所以我們最好傳引用,引用實(shí)體不改變就加const。
3.2 類模板的實(shí)例化
類模板實(shí)例化與函數(shù)模板實(shí)例化不同,類模板實(shí)例化需要在類模板名字后跟<>,然后將實(shí)例化的類型放在<>中即可,類模板名字不是真正的類,而實(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是兩個(gè)類,類型分別是Stack<int> 、 Stack<double>。
4 模板分離編譯
4.1 模板的分離編譯
//聲明
template<class T>
void Swap(T& left, T& right);
template<class T>
class Vector//Vector不是具體的類,是編譯器根據(jù)被實(shí)例化的類型生成具體類的模具,其實(shí)就是順序表
{
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í)候給了模板參數(shù),但是定義的時(shí)候還要聲明一次,不然編譯器不知道T是哪來(lái)的。

但是這里還有一個(gè)問(wèn)題:模板不支持聲明和定義分別放在xxx.h和xxx.cpp中!會(huì)出現(xiàn)鏈接錯(cuò)誤!
來(lái),我們來(lái)玩一下:

為什么模板分離編譯會(huì)鏈接不上?
ok,其實(shí)這里和模板的實(shí)例化有關(guān)系。

大家想一想編譯器把template.i處理成template.s,能干什么事情?
編譯器是不是應(yīng)該把類和函數(shù)編譯了放在符號(hào)表里面去?但是大家想一想,編譯器能不能處理?
ok,編譯器根本無(wú)從下手,其實(shí)templa.i編譯之后是空的,template.o和符號(hào)表也是空的,全都是空的。因?yàn)榫幾g器沒(méi)辦法知道模板參數(shù)T是啥!
大家再看調(diào)用的地方能不能編譯過(guò),調(diào)用的地方?jīng)]啥毛病,能過(guò)。但是調(diào)用Swap和實(shí)例化對(duì)象調(diào)用構(gòu)造函數(shù)時(shí)會(huì)去call相應(yīng)函數(shù)的地址(正常是鏈接時(shí)拿修飾過(guò)的函數(shù)名去符號(hào)表里找這個(gè)地址)。但是符號(hào)表是空的呀,所以模板分離編譯,鏈接時(shí)找不到這些函數(shù)模板調(diào)用的地址。
4.2 解決方法
第一種方法:(推薦使用)
將聲明和定義放到一個(gè)文件 “xxx.hpp” (.h+.cpp合在一起這樣命名更規(guī)范)里面或者xxx.h其實(shí)也是可以的。

這樣頭文件展開(kāi)的時(shí)候,就是聲明和定義一起在test.cpp中展開(kāi)了,這樣test.cpp在編譯的時(shí)候直接就把模板實(shí)例化了,調(diào)用的時(shí)候也是直接call函數(shù)的地址了,不用鏈接的時(shí)候去找了。
第二種方法:模板定義的位置顯式實(shí)例化。這種方法不實(shí)用,不推薦使用。

到此這篇關(guān)于C++適用入門同學(xué)的模板講解的文章就介紹到這了,更多相關(guān)C++模板內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言動(dòng)態(tài)開(kāi)辟內(nèi)存詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言動(dòng)態(tài)開(kāi)辟內(nèi)存,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02
QT設(shè)計(jì)秒表功能(跑步計(jì)時(shí)器)
這篇文章主要為大家詳細(xì)介紹了QT設(shè)計(jì)秒表功能,跑步計(jì)時(shí)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
關(guān)于C++中數(shù)據(jù)16進(jìn)制輸出的方法
本文主要介紹了關(guān)于C++中數(shù)據(jù)16進(jìn)制輸出的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
C指針原理教程之語(yǔ)法樹(shù)及其實(shí)現(xiàn)
本文給大家分享的是如何使用C語(yǔ)言的指針原來(lái)來(lái)實(shí)現(xiàn)語(yǔ)法樹(shù),并給大家提供了詳細(xì)的實(shí)例代碼,希望大家能夠喜歡2019-02-02
使用c語(yǔ)言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管
這篇文章主要介紹了使用c語(yǔ)言輕松實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存管,本文章內(nèi)容詳細(xì),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,需要的朋友可以參考下2023-01-01
openCV中meanshift算法查找目標(biāo)的實(shí)現(xiàn)
本文主要介紹了openCV中meanshift算法查找目標(biāo)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

