C++模板剖析:函数模板、类模板解析
C++中關于模板&泛型編程問題:
?
問題引入:何編寫一個通用加法函數?
?
(1)使用函數重載,針對每個所需相同行為的不同類型重新實現它
int Add(const int &_iLeft, const int&_iRight)
{
return (_iLeft +_iRight);
}
float Add(const float &_fLeft, constfloat &_fRight)
{
return (_fLeft +_fRight);
}
【缺點】
1、只要有新類型出現,就要重新添加對應函數。
2、除類型外,所有函數的函數體都相同,代碼的復用率不高
3、如果函數只是返回值類型不同,函數重載不能解決
4、一個方法有問題,所有的方法都有問題,不好維護。
?
(2)使用公共基類,將通用的代碼放在公共的基礎類里面
?
【缺點】
1、借助公共基類來編寫通用代碼,將失去類型檢查的優點;
2、對于以后實現的許多類,都必須繼承自某個特定的基類,代碼維護更加困難。
?
(3)使用特殊的預處理程序
#define ADD(a, b) ((a) + (b))
【缺點】
不是函數,不進行參數類型檢測,安全性不高
?
綜上所述的問題,我們需要引入泛型編程,即為需要的函數或者類編寫一個模板,在實用的時候實例化即可。那么,什么是泛型編程?什么是模板?
?
一、???泛型編程
泛型編程:編寫與類型無關的邏輯代碼,是代碼復用的一種手段。模板是泛型編程的基礎。
二、???函數模板
函數模板:代表了一個函數家族,該函數與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。
模板函數定義的格式:template<typename T1, teypename T2, ……….typename Tn>
函數返回值 函數名(參數列表)
{
?????? . . . . . .
}
?
Eg:
?????? template<typename T>
T Add( T left, T right )
{
?????? return left+right;
}
?????? template和typename 為關鍵字,T為模板形參的名字,可隨意命名。
?
typename是用來定義模板參數關鍵字,也可以使用class。建議盡量使typename。
??????
?????? 實例化:模板是一個藍圖,它本身不是類或者函數,編譯器用模板產生指定的類或者函數的特定類型版本,產生模板特定類型的過程稱為函數模板實例化
注:模板被編譯了兩次:
① 實例化之前,檢查模板代碼本身,查看是否出現語法錯誤,如:遺漏分號
②在實例化期間,檢查模板代碼,查看是否所有的調用都有效,如:實例化類型不支持某些函數調用
?
實參推演:
從函數實參確定模板形參類型和值的過程稱為模板實參推斷,多個類型形參的實參必須完全匹配
類型形參轉換:
一般不會轉換實參以匹配已有的實例化,相反會產生新的實例。
編譯器只會執行兩種轉換:
1、const轉換:接收const引用或者const指針的函數可以分別用非const對象的引用或者指針來調用
?
2、數組或函數到指針的轉換:如果模板形參不是引用類型,則對數組或函數類型的實參應用常規指針轉換。數組實參將當做指向其第一個元素的指針,函數實參當做指向函數類型的指針。
?
Eg:
template<typename T>
void FunTest1(const T* t)
{
? cout<<"FunTest1();"<<*t<<endl;
}
template<typename T>
void FunTest2(const T& t)
{
?? cout<<"FunTest2();"<<t<<endl;
}
template<typename T>
void FunTest3(T t1, T t2)
{
? cout<<"FunTest3()"<<endl;
}
?
int Add(int a, int b)
{
? return a+b;
}
int main()
{
? int A = 10;
? int* pA = &A;
? FunTest1(pA);
?
? int b = 20;
? int& pB = b;
? FunTest2(pB);
?
? int array1[10];
? int array2[20];
?
? FunTest3(array1, array2);
? FunTest3(Add,Add);
? system("pause");
? return 0;
}
?
?
?????? 模板參數:函數模板有兩種類型參數:模板參數和調用參數。
?
(1)???模板形參:
a、模板形參名字只能在模板形參之后到模板聲明或定義的末尾之間使用,遵循名字屏蔽規則。
b、模板形參的名字在同一模板形參列表中只能使用一次。
Template<typename T, typenameT>
Void FunTest(T t1, T t2)
{};
?
c、 所有模板形參前面必須加上class或者typename關鍵字修飾,并且兩個關鍵字可以混用。
d、在函數模板的內部不能指定缺省的模板實參。
Template<typename T>
T Add(T = int , T)?? //編譯出錯。
{};
非模板類型參數:
?
非模板類型形參是模板內部定義的常量,在需要常量表達式的時候,可以使用非模板類型參數。
?
?
????????????? 注:
1、模板形參表使用<>括起來。
2、和函數參數表一樣,跟多個參數時必須用逗號隔開,類型可以相同也可以不相同。
template<typenameT, typename V …….>
?
3、模板形參表不能為空(模板特化的時候,模板參數為空)。
?
4、模板形參可以是類型形參,也可以是非類型新參,類型形參跟在class和typename后
?
5、模板類型形參可作為類型說明符用在模板中的任何地方,與內置類型或自定義類型使用方法完全相同,可用于指定函數形參類型、返回值、局部變量和強制類型轉換。
?
6、模板形參表中,class和typename具有相同的含義,可以互換,使用typename更加直觀。但關鍵字typename是作為C++標準加入到C++中的,舊的編譯器可能不支持。
?
?????? 模板函數重載:
?
注:
1、函數的所有重載版本的聲明都應該位于該函數被調用位置之前
2、一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化為這個非模板函數。
3、對于非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調動非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數,那么將選擇模板。
4、顯式指定一個空的模板實參列表,該語法告訴編譯器只有模板才能來匹配這個調用,而且所有的模板參數都應該根據實參演繹出來。
5、模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換。
?
新的問題引入:
string s1 = "addfhgj";
??? string s2 = "addfghjkl";
Max(s1,s2); //未能從“const std::string”為“const std::move_iterator<_RanIt> &”推導 模板 參數。
故模板有一些特殊的情況不能處理,就需要引入模板的特化,什么是模板的特化?
?
?????? 模板函數特化:
?
有時候并不總是能夠寫出對所有可能被實例化的類型都最合適的模板,在某些情況下,通用模板定義對于某個類型可能是完全錯誤的,或者不能編譯,或者做一些錯誤的事情。
?
Eg:比較兩個字符串的大小
?????? 調試之后發現比較的是字符串地址的大小而不是字符串的大小。因此需要對模板函數進行特化以處理特殊的情況。這就需要對模板類的特殊情況進行處理-------模板特化:
注意:
在模板特化版本的調用中,實參類型必須與特化版本函數的形參類型完全匹配,如果不匹配,編譯器將為實參模板定義中實例化一個實例。
特化不能出現在模板實例的調用之后,應該在頭文件中包含模板特化的聲明,然后使用該特化版本的每個源文件包含該頭文件。
?
三、???模板類
1、????? 模板類的定義格式:
?
template<class 形參名1, class 形參名2, ...class 形參名n>
class 類名
{ ... };
?
// 使用模板方式實現動態順序表
template<typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
?
?
template <typename T>
SeqList <T>:: SeqList()
: _size(0)
, _capacity(10)
, _data(new T[ _capacity])
{}
template <typename T>
SeqList <T>::~ SeqList()
{
delete [] _data ;
}
?
void Test()
{
???????? SeqList<int>s1;
???????? SeqList<double>s2;
???????? SeqList<char>s3;
}
?
?
?
2、?? 模板類的實例化
只要有一種不同的類型,編譯器就會實例化出一個對應的類。
SeqList<int > sl1;
SeqList<double > sl2;
當定義上述兩種類型的順序表時,編譯器會使用int和double分別代替模板形參,重新編寫SeqList類,最后創建名為SeqList<int>和SeqList<double>的類。
?
(1)模板參數實現容器適配器。
#include "List.h"
//底層使用List 容器
?
template <class T, class container = list<T>> //模板參數
class Queue
{
public:
? void push(const T& x)
? {
???? return _con.PushBack(x);
? }
? void pop()
? {
???? return _con.PopFront();
? }
? const T& GetHead()
? {
???? return _con.Front();
? }
? const T& GetTail()
? {
???? return _con.Back();
? }
? bool IsEmpty()
? {
???? return _con.Empty();
? }
private:
? container _con;
};
void Test2()
{
? Queue<int> q1;//使用缺省的模板參數構造對象
? Queue<int, List<int>> q2;//使用模板參數構造對象
}
(2)模板的模板參數實現容器適配器。
??? template <class T, template<class>? class container = List> //使用模板的模板參數
class Queue
{
public:
? void push(const T& x)
? {
???? return _con.PushBack(x);
? }
? void pop()
? {
???? return _con.Pop();
? }
? const T& GetHead()
? {
???? return _con.Front();
? }
? const T& GetTail()
? {
???? return _con.Back();
? }
? bool IsEmpty()
? {
???? return _con.Empty();
? }
private:
? container<T>? _con;
};
void Test1()
{
Queue<int>q1;//使用缺省的模板類的模板參數構造對象
? Queue<int,List> q2;//使用模板的模板參數構造不同類型對象
}
?
(3)非類型的類模板參數
template <typename T, size_t MAX_SIZE = 10>//帶缺省模板參數
//template<typename T, double MAX_SIZE = 10.0> // “double”: 非類型模板參數“MAX_SIZE”的類型非法
?
class Array
{
public :
? Array();
private :
? T _array [MAX_SIZE];
int _size ;
};
template <typename T, size_t MAX_SIZE>
Array <T,MAX_SIZE>::Array()
? : _size(0)
? {}
?
void Test()
{
? Array<int> a1;
? Array<int , 20> a2;
}
(4)類模板的特化:
//順序表類的部分實現
template <typename T>
class SeqList
{
public :
??? SeqList();
??? ~ SeqList();
private :
??? int _size ;
??? int _capacity ;
??? T* _data ;
};
template<typename T>
SeqList <T>:: SeqList()
??? : _size(0)
??? , _capacity(10)
??? , _data(new T[ _capacity])
??? {
??????? cout<<"SeqList<T>:: SeqList()" <<endl;
??? }
template<typename T>
SeqList <T>::~ SeqList()
{
??? delete[] _data ;
}
?
//全特化:
template <>
class SeqList <int>
{
public :
??? SeqList(int capacity);
??? ~ SeqList();
private :
??? int _size ;
??? int _capacity ;
??? int* _data ;
};
// 特化后定義成員函數、成員函數不再需要模板形參列表
SeqList <int>:: SeqList(int capacity)
??? : _size(0)
??? , _capacity(capacity )
??? , _data(new int[ _capacity])
??? {
??????? cout<<"SeqList<int>" <<endl;
??? }
SeqList <int>::~ SeqList()
{
??? delete[] _data ;
}
void test1 ()
{
??? SeqList<double > sl2;
??? SeqList<int > sl1(2);
}
?
//偏特化(部分特化)
?
template <typename T1, typename T2>
class Data
{
public :
??? Data();
};
template <typename T1, typename T2>
Data<T1 , T2>::Data()
{
??? cout<<"Data<T1,T2>"<<endl;
}
// 局部特化第二個參數為某個具體的類型,如int 、 double 等
template <typename T1>
class Data <T1, int>
{
public :
??? Data();
?
};
template <typename T1>
Data<T1 , int>::Data()
{
cout<<"Data<T1,int>"<<endl;
}
?
下面的例子可以看出,偏特化并不僅僅是指特化部分參數,而是針對模板參數更進一步的條件限制所設計出來的一個特化版本。
?
// 局部特化兩個參數為指針類型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public :
??? Data();
?
};
template <typename T1, typename T2>
Data<T1 *, T2*>:: Data()
{
??? cout<<"Data<T1*,T2*>"<<endl;
}
// 局部特化兩個參數為引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public :
??? Data(const T1& d1, const T2& d2);
?
};
template <typename T1, typename T2>
Data<T1 &, T2&>:: Data(const T1& d1, const T2& d2)
??? {
?????? cout<<"Data<T1&,T2&>"<<endl;
??? }
void Test()
{
??? Data<double , int> d1;
??? Data<int , double> d2;
??? Data<int *, int*> d3;
??? Data<int&, int&> d4(1, 2);
}
?
總結
以上是生活随笔為你收集整理的C++模板剖析:函数模板、类模板解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图像去雾算法学习
- 下一篇: C++多态相关关问题及虚表剖析