泛型编程是编写和类型无关的逻辑代码,是代码复用的另一种手段。模板是实现泛型编程的一种技术。
以往要编写一个通用的函数有3种方法,函数重载,使用公共基类,宏函数。
然而函数重载需要对各种类型一一枚举,且一个错个个错。第二种继承公共基类并重写虚函数实现多态,失去了类型检查,对每一个子类维护也很麻烦。宏函数也没有类型检查。
实现Vector和List容器:https://github.com/zzaiyuyu/Vector-List
不管是类模板或是函数模板,都不是真正的类或函数,只是一个编译器用来生成代码的蓝图。
模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程。对于函数模板而言,模板实例化之后,会生成一个真正的函数。而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始化。模板的实例化分为隐式实例化和显示实例化。
隐式实例化:在发生模板函数调用或者模板类时,才进行实例化。
//隐式实例化函数模板
template <typename T>
T Max(const T& t1,const T& t2){
return (t1>t2)?t1:t2;
}
//隐式实例化类模板
template <typename T>
class A{
T num;
}
显示实例化:不管有无调用,只要显式声明,必定实例化出模板类或函数
//用template声明一下函数模板
template void func<int>(const int&);
//类模板
template class theclass<int>;
对函数模板的使用而言,分为两种调用方式,一种是显示模板实参调用(显示调用),一种是隐式模板实参调用(隐式调用)。对于类模板的使用而言,没有隐式模板实参和显式模板实参使用的说法,因为类模板的使用必须显示指明模板实参
template <typename T>
T Max(const T& t1,const T& t2){
return (t1>t2)?t1:t2;
}
int main(){
int i=5;
cout<'a')<//隐式调用,没有匹配的参数无法通过编译
cout<int>(i,'a')<//显示调用,强转int,通过编译
}
template<typename T, size_t N> //模板参数,其中T是类型参数,N是非类型参数
void PrintArray(const T (&array)[N]) {
for(size_t i = 0; i < N; ++i)
cout<<array[i]<<" ";
cout<int arr[10];
PrintArray(arr); //调用参数
<>里typename后跟类型参数,其他为非类型参数。非类型参数N会根据实参决定,作为常量在函数模板里使用。
模板参数的作用域在紧跟的函数模板里有效。
函数模板有全特化,类模板有全特化和偏特化。
有时候在一个泛型模板里并不能正确处理所有的类型,所以需要特化。
//函数模板
template<class T>
bool IsEqual(T t1,T t2){
return t1==t2;
}
template<> //函数模板特化
bool IsEqual(char *t1,char *t2){
return strcmp(t1,t2)==0;
}
另外函数重载也可以实现函数模板特化的功能
bool IsEqual(char *t1,char *t2){
return strcmp(t1,t2)==0;
}
如果使用普通重载函数,那么不管是否发生实际的函数调用,都会在目标文件中生成该函数的二进制代码。分离编译模式下,应该在各个源文件中包含重载函数的声明重载的函数。
全特化:模板中参数全部被固定,相当于定义一个全新的类
偏特化:模板参数没有全部固定,对部分模板参数进行指定限制
类模板全特化的应用场景——类型萃取
struct TrueType {
static bool Get()
{
return true;
}
};
struct FalseType {
static bool Get()
{
return false;
}
};
//如果没有特化,就是自定义类型
template<class T>
struct TypeTraits {
typedef FalseType PODTYPE;
};
template <>
struct TypeTraits<int> {
typedef TrueType PODTYPE;
};
template <>
struct TypeTraits<double> {
typedef TrueType PODTYPE;
};
//调用
//TypeTraits::PODTYPE::Get()
偏特化不仅是对部分参数的特化,同时可以是对参数的限制
常见的有将参数特化为绝对类型,指针类型
// 局部特化两个参数为指针类型
template <typename T1, typename T2>
class Data {
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
T1* _d3 ;
T2* _d4 ;
};
template <typename T1, typename T2>
Data:: Data() {
cout<<"Data" <
已有的类模板比如vector,list,deque对操纵数据的封装,称为容器。进一步想实现一些数据结构可以借助容器的接口快速实现。
templateT, class Container = SeqList<T> >
class{
...
private:
Container _con;
}
https://blog.csdn.net/pongba/article/details/19130
上文总结一下:
每个cpp文件都会先把包含的.h复制过来,然后编译成一个单独的obj目标文件。正常情况下一个cpp里找不到其他cpp文件里的函数,会在链接期间从其他obj文件的导出符号表里找到需要的函数,并把函数地址填到自己的调用函数处。
由于模板的特殊性,只有在同一个cpp文件里发现有调用才会实例化出真正的模板函数,当函数模板单独放在一个cpp里,编译器就不会为其实例化,在链接期间别人也不可能找得到没有实例化的函数。
解决方案:
hpp实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该hpp文件即可.**实现代码将直接编译到调用者的**obj文件中,不再生成单独的obj