什麼是泛型編程?
泛型編程:編寫與類型無關的通用代碼,是代碼複用的一種手段。模板是泛型編程的基礎
函數模板
函數模板代表了一個函數家族,在使用時被參數化,根據實參類型產生函數的特定類型版本,函數模板不是函數
格式:
template<typename T1, typename T2,......,typename Tn>
//返回值類型 函數名(參數列表){}
//typename 也可以用 class 代替
template<typename T>
void Swap( T& left, T& right) {
T temp = left;
left = right;
right = temp;
}
函數模板原理:
模板本身並不是一個函數,但是編譯器會根據模板生成一個具體的函數,所以模板就是將本來程序猿做的事情交給了編譯器來完成
在編譯期間,編譯器會根據傳入的實參類型來推演實參類型,生成具體的函數,比如上述代碼中如果傳入兩個 int 型變量,編譯器會推演出變量類型生成 int 對應的處理函數,專門用來處理 int 型變量
模板函數實例化:
隱式實例化:讓編譯器根據實參推演模板參數的實際類型
template<class T>
T Add(const T& left, const T& right) {
return left + right;
}
int main() {
int a1 = 10;
int a2 = 20;
double d1 = 10.0
double d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
/*
Add(a1, d1);
該語句不能通過編譯,因爲在編譯期間,當編譯器看到該實例化時,需要推演其實參類型
通過實參a1將T推演爲int,通過實參d1將T推演爲double類型,但模板參數列表中只有一個T,
編譯器無法確定此處到底該將T確定爲int 或者 double類型而報錯
注意:在模板中,編譯器一般不會進行類型轉換操作
*/
// 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化
Add(a, (int)d);
return 0;
}
顯示實例化:在函數名後的<>中指定模板參數的實際類型
int main(void) {
int a = 10;
double b = 20.0;
// 顯式實例化,如果類型不匹配,編譯器會嘗試進行隱式類型轉換,如果無法轉換成功編譯器將會報錯
Add<int>(a, b);
return 0;
}
模板參數的匹配原則:
一個模板可以和一個同名的普通函數共同存在,並且模板還可以實例化爲這個函數
對於模板函數和非模板函數,優先調用非模板函數而不會生成模板函數,但是如果模板函數能夠生成一個更加好的版本,則調用模板函數
模板函數不支持自動類型轉換,普通函數支持
類模板
格式:
template<class T1, class T2, ..., class Tn>
class 類模板名 {
// 類內成員定義
};
類模板的實例化:
類模板實例化需要在類模板名字後跟<>,然後將實例化的類型放在<>中,類模板並不是類,實例化結果纔是真正的類
vector<int> v;
//vector 不是類型,vector<int>纔是類型
非類型模板參數
模板參數分爲 類型形參和非類型形參
類型形參:就是在模板參數列表中,跟在 class 和 typename 後面的參數類型名稱
非類型形參:就是用一個常量代替模板的一個參數,這個常量在模板中可以直接使用
template<class T, size_t N = 10>
class Array {
public:
private:
T _array[N];
size_t _size;
}
浮點數、類對象以及字符串是不允許作爲非類型模板參數的
模板的特化
在原模板類的基礎上,針對特殊類型所進行特殊化的實現方式,模板特化中分爲函數模板特化與類模板特化
函數模板特化:
(1) 必須要先有一個基礎的函數模板
(2) 關鍵字 template 後面接一對空的尖括號 <>
(3) 函數名後跟一對尖括號,尖括號中指定需要特化的類型
(4) 函數形參表: 必須要和模板函數的基礎參數類型完全相同
void Swap(int& left, int& right) {
int tmp = left;
left = right;
right = tmp;
}
類模板特化:
全特化:將模板參數列表中所有參數都確定化
template<class T1, class T2>
class Data {
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
template<>
class Data<int, char> {
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
void TestVector() {
Data<int, int> d1; //調用模板
Data<int, char> d2; //調用偏特化版本
}
偏特化:任何對模板參數進行進一步條件限制生成的特化版本
template<class T1, class T2>
class Data {
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//1. 部分特化
// 將模板參數的一部分特化
template <class T1>
class Data<T1, int> {
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
//2. 對模板參數的進一步限制
// 兩個參數偏特化爲指針類型
template <typename T1, typename T2>
class Data <T1*, T2*> {
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
// 兩個參數特化爲引用類型
template <typename T1, typename T2>
class Data <T1&, T2&> {
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
void test2 () {
Data<double , int> d1; // 調用特化的int版本
Data<int , double> d2; // 調用基礎的模板
Data<int *, int*> d3; // 調用特化的指針版本
Data<int&, int&> d4(1, 2); // 調用特化的引用版本
}
類模板特化應用之類型萃取
類型萃取就是利用類模板的特化,將內置類型和自定義類型區分開
/*==============================================================
* Copyright . All rights reserved.
* Filename: traits.cpp
* Author: shen
* Last modified: 2019-08-28 16:20
* Description:
==============================================================*/
#include <cstdio>
// 代表內置類型
struct TypeTrue {
static bool Get() {
return true;
}
};
// 代表自定義類型
struct TypeFalse {
static bool Get() {
return false;
}
};
//給出以下類模板,用戶可以按照任意類型實例化該類模板。
template<class T>
struct TypeTraits {
typedef TypeFalse IsPODType;
};
//對上述的類模板進行實例化
template<>
struct TypeTraits<int> {
typedef TypeTrue IsPODType;
};
template<>
struct TypeTraits<double> {
typedef TypeTrue IsPODType;
};
template<>
struct TypeTraits<long> {
typedef TypeTrue IsPODType;
};
template<>
struct TypeTraits<char> {
typedef TypeTrue IsPODType;
};
// ... 所有內置類型都特化一下
//自定義類型
class Data {
};
int main() {
if (TypeTraits<long>::IsPODType::Get()) {
printf("is true\n");
}
else {
printf("is false\n");
}
if (TypeTraits<Data>::IsPODType::Get()) {
printf("is true\n");
}
else {
printf("is false\n");
}
return 0;
}
模板分離編譯
什麼是分離編譯:
一個程序(項目)由若干個源文件共同實現,而每個源文件單獨編譯生成目標文件,最後將所有目標文件鏈接起來形成單一的可執行文件的過程稱爲分離編譯
模板不支持分離編譯:
如果將模板的聲明和定義分別放在 .h 文件和 .cpp 文件中,在編譯期間(不會對.h文件進行編譯)由於在 .h 文件中只存在聲明並不存在模板的定義,因此即使在 main 函數中調用了模板也不會對模板進行實例化,最終鏈接的時候會報錯(找不到函數定義)
解決方法:
將模板的聲明和定義放在同一個文件中