文章目錄
模板
爲什麼要模板,其實目的就是一個,
就是將本來應該我們做的重複的事情交給編譯器
模板的基本用法
模板函數
用法用例
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
上面實現了一個兩個類型交互的函數,利用模板函數,我們可以傳入類型完成不同類型值的交換,例如
int a;
int b;
swap<int>(a,b);
float f1;
float f2;
swap<float>(f1,f2);
隱式實例化
編譯器根據傳入實參推演模板參數的實際類型
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a;
int b;
swap(a,b);
}
顯式實例化
在函數名後的<>中指定模板參數的實際類型
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a;
int b;
swap<int>(a,b);
}
模板參數的匹配原則
(1)一個非模板函數可以和一個同名的模板函數同時存在,而且該函數的模板還可以被實例化爲這個非模板函數
void swap(int& a,int& b)
{
int tmp = a;
a = b;
b = tmp;
}
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a=10;
int b=20;
swap(a,b);//編譯器優先調用可以匹配的非模板參數
swap<int>(a,b);//主動讓編譯器生成swap的實例
}
(2)對於非模板函數的同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好的函數,那麼將選擇模板
void swap(int& a,int& b)
{
int tmp = a;
a = b;
b = tmp;
}
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a=10;
int b=20;
float f1;
float f2;
swap(a,b);//編譯器優先調用可以匹配的非模板參數
swap(f1,f2);//編譯器會生成一個更加匹配的版本,編譯器根據實參生成更加匹配的add函數
}
(3)模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換.
模板定義的格式
類模板
template<class T1,class T2,..,clas Tn>
class 模板類名
{
//類內成員定義
}
類外的函數定義需要加上模板參數列表
template<class T>
class Vector
{
...
}
template <class T>
vector<T>::~vector()
{
...
}
模板的實例化
我們定義的模板函數其實不是真正調用的函數,我們真正使用的函數是通過模板函數實例化出來的代碼
非類型模板參數
上面的模板是傳類型,而事實上,模板還可以傳非類型的參數,例如下面
示例代碼
#include <iostream>
template<class T,size_t N = 10>
class Array
{
public:
T _array[N];
};
int main()
{
Array<int,10> arr1;
Array<int,20> arr2;
return 0;
}
注意
(1)浮點數,類對象以及字符串是不允許作爲非類型模板參數的
(2)非類型的模板參數必須在編譯期就能確認結果。就和數組長度的定義一樣
模板的特化
模板可以實現與類型無關的代碼,但是也會有一些地方需要特殊處理,例如
template<class T>
bool IsEqual(T& left,T& right)
{
return left==right;
}
void Test()
{
char* p1 = "hello";
char* p2 = "world";
if(IsEqual(p1, p2))
cout<<p1<<endl;
else
cout<<p2<<endl;
}
對於字符串的比較就不能直接像上面這樣比較,而需要特殊化處理
模板特化的步驟
(1)必須有一個基礎的模板
(2)關鍵template後面接一對空的尖括號<>
(3)函數名後跟一對尖括號,尖括號指定需要特化的類型
(4) 函數形參表必須和函數模板的基礎參數類型完全相同,如果不同就會報錯
template<>
bool IsEqual<char*>(char*& left, char*& right)
{
if(strcmp(left, right) > 0)
return true;
return false;
}
注意
也可以直接實現該函數,直接匹配最佳的非模板參數,那樣就不需要編譯器再生成一個實例了
bool IsEqual(char* left, char* right)
{
if(strcmp(left, right) > 0)
return true;
return false;
}
利用非類型模板參數的特性解題
(1)求1+2+3+…n,要求不能使用乘法除法,for,while,if,else,switch,case等關鍵字及條件判斷語句(A?B:C)**
解題思路
利用非類型參數做一個遞歸的實例化,利用特化做一個遞歸的收斂
代碼
模板的偏特化
部分特化
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
// 將第二個參數特化爲int
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
對參數進行限制
//兩個參數偏特化爲指針類型
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;
};
模板特化的應用
類型萃取
爲什麼要類型萃取呢?看下面的代碼
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
int main()
{
// 試試下面的代碼
string strarr1[3] = {"11", "22", "33"};
string strarr2[3];
Copy(strarr2, strarr1, 3);
}
顯然對字符串的拷貝,上面的代碼並不通用 ,我們可以嘗試用一個方法區別自定義類型和內置類型,比如
template<class T>
void Copy(T* dst, const T* src, size_t size, bool IsPODType)
{
if(IsPODType)
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
上面增加了一個 bool 來區別自定義類型和內置類型,但是這樣就增加了使用上出錯的概率,能否用一種方法讓編譯器自動識別呢,如下
//
// POD: plain old data 平凡類型(無關痛癢的類型)--基本類型
// 指在C++ 中與 C兼容的類型,可以按照 C 的方式處理。
//
// 此處只是舉例,只列出個別類型
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(array)/sizeof(array[0]); ++i)
{
if(0 == strcmp(strType, arrType[i]))
return true;
}
return false;
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
if(IsPODType(typeid(T).name()))
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
上面利用tipeid來確認拷貝對象的實際類型,在內置類型的集合中尋找是否匹配內置類型,但是這樣就有缺陷了,每次都要比較字符串,效率很低
事實上我們可以藉助模板的特化進行類型萃取,而且是編譯的時候就已經完成了,所以這方法效率很高
/*
T爲int:TypeTraits<int>已經特化過,程序運行時就會使用已經特化過的TypeTraits<int>, 該類中的
IsPODType剛好爲類TrueType,而TrueType中Get函數返回true,內置類型使用memcpy方式拷貝
T爲string:TypeTraits<string>沒有特化過,程序運行時使用TypeTraits類模板, 該類模板中的IsPODType
剛
好爲類FalseType,而FalseType中Get函數返回true,自定義類型使用賦值方式拷貝
*/
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
if(TypeTraits<T>::IsPODType::Get())
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
int main()
{
int a1[] = {1,2,3,4,5,6,7,8,9,0};
int a2[10];
Copy(a2, a1, 10);
string s1[] = {"1111", "2222", "3333", "4444"};
string s2[4];
Copy(s2, s1, 4);
return 0;
}
關於爲什麼模板不能分離編譯?
1.爲什麼模板不能分離編譯了,首先我們要這樣想,模板是一個圖紙一樣的東西,假設我們的模板參數是一個int,那編譯器就要藉助這個模板生成一個爲int類型服務的代碼,也就是生成一個模板實例,而這個實例是一段代碼。
2.模板生成實例的時候是在鏈接前,生成實例的前提是當前文件中調用了模板
3.C/C++程序要運行,一般要經歷一下步驟
預編譯—》編譯—>彙編—> 鏈接
編譯:對程序按照語言特性進行詞法,語法,語義分析,錯誤檢查無誤後生成彙編代碼,注意頭文件不參與編譯,編譯器對工程中的多個源文件是分離開單獨編譯的。
鏈接:將多個obj文件和合併成一個,並處理沒有解決的地址問題。
下面代碼引用自 https://blog.csdn.net/pongba/article/details/19130
//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); // 這裏只是個聲明
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f()// 模板的實現
{
…//do something
}
//---------------main.cpp---------------//
#include”test.h”
int main()
{
A<int> a;
f(); // #1
}
編譯器在編譯 #1的時候並不知道A::f的定義,因爲它不在test.h裏面,於是編譯器只好寄希望於連接器,希望它能夠在其他的.obj裏面找到A::f的實例,在本例中就是test.obj,然而,實際上後者根本就沒有A::f的二進制代碼
因爲C++標準明確表示,當一個模板不被用到的時候它就不該被實例化出來,test.cpp中用到了A::f了嗎?沒有!!所以實際上test.cpp編譯出來的test.obj文件中關於A::f一行的二進制代碼也沒有,於是連接器就傻了,根本就沒有要的二進制代碼嘛,於是就報出了鏈接錯誤
如果在test.cpp中寫一個函數,然後裏面調用了A::f 編譯器就會把它實例化出來,因爲test.cpp使用了A::f,並且test.cpp也有A::f的模板,所以就會實例化出來,在這種情況下,我們再鏈接,就發現可以鏈接上了。
下面是在test.cpp中寫一個函數,函數中調用A::f,這樣就會生成A::f的實例,test.obj的符號導出表中就有了A::f這個符號的地址,於是連接器就能完成任務。
如下
//---------test.h------//
template<class T>
int Add(const T& d1,const T& d2);
//---------test.cpp-----//
#include "test.h"
template<class T>
int Add(const T& d1,const T& d2)
{
return d1+d2;
}
void fun()
{
Add<int>(1,2);
}
//-------main.cpp---//
#include <iostream>
#include "test.h"
using namespace std;
int main()
{
cout<<Add<int>(1,2)<<endl;
return 0;
}
//------makefile---//
main:main.cpp test.cpp
g++ -o $@ $^
加上void fun()
{
Add(1,2);
}後就可以編譯成功了,同時要這樣編譯g++ -o main main.cpp test.cpp