C++適用入門同學(xué)的模板講解
1. 泛型編程
C++下一個(gè)針對(duì)C語言不足而設(shè)計(jì)的語法就叫作模板,模板的這種思想叫作泛型編程。
泛型編程的意思是:
我們以前C語言寫代碼是不是都是針對(duì)一種類型,比如我們寫個(gè)Swap,寫個(gè)排序,我們換一個(gè)類型,程序都得掛。
而泛型編程不再是針對(duì)某種類型,能夠適應(yīng)廣泛的類型。泛型編程用的一個(gè)東西就叫作模板。
博主先帶大家來舉一個(gè)簡單的范例:
如何實(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è)。有意思沒?
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ù)模板與類型無關(guān),在使用時(shí)被參數(shù)化,根據(jù)實(shí)參類型產(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é)可能就會(huì)問這個(gè)函數(shù)模板到底是怎么做到的呢?
大家猜一猜這兩個(gè)Swap調(diào)用的是不是同一個(gè)函數(shù)?

ok,博主告訴大家,它們調(diào)用的肯定不是同一個(gè)函數(shù)。雖然它們看起是來調(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í)模板就是將本來應(yīng)該我們做的重復(fù)的事情交給了編譯器。
模板的實(shí)例化:

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

2.4 函數(shù)模板的實(shí)例化
用不同類型的參數(shù)使用函數(shù)模板時(shí),稱為函數(shù)模板的實(shí)例化。模板參數(shù)實(shí)例化分為:隱式實(shí)例化和顯式實(shí)例化。
2.4.1顯式實(shí)例化
講到這,博主問大家一個(gè)問題:函數(shù)模板一定就是推演的嗎?不一定。
假設(shè),我們寫了一個(gè)沒有參數(shù)或者沒有用模板參數(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ù)。只是說模板參數(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í)候是不是就無法調(diào)用了。那我就想這樣調(diào)用怎么辦呢?
ok,也有。
此時(shí)有兩種處理方式:
1. 用戶自己來強(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è)匹配既沒有非模板函數(shù),也沒有函數(shù)模板可以匹配到的時(shí)候,會(huì)嘗試通過自動(dòng)類型轉(zhuǎn)換調(diào)用到非模板函數(shù)(前提是可以轉(zhuǎn)換為非模板函數(shù)的參數(shù)類型)
3. 類模板
我們以前C語言要解決類型更換的問題用的是typedef,但是typedef不能解決全部的問題。
假設(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語言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ù)沒有傳參或者構(gòu)造函數(shù)沒有參數(shù),那就麻煩了,所以類模板得在調(diào)用的時(shí)候指定類型。
注意:Push的傳參不能傳值,因?yàn)榇鎯?chǔ)的類型不一樣,如果是自定義類型,還可能會(huì)考慮深拷貝的問題,代價(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是哪來的。

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

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

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

這樣頭文件展開的時(shí)候,就是聲明和定義一起在test.cpp中展開了,這樣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語言實(shí)現(xiàn)輸入一組數(shù)自動(dòng)從大到小排列的實(shí)例代碼
下面小編就為大家?guī)硪黄猚語言實(shí)現(xiàn)輸入一組數(shù)自動(dòng)從大到小排列的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09
c語言 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)之字符串
這篇文章主要介紹了c語言 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)之字符串的相關(guān)資料,需要的朋友可以參考下2017-05-05
vscode使用cmake時(shí)將命令行參數(shù)傳遞給調(diào)試目標(biāo)的方法
這篇文章主要介紹了vscode使用cmake時(shí)將命令行參數(shù)傳遞給調(diào)試目標(biāo),下面介紹了一個(gè)示例,將參數(shù)first_arg, second-arg和third arg傳遞給程序(此處需要注意,third arg中間雖然存在空格,但是仍然被視作一個(gè)參數(shù)),需要的朋友參考下吧2024-03-03
Qt使用QCustomPlot的實(shí)現(xiàn)示例
QCustomPlot是一個(gè)基于Qt C++的圖形庫,用于繪制和數(shù)據(jù)可視化,并為實(shí)時(shí)可視化應(yīng)用程序提供高性能服務(wù),本文主要介紹了Qt使用QCustomPlot的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-01-01
Matlab控制電腦攝像實(shí)現(xiàn)實(shí)時(shí)人臉檢測(cè)和識(shí)別詳解
人臉識(shí)別過程主要由四個(gè)階段組成:人臉檢測(cè)、圖像預(yù)處理、面部特征提取和特征識(shí)別。這篇文章主要介紹了如何使用MATLAB控制筆記本電腦的攝像頭,并進(jìn)行實(shí)時(shí)人臉檢測(cè)和識(shí)別,需要的可以參考一下2022-10-10

