举个例子,我们之前写过一个函数模板比较大小:
//模板函数
template <typename T>
int compare(const T &v1,const T &v2)
{
if(v1<v2)
return -1;
if(v2<v1)
return 1;
return 0;
}
但是当调用函数的char型的数组时,这个函数就无可奈何了,因为这个类型并不提供比较大小的操作。相反,我们可以利用特化模板函数来解决这个问题:
//特化模板函数
template <>
int compare<const char*>(const char* const &v1,const char* const &v2)
{
return strcmp(v1,v2);
}
这里调用的c语言的strcmp函数完成比较。
特化模板函数与一般模板函数相比,省略了模板函数中的模板形参列表,而在函数后面增加了尖括号,括号中指明了特化的类型,然后接形参列表和函数体。
此时当我们调用:
const char *cp1 = "world",*cp2 = "hi";
cout<<compare(cp1,cp2)<<endl;//使用特化函数
cout<<compare(1,2)<<endl;//使用模板函数
有必要说明的是,在调用特化版本之前,必须在他之前声明,否则编译器就可能调用非特化的模板函数。
对应于函数,类也可以被特化:
//类模板
template <class Type> class Base
{
public:
Base(Type v):val(v){}
void set(const Type v){val = v;}
Type get(){return val;}
~Base(){};
private:
Type val;
};
//类模板的特化
template<> class Base<string>
{
public:
Base(string v):val(v){}
void set(const string v){val = v;}
string get(){return val;}
~Base(){};
private:
string val;
};
那么在调用时:
Base<int> ibase(1);//使用类模板
cout<<ibase.get()<<endl;
Base<string> sbase("aaa");//使用特化的类模板
cout<<sbase.get()<<endl;
在能使用特化的类模板时,编译器是不会使用普通类模板的。
更夸张的是,我们甚至可以特化一个类模板的成员:
template<>
double Base<double>:: get()
{
return val+1;
}
这是对类模板Base中get函数的特化,为了明确效果,我们特意给val+1来与原来的非特化get函数做了对比。当我们调用:
Base<double> dbase(1.1);
cout<<dbase.get()<<endl;
时,首先会采用非特化的Base类,当调用get函数时,则会使用特化了的get版本。
对于包含多个模板形参的模板类,我们可以特化它的一部分模板形参:
template <typename T1,typename T2> class twoTypes
{
public:
twoTypes(){cout<<" 普通版本"<<endl;}
};
template <typename T1> class twoTypes<T1,string>
{
public:
twoTypes(){cout<<" 特化版本"<<endl;}
};
这个类虽然没什么具体的东西,但是从构造函数中可以区别它们。
twoTypes<int,int> twotpi;//调用非特化模板
twoTypes<int,string> twotys;//调用特化模板
可以发现,即使普通模板能够使用,如果定义了特化版本,编译器总是会选择特化的版本。原因正如开头所说的,编译器倾向于认为我们专门定义的、特化的版本是为特定类型所写的更加精良的代码。
除了模板的特化,其实模板函数之间,模板函数与非模板函数也可以发生重载。回忆一下我们前面介绍的重载规则就能想像的到这些规则是多么的复杂和无趣。这里就不展开介绍了。与其了解这些复杂规则,不如尽量避免这些可怕的事情发生。我们倾向于使用模板的特化而不是重载非模板的版本。