在面向对象中,我们可以使用重载来实现多态。
但是问题在于,重载的函数仅仅是类型不同,代码复用率比较低,只要有新的类型出现时,就要增加对应的函数;另一方面它的代码可维护性比较低,一个出错可能所有的重载均出错。
那么在C++中,我们可以使用模板来尽量避免这些问题。
一、函数模板
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板格式如下:
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
其中,typename是用来定义模板参数关键字,也可以使用class。
要注意模板和函数之间是不需要任何符号的,并且它们可以在同一行。
下面的代码就是使用函数模板的一个实例:
template <typename T>
void Swap(T& left,T& right)
{
T temp = left;
left = right;
right = temp;
}
函数模板本身不是一个函数,而是“蓝图”,是编译器用使用方式产生特定具体类型函数的模具。
模板就是将本来应该我们做的重复的事情交给了编译器。
当使用了模板时,在编译器编译阶段,编译器需要根据传入的实参类型来推演生成对应的函数,然后供调用。
假如我们用double类型使用函数模板时,编译器通过对实参类型的推演,确定T为double,然后生成专门处理double类型参数的函数:
可以看到,类型仍然为double,变量的值也被成功交换。
实例化函数模板的方式有两种,一种是隐式实例化,另一种是显式实例化。
隐式实例化,即让编译器根据实参推演推演模板参数的实际类型,例如:
从监视1窗口可以看到,得到了正确的结果。
那么,根据刚才的Add函数模板及变量,能否这样隐式实例化呢?
Add(num0,num2);
根据编译器警告信息,可以明确是不可行的:
要处理这种状况,方法有两种,一种是用户自己强制类型转换:
Add((double)num0, num2);
Add(num0, (int)num2);
还有一种方法就是,显式实例化。
只需要在函数名后的<>中指定模板参数的实际类型即可显式实例化:
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
模板参数的匹配规则如下:
1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数;
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板;
3.模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
例如,func同时是函数,也是模板。现在在main函数中用不同的类型对其实例化,根据打印输出结果可知,当参数为int时,调用的是函数,当参数不为int时,调用的是根据模板生成的函数:
二、类模板
类模板是编译器根据被实例化的类型生成具体类的模具。
在STL中,如vector,随处可见类模板的身影:
类模板的定义格式如下:
template <class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>
中即可。
类模板名字不是真正的类,实例化的结果才是真正的类。
一个类模板实例化的具体例子: