在我們編寫代碼時(shí),我們會(huì)遇見這種情況:
比如交換函數(shù),當(dāng)我們要交換的類型是int(傳的參數(shù)為int型)時(shí),我們要編寫的swap函數(shù)的形參就應(yīng)該是int,但當(dāng)我們要交換的是double型時(shí),我們還要再寫一個(gè)swap函數(shù)來滿足要求。每換一種類型就要再重載一個(gè)swap函數(shù)來滿足條件。
雖然通過這方法重載實(shí)現(xiàn)所有類型的交換函數(shù),但是這種方法有幾個(gè)不好的地方,一是重載函數(shù)僅僅類型不同,導(dǎo)致代碼的復(fù)用率很低,只要有新類型出現(xiàn),就要增加對(duì)應(yīng)的函數(shù);再者代碼的可維護(hù)性比較低,一個(gè)出錯(cuò)可能所有的重載都出錯(cuò),要一個(gè)一個(gè)改。興寧ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書未來市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18980820575(備注:SSL證書合作)期待與您的合作!
通過上面的例子,我們想能不能告訴編譯器一個(gè)模子,編譯器可以通過不同的類型利用這樣的模子自動(dòng)生成適合各種類型的函數(shù)。答案是當(dāng)然可以
即泛型編程:編寫與類型無關(guān)的通用代碼,而模板是泛型編程的基礎(chǔ)。
下面我們來鄭重的引入模板
如何使用?
template <typename T1,typename T2...>
返回值 函數(shù)名(參數(shù)列表){ }
typename是用來定義模板參數(shù)關(guān)鍵字的,也可以用class
例如:
template<typename T>
void Swap(T &x, T &y)
//之前不同的類型,對(duì)應(yīng)不同的形參列表,所以要寫多個(gè)重載函數(shù),但現(xiàn)在只寫一個(gè)模板函數(shù),編譯器就可以根據(jù)這個(gè)模板結(jié)合傳入的參數(shù)就可完成所有類型的生成對(duì)應(yīng)類型的函數(shù)以供調(diào)用
T tmp = x;
x = y;
y = tmp;
}
//在這個(gè)函數(shù)中,T只能被替換為一樣的類型,若傳入?yún)?shù)不同,則編譯器其則生成不了匹配的函數(shù),而編譯器又不會(huì)進(jìn)行類型轉(zhuǎn)換,因而會(huì)報(bào)錯(cuò)
//若想要不同類型,可定義兩個(gè)T
template<class T1, class T2> //typename可用class代替
void Swap(T1 &x, T2 &y)
{
T1 tmp = x;
x = y;
y = tmp;
}
int main()
{
int x1=0, y1=1;
double x2 = 1.0, y2 = 2.0;
Swap(x1, y1);
Swap(x2, y2);
Swap(x1, y2);
}
函數(shù)模板實(shí)例化
編譯器根據(jù)不同的參數(shù)用模板推演不同的函數(shù)稱為函數(shù)模板的實(shí)例化,模板參數(shù)實(shí)例化分為:隱式實(shí)例化和顯示實(shí)例化。
通過下面例子介紹隱式實(shí)例化和顯示實(shí)例化
template<typename T>
T Add(const T &x, const T &y)
{
return x + y;
}
int main()
{
int x1=0, y1=1;
double x2 = 1.0, y2 = 2.0;
//隱式實(shí)例化,讓編譯器根據(jù)實(shí)參推演模板參數(shù)的實(shí)際類型
Add(x1, y1); //隱式實(shí)例化
//當(dāng)無法確定T是什么,由該推演成什么的時(shí)候,會(huì)報(bào)錯(cuò),因而要不然進(jìn)行強(qiáng)轉(zhuǎn)使傳入?yún)?shù)類型相同,要不然進(jìn)行顯式實(shí)例化
Add(x1, (int)y2); //強(qiáng)轉(zhuǎn)
//顯式實(shí)例化,直接告訴編譯器,由模板該推演成什么,在函數(shù)名后的<>中指定模板參數(shù)的實(shí)際類型
Add<int>(x1, (int)y2);
}
1.一個(gè)非模板函數(shù)可以和一個(gè)同名的函數(shù)模板同時(shí)存在,而且該函數(shù)模板還可以被實(shí)例化為這個(gè)非模板函數(shù)。
2.模板函數(shù)不允許自動(dòng)類型轉(zhuǎn)換,但普通函數(shù)可以進(jìn)行自動(dòng)類型轉(zhuǎn)換。
3.對(duì)于非模板函數(shù)和同名函數(shù)模板,如果其他條件都相同,在調(diào)動(dòng)時(shí)會(huì)優(yōu)先調(diào)用非模板函數(shù)而不會(huì)從該模板產(chǎn)生出一個(gè)實(shí)例。如果模板可以產(chǎn)生一個(gè)具有更好匹配的函數(shù),那么將選擇模板。
int Add(int x, int y)
{
return x + y;
}
template<typename T>
T Add(const T &x, const T &y)
{
return x + y;
}
template<typename T1, typename T2>
T1 Add(const T1 &x, const T2 &y)
{
return x + y;
}
int main()
{
//1
Add(1, 2); //調(diào)用非模板函數(shù),無需模板實(shí)例化
Add<int>(1, 2);//調(diào)用編譯器特化的模板函數(shù)版本 --如果指定類型就必須用模板來生成相應(yīng)類型
//2.
Add(1, 2.0); //此處會(huì)調(diào)用非模板函數(shù),發(fā)生隱式類型轉(zhuǎn)換 (說明:此處還未加Add(const T1 &x, const T2 &y)函數(shù)模板)
//3.
Add(1, 2.0); //選擇函數(shù)模板若選擇非模板函數(shù),則會(huì)發(fā)生隱式類型轉(zhuǎn)化,
//不如調(diào)用Add(const T1 &x, const T2 &y)這個(gè)實(shí)例化的函數(shù)形參列表更匹配
}
使用格式:
template <class T1,class T2....>
class A( /A為類模板名,A不是具體的類,是編譯器根據(jù)被實(shí)例化的類型生成具體類的模具)
{
...
};
類模板實(shí)例化需要在類模板名字后跟<>,然后將實(shí)例化的類型放在<>中即可,類模板名字不是真正的類,而實(shí)例化的結(jié)果才是真正的類。
例如容器vector的實(shí)現(xiàn):
template <class T>
class vector{
typedef T* iterator;
public:
//構(gòu)造析構(gòu)
vector()
: _start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
}
vector(int n, const T &value = 0)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
_start = new T[n+1];
int i = n;
while (i--)
{
_start[i] = value;
}
_start[n] = '\0';
_finish = _start + n;
_endOfStorage = _finish;
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
int n=0;
auto tmp = first;
while (tmp != last)
{
n++;
tmp++;
}
_start = new T[n + 1];
_finish = _start + n;
_endOfStorage = _finish;
last--;
while (n--)
{
_start[n] = *last;
last--;
}
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish (nullptr)
,_endOfStorage(nullptr)
{
reserve(v.capacity());
//memcpy(_start, v._start,v.size());
for (int i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_endOfStorage = _start + v.capacity();
}
~vector()
{
delete[] _start;
_start = nullptr;
_finish = nullptr;
_endOfStorage = nullptr;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
int size()const
{
return _finish - _start;
}
int capacity()const
{
return _endOfStorage - _start;
}
friend iterator find(iterator begin, iterator end, const T &value);
private:
T *_start;
T *_finish;
T *_endOfStorage;
};
// 注意:類模板中函數(shù)放在類外進(jìn)行定義時(shí),需要加模板參數(shù)列表
template <class T>
T* find(T* begin, T* end, const T &value)
{
auto it = begin;
while (it != end)
{
if (*it == value)
{
return it;
}
it++;
}
return nullptr;
}
int main(){
vector<int> v1; //實(shí)例化
}
模板的其他知識(shí)說明:
模板參數(shù)分為類型形參與非類型形參
類型形參:出現(xiàn)在模板參數(shù)列表中,跟在class或者typename之類的參數(shù)類型名稱。
非類型形參,就是用一個(gè)常量作為類(函數(shù))模板的一個(gè)參數(shù),在類(函數(shù))模板中可將該參數(shù)當(dāng)成常量來使用。
例如:
namespace Eg
{
template <class T,size_t N=10> //N在下面類中,作為常數(shù)使用
class array{
public:
size_t size()const
{
//N = 20; //會(huì)報(bào)錯(cuò):錯(cuò)誤 1 error C2106: “=”: 左操作數(shù)必須為左值,可證明N是常數(shù)
return _size;
}
private:
T _array[N];
size_t _size;
};
}
void Test1(){
Eg::array<int> x;
x.size();
}
對(duì)于一些特殊的類型(比如指針類型),使用已寫的模板可能達(dá)不到我們想要的結(jié)果,得出錯(cuò)誤的結(jié)果。
例如:
template <class T>
T& MAX_T(T& left, T& right)
{
return left>right?left:right;
}
void Test(){
int x = 2,y=3;
cout << MAX_T(x, y) << endl;
char *p1 = "wello";
char *p2 = "hello";
cout << MAX_T(p1, p2) << endl; //應(yīng)該輸出wello 但因?yàn)楸容^的是地址卻輸出hello不符合邏輯,因此我們要為char*來特化一個(gè)模板提供給這種類型
}
通過上述列子我們可以通過對(duì)模板進(jìn)行特化,在原來模板函數(shù)(或類)的基礎(chǔ)上,針對(duì)特殊類型進(jìn)行特殊化的實(shí)現(xiàn)。比如上述例子中為char*特化一個(gè)函數(shù),按照我們的想法針對(duì)這種類型進(jìn)行特殊化處理,得到正確的結(jié)果。
模板特化又分為函數(shù)模板特化與類模板特化
函數(shù)模板的特化方式:
1.要先有一個(gè)基礎(chǔ)函數(shù)模板
2.關(guān)鍵字template后面接一堆空的尖括號(hào)<>
3.函數(shù)名后跟一對(duì)尖括號(hào)里面放需要特化的類型
4.函數(shù)形參表必須要和模板函數(shù)的基礎(chǔ)參數(shù)類型完全相同,不同的話編譯器可能會(huì)報(bào)一些錯(cuò)誤
//比如針對(duì)上述char*類型特化:
template <class T>
T& MAX_T(T& left, T& right)
{
return left>right?left:right;
}
template <>
char*& MAX_T<char*>(char*& left, char*& right)
{
if (strcmp(left, right) == 1)
{
return left;
}
else{
return right;
}
}
void Test(){
char *p1 = "wello";
char *p2 = "hello";
cout << MAX_T(p1, p2) << endl;
//會(huì)調(diào)用char*& MAX_T<char*>(char*& left, char*& right)特化模板函數(shù)
}
但是??!
對(duì)于特例化的函數(shù)模板,一般都是將該函數(shù)直接給出,一是實(shí)現(xiàn)簡(jiǎn)單,二是因?yàn)楹瘮?shù)模板可能會(huì)遇到不能處理或者處理有誤的類型
類模板特化又分為全特化和偏特化
template<class T1, class T2>
class Data1
{
public:
Data1() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<>
class Data1<int, int>
{
public:
Data1()
{
cout << "Data1<int, int>" << endl;
}
private:
int _d1;
int _d2;
};
void Test(){
Data1<int, int> d1; //用特化模板參數(shù)類
Data1<int, double> d2;
}
2.偏特化:有兩種表現(xiàn):部分特化和參數(shù)更進(jìn)一步的限制
template<class T1, class T2>
class Data2
{
public:
Data2() { cout << "Data2<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<class T1>
class Data2<T1, int> //特化一半
{
public:
Data2()
{
cout << "Data2<T1, int>" << endl;
}
private:
T1 _d1;
int _d2;
};
void Test(){
Data2<int, int> d1; //用部分特化模板參數(shù)類
Data2<double, int> d2; //用部分特化模板參數(shù)類 因?yàn)楹竺娴亩际莍nt,都符合這個(gè)部分特化模板
Data2<int, double> d3;
Data2<double, double> d4;
}
template<class T1, class T2>
class Data2<T1*, T2*>
{
public:
Data2()
{
cout << "Data2<T1*, T2*>" << endl;
}
private:
T1* _d1;
T2* _d2;
};
void Test(){
Data2<int*, int> d5; //使用class Data2<T1, int>
Data2<int, int*> d6; //使用template<class T1, class T2> class Data2{}
Data2<int*, int*> d7; //使用class Data2<T1*, T2*>
Data2<int*, double*> d8; //使用class Data2<T1*, T2*>
}
通過以下題目來感受類型萃取
// 寫一個(gè)通用的拷貝函數(shù),要求:效率盡可能高
/*Way1:
//String:用該函數(shù)會(huì)報(bào)錯(cuò)拷貝的是地址,析構(gòu)會(huì)對(duì)同一塊空間釋放兩次,會(huì)報(bào)錯(cuò)
template<class T>
void Copy(T* dst, T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
*/
//因而要區(qū)分自定義和內(nèi)置類型,來調(diào)用使用不同的方法進(jìn)行拷貝
/*
//Way2:增加函數(shù)判定 區(qū)分自定義和內(nèi)置類型
bool IsPodType(const char* strType){
const char* arrType[] = { "char", "short", "int", "long", "long long", "float","double", "long double" };
for (size_t i = 0; i < sizeof(arrType) / sizeof(arrType[0]); ++i) //每次都要遍歷,效率太低!
{
if (0 == strcmp(strType, arrType[i]))
return true;
}
return false;
}
template<class T>
void Copy(T* dst, T* src, size_t size)
{
if (IsPodType(typeid(T).name()))
{
memcpy(dst, src, sizeof(T)*size);
}
else{
for (int i = 0; i < size; i++)
{
dst[i] = src[i];
}
}
}
*/
//Way3:萃取類型
//代表內(nèi)置類型
struct TrueType{
static bool Get(){ //只有靜態(tài)才能用 :: 訪問
return true;
}
};
//代表自定義類型
struct FlaseType{
static bool Get(){
return false;
}
};
template<class T>
struct TypeTraits{
typedef FlaseType IsPodeType;
};
//對(duì)上述模板進(jìn)行實(shí)例化,將內(nèi)置類型都特化
template<>
struct TypeTraits<int>{
typedef TrueType IsPodeType;
};
template<>
struct TypeTraits<double>{
typedef TrueType IsPodeType;
};
/*
T為int:TypeTraits<int>已經(jīng)特化過,程序運(yùn)行時(shí)就會(huì)使用已經(jīng)特化過的TypeTraits<int>, 該類中的IsPODType剛好為類TrueType,而TrueType中Get函數(shù)返回true,內(nèi)置類型使用memcpy方式拷貝
T為string:TypeTraits<string>沒有特化過,程序運(yùn)行時(shí)使用TypeTraits類模板, 該類模板中的IsPODType剛好為類FalseType,而FalseType中Get函數(shù)返回true,自定義類型使用賦值方式拷貝
*/
template<class T>
void Copy(T* dst, T* src, size_t size)
{
if (TypeTraits<T>::IsPodeType::Get())
{
memcpy(dst, src, sizeof(T)*size);
}
else{
for (int i = 0; i < size; i++)
{
dst[i] = src[i];
}
}
}
void TestCopy()
{
int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int array2[10];
Copy(array2, array1, 10);
String s1[3] = { "1111", "2222", "3333" };
String s2[3];
Copy(s2, s1, 3);
}
要說模板分離編譯我們就要先來談?wù)勈裁词欠蛛x編譯
一個(gè)工程中有很多文件,但他們分為兩大類:頭文件和源文件
程序的運(yùn)行有以下五個(gè)步驟:
預(yù)處理-->編譯-->匯編-->鏈接
頭文件會(huì)在預(yù)處理階段展開,在預(yù)處理期間程序會(huì)將頭文件中的內(nèi)容復(fù)制一份到源文件。參與編譯的只有源文件,而且每個(gè)源文件都單獨(dú)進(jìn)行編譯生成目標(biāo)文件,最后將所有目標(biāo)文件鏈接形成單一的可執(zhí)行文件。如下圖:
目標(biāo)文件鏈接的時(shí)候,是通過找函數(shù)地址(入口)進(jìn)行調(diào)用的。
注意:強(qiáng)調(diào):每個(gè)源文件都單獨(dú)進(jìn)行編譯
對(duì)于模板:
實(shí)例化之前編譯器只會(huì)做一些簡(jiǎn)單的語法測(cè)驗(yàn),不會(huì)生成處理具體類型的代碼
實(shí)例化期間編譯器用過推演形參類型來確保模板參數(shù),通過列表中T的實(shí)際類型在生成處理具體類型的代碼
//頭文件 CompilingTest.h
template <class T>
T Add(T left,T right);
//源文件 CompilingTest.c
#include "CompilingTest.h"
template <class T>
T Add(T left, T right)
{
return left + right;
}
//源文件 main.c
#include "CompilingTest.h"
int main()
{
Add(1.0, 1.0); //會(huì)發(fā)生報(bào)錯(cuò),因?yàn)闆]有實(shí)例化Add(int,int),找不到匹配的函數(shù)入口地址,因而在鏈接時(shí)會(huì)報(bào)錯(cuò)
}
//頭文件 CompilingTest.h
template <class T>
T Add(T left,T right);
//源文件 CompilingTest.c
#include "CompilingTest.h"
template <class T>
T Add(T left, T right)
{
return left + right;
}
void tmp(){
Add(1, 2); //在此編譯器推演出了T->int的函數(shù),在鏈接時(shí)找到了適合Add(1, 1);該函數(shù)的入口地址,因而才不會(huì)報(bào)錯(cuò)
}
//源文件 main.c
#include "CompilingTest.h"
int main()
{
Add(1, 1); //這樣就不會(huì)發(fā)生錯(cuò)誤了
//Add(1.0, 1.0); //無匹配的Add(double,,double)
}
通過上述例子可得模板不支持分離編譯
解決方法:
分離編譯詳細(xì)講解: http://blog.csdn.net/pongba/article/details/19130
優(yōu)點(diǎn):
本文題目:模板
文章分享:http://jinyejixie.com/article36/ijdpsg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、網(wǎng)站導(dǎo)航、響應(yīng)式網(wǎng)站、虛擬主機(jī)、手機(jī)網(wǎng)站建設(shè)、網(wǎng)站改版
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)