模板
一般我们在实现一个函数的时候,都会使用模板,因为如果将类型写死,下次再使用的时候就要新写一个函数,尽管重载可以让名字方便,但每重载都要自己去写一个函数,这样非常麻烦,所以模板就是让编译器自己去推导,来生成对应的函数,从而实现泛型编程。
重载的缺点:
1、代码复用率低,只要有新类型出现,就得重新写函数。
2、安全性问题,只要一个重载函数出现问题,所有的重载函数都出错。
泛型编程:编写与类型无关的通用代码,是代码复用的手段,模板是泛型编程的基础。
函数模板
格式
template<class T>
bool Compare(T& t1,T& t2)
{
return t1 < t2;
}
实例化
隐式实例化:
由编译器自己推导的称为隐式实例化,上面函数如果传入 <int,double> 会在编译期间就报错,因为编译器无法确定T是int还是double,可以显示强转或显式实例化。
int a = 10;
double b = 20;
//编译失败
Compare(a,b);
显式实例化:
如果类型不匹配,编译器会自动对不匹配的类型进行隐式类型转换,如果不行就报错。
int a = 10;
double b = 20;
//显式实例化
Compare<int>(a,b);
类模板
格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
ps:类外定义的函数需要加上类模板参数列表:
template <class T>
Vector<T>::func()
{}
实例化
类模板实例化需要指定类型,比如
vector<int> v;
其中 vector 不是
类型, vector 才是类型。
仿函数
正如其名,仿函数就是用类来模仿一个函数,实例如下:
template<class T>
class Compare
{
public:
bool operator() (const T& t1,const T& t2)
{
return t1 < t2;
}
};
非类型模板参数
类型形参:
跟在class/typename后面的参数,int、double
非类型形参:
可以是具体的一个变量,但是不能是浮点数、字符、字符串等…
实例:
template<class T, size_t N = 100>
class test
{
};
模板特化
函数模板特化
为什么需要模板特化:
上面的比较函数在基本类型的比较中是没有问题的,但如果我们比较两个指针的内容显然是不合适的,我们又想要能够比较两个指针的内容,我们就可以选择特化:
示例:
template<class T>
bool Compare(T t1, T t2)
{
return t1 < t2;
}
template<>
bool Compare<int*>(int* t1, int* t2)
{
return *t1 < *t2;
}
但是注意:必须要和模板函数的参数列表完全相同:
template<class T>
bool Compare(T& t1, T& t2)
{
return t1 < t2;
}
template<>
bool Compare<int*&>(int*& t1, int*& t2)
{
return *t1 < *t2;
}
类模板特化
全特化
template<class T1,class T2>
class Data
{
};
template<>
class Data<int, char>
{
};
偏特化
//偏特化
template<class T1>
class Data<T1, char>
{
};
//进一步限制
template<class T1>
class Data<T1*, char>
{
};
模板分离编译
模板分离编译为什么会有问题:
先来回顾一下程序编译的过程:
解释:编译阶段拿到函数声明,可以顺利通过编译,但无法通过链接,因为链接时找不到函数地址(函数没有实例化)。