当我们希望可以用同一个函数处理不同类型的参数时(比如写一个加法函数,可以处理各种不同类型的数据)都有哪些方法呢?
1、函数重载(同一作用域;函数名相同;参数列表不同)
缺点:
a、只要有新类型出现,就必须添加对应的函数
b、除了类型外,所有的函数体都相同,代码的复用率太低
c、如果只是返回类型不同,函数重载不能实现
d、一个方法有问题,所有的方法都有问题,不好维护
2、使用公共基类,将所有通用的代码放在公共的基类里面(通过继承的方式将其复用)
缺点:
a、借助公共基类来编写通用代码将失去类型检查的优点
b、对于以后实现的许多类,都必须继承自某个特定的基类,代码维护更贱困难
3、使用特殊的预处理程序(使用宏)
缺点:
a、不是函数,不会进行类型检测,安全性不高
b、不能调试
c、经常会有副作用(除非将每一项都打上括号)
d、直接替换的方式,如果多次使用会使代码变得越来越长
接下来要介绍的另外一种新的方法--------泛型编程
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。模板相当于一个蓝图,它本身不是类或函数,编译器用模板产生指定的类或函数的特定版本类型,产生模板特定类型的过程称为函数模板的实例化。
模板分为函数模板和类模板。
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
格式:
模板形参名在同一模板形参列表中只能使用一次。所有模板前面必须加上class或者typename.
举个例子
#include
using namespace std;
template
T Add(T left, T right)
{
return left + right;
}
int main()
{
cout << Add(1, 2)<(1, 2.2)<
上边这个例子,当不将主函数中的第三行代码注释的话是会因为两个实参的类型不同而报错,遇到这种情况,我们可以用强制类型转化或显示实例化的方式解决。
注释前
将其注释后通过编译,没有任何错误。
注意,模板被编译了两次,一次T为int ,一次T为double。通过函数调用给出的实参来确实能够模板形参 T 的类型和值的过程称为模板的实参推演。
类型形参转换
一般不会将实参转换为已有的实例化的类型,(比如不会为了匹配已经实例化的函数而将上述例子中主函数的第二行代码中的double类型的实参转换为 int),相反的,会产生新的实例。
编译器只会执行两种转换:
1、const转换:接收const引用或const指针的函数可以分别用非const对象的引用或指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将会被当做指向第一个元素的指针,函数实参当做指向函数类型的指针。
模板参数有两种类型:类型形参和非类型形参。
类型形参:
上述例子的模板就是类型形参。类型形参名字只能在模板形参列表后到模板声明或定义的末尾之间使用,且遵循名字屏蔽规则。
非模板类型参数
非模板类型参数是在模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数
例如数组长度
说明:
1、模板函数也可以重载
2、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板可以被实例化为该非模板函数
3、对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用该非模板函数,而不是从模板函数产生出一个模板。如果函数模板可以产生出一个更加匹配的函数,则选择函数模板
4、显示指定一个空的模板实参列表,该语法告诉编译器只有模板才能匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
5、模板函数不允许自动类型转换,普通函数可以进行自动类型转换
模板函数特化
有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情。
举个例子:
#include
using namespace std;
template
int Compare(T t1, T t2)
{
if (t1 < t2)
return -1;
else
return 1;
}
int main()
{
char *str1 = "bcde";
char *str2 = "abcd";
int ret = Compare(str1, str2);
return 0;
}
正常情况下结果为1,看一看运行结果
模板函数特化的形式:
1、关键自template后边接一对尖括号 < >
2、函数名后边接模板名和一对尖括号,尖括号中指定这个模板特化定义的模板形参
3、函数形参表
4、函数体
#include
using namespace std;
template
int Compare(T t1, T t2)
{
if (t1 < t2)
return -1;
else
return 1;
}
template<>
int Compare(const char* p1, const char* p2)
{
return strcmp(p1,p2);
}
int main()
{
char *str1 = "bcde";
char *str2 = "abcd";
int ret1 = Compare(str1, str2);
const char *pstr1 = "bcde";
const char *pstr2 = "abcd";
int ret2 = Compare(pstr1, pstr2);
return 0;
}
由上图可以看到,在函数特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将将为实参版本定义中实例化一个实例而不会调用特化的版本。
模板类
模板类也是模板,必须以关键字template开头,后接模板形参表。
模板类格式:
#include
using namespace std;
template
class SeqList
{
T* _data;
int _size;
int _capacity;
};
int main()
{
SeqList s1;
SeqList s2;
return 0;
}
注意:类名 SeqList 是模板类的类名,它不是一个真正地类,只是一个类模板,而类名SeqList不是一个完整的类类型。在类名后边加一对尖括号,尖括号里边是将要实例化的模板形参的实际类型,比如SeqList < int >
还需注意的是在模板参数T同样是在模板参数列表给出之后到声明或定义的末尾有效,其他地方要使用模板参数T,需要在使用之前用 template < T >声明。
PS:欢迎提出各种意见哦~