闲谈:上一篇初识模板我也只是初略了解,这一次我在整理一下。
模板
模板是C++支持泛型程序设计的工具,通过它可以实现参数化多态性。
参数化多态性:就是将程序所处理的对象的类型参数化,使得一段程序可以处理多种不同类型的对象。
函数模板
与重载、宏定义对比所解决的缺陷
重载与函数模板
函数模板与函数重载看起来大同小异,但是重载的类不能传入与之相同类型的参数,从而使得要多建几个代码框架相同的函数,使程序代码冗余,可读性降低。
比如:
int max(int x,int y){…} float max(float x,float y){…}与之相似的还有宏定义:
宏定义只是在编译时进行简单的宏展开,避开了类型检查机制,容易产生隐藏错误。
#define max(x,y) ((x)>(y)?(x):(y)) max(a++, b++) //替换后:((a++)>(b++)?(a++):(b++))像这样,我们调用max宏定义替换时,a和b会产生多次自增,而我们需要的只是一次自增
函数模板
模板可以轻松地解决上述问题,减少隐式错误和减少代码冗余。
还是以max举个例子:
template <class T> T max(T x, T y){ return x > y ? x : y; }函数模板的使用
函数模板的格式:
template <class 形参名1,class 形参名2,......>
返回类型 函数名(参数列表) {
函数体
}
以上面举例的max模板为例:
关键字template后面的尖括号表明,max函数要用到一个叫做T的参数(我们称作模板参数),而这个参数是一种类型。
该模板的含义就是无论参数T为int、char或其他数据类型(包括类类型),函数max的语意都是对x和y求最大值。
这样定义的max代表了一类具有相同程序逻辑的函数,称为函数模板。
注意:使用函数模板时一定要实例化,。
原因:函数模板本身是不被编译器编译的,必须使用实例化给模板T绑定类型后才能使用。
完整使用:
template <class T> T max(T x, T y){ return x > y ? x : y; } //一下是传入模板后编译器扩展结果 //double max(double x, double y) //{ return x > y ? x : y; } int main( ){ double a = 1.0, b2,b1; b1 = max(a, 2.0);//这里是隐式实例化b b2 = max<double>(a, 2.0)//显示实例化 return 0; }直接使用max()传参识,函数模板接受了一个隐含的参数:double。
编译器自动将函数模板扩展成一个完整的关于double数据比较大小的函数,然后再在函数模板被调用的地方产生合适的函数调用代码。
显示调用就是我们手动转换了传参的类型。
这里有个问题了,既然要实例化才能使用模板,那这里我就需要传入类型不同的参数咋办啊?这里那就需要我们在模板里多写几条class T来分类传入类型了!
template <class T1, class T2> auto max(T1 x, T2 y) { return x > y ? x : y; } //一下是传入模板后编译器扩展结果 //传入res1: //double max(int x, double y) //{ return x > y ? x : y; } //传入res2: //double max(int x, float y) //{ return x > y ? x : y; } int main() { int a = 10; double b = 20.5; float c = 15.8f; auto res1 = max(a, b); cout << "int和double的最大值:" << res1 << endl; auto res2 = max(b, c); cout << "double和float的最大值:" << res2 << endl; return 0; }当然我还是拿之前的例子举例,这里返回值会被强制全部转换成double返回。但想传入不同类型参数,大抵就是这样使用了。
函数模板传入类类型的使用
模板除了简单的传入类型,我们也可以将自定义类型传入进去,比如这样,我定义有个学生类来比较:
class Student { public: string name; int score; Student(string n, int s) : name(n), score(s) {} }; template <class T> T max(T x, T y) { return x > y ? x : y; } bool operator>(const Student& a, const Student& b) { return a.score > b.score; } int main() { Student s1("张三", 80); Student s2("李四", 90); Student s_max = max(s1, s2); cout << "成绩更高的学生:" << s_max.name << ",成绩:" << s_max.score << endl; return 0; }当然了,这里的“>”肯定是比较不了我们自定义类型的,所以我们要对此进行一次运算符重载。
模板匹配策略
还有个问题,那就是如果出现了与函数模板相同的函数,那该怎么办?我怎么知道该调用谁?就比如下面这例子:
template <class T> T max(T x, T y){ return x > y ? x : y; } int max(int x, int y){ return x > y ? x : y; } //char max(char x, char y){ // return x > y ? x : y; // } int main( ){ int num=1; char ch=2; int num = 200; // 超过char范围(-128~127) long l = 123456789L; double d = 987654321.0; max(num,num); //调用max(int,int) max(ch,ch); //调用max(T,T),若char max没被注释优先调用char max max(num,ch); //调用max(int,int),char→int // max(num, 'a'); //调用max(char,char ),int→char。 //前提是没用int max(),否则会优先char转化成int。 max(l, d);//调用max(T,T),long→double return 0; }max模板和max类都满足条件,编译器该怎么判定?
那就来看看匹配规则吧。
在这里优先级从高到低分为四类:
1、完全匹配。
当普通函数和模板都完全匹配时,编译器优先选非模板的普通函数。
如:max(num,num)满足两个函数,那就优先调用普通函数int max()。
2、提升转换(char和short转换为int,及float转换为double)
完全匹配的优先级远高于需要转换,优先调用和实例化完全匹配的函数。
如:max(ch,ch),模板完全匹配可以直接用(完全匹配),而普通函数还要将ch转换为int类型(提升转换),那就优先调用完全匹配的函数模板。
max(num,ch),函数模板只能传入一个类型,这里有两个类型,不满足模板。只能将int隐式转换成char(优先满足提升转换的转换,没有才进行标准转换。)传入int max()。
3、标准转换(int转换为char,及long转换为double)
提升转换与标准转换同时出现,提升转换的优先级大于标准匹配,编译器优先调用满足提升匹配的函数。
如:被注释调的代码 max(num, 'a');,在这里我们将int max()注释掉并且解开char max的注释,函数模板只能传入一个类型,这里有两个类型,不满足模板。只能将int转化成char,完成标准转换。
但注意的是,max(l, d)可以完成long→double的转换从而使用模板函数。
写到这时我发现个这里有一个小问题,前面模板类型使用不是说了max可以隐式转化,这里max(num,ch)中的ch不应该也被转化成int形了吗,那不应该可以传入模板?但实际 max(num,ch)根本传不进去,那为啥 max(l, d)的long→double的转换可以传入模板?
以下为我总结个人理解:
这是因为int与char之间的类型转换有争议,他既可以提升转换也可以标准转换,于是编译器根本不做任何隐式转换,类型推导T不一致,直接失败。
而long与double之间,推导失败后编译器通过模板实参显式指定(隐式的)完成了匹配long→double的隐式转换,并且不存在double→long逆方向,认为逆方向是不合理的,除非我们手动显示转换,不然类型推导T只有long→double着一种情况。
同理,float→double也是一样,他们只有标准转换和提升转换一种种情况。即使我们写了float max和long max,编译器依然不会逆向转换,主动调用模板函数。
PS:这一段理解的我头要秃了,这只是个人理解,还请酌情参考,和指出我的错误>_<
4、用户定义的转换,如类声明中定义的转换。
用户自定义的比较复杂,这里我在重写一个代码:
template <class T> T max(T x, T y){ return x > y ? x : y; } int max(int x, int y){ return x > y ? x : y; } class MyClass { public: int val; MyClass(int v=0) : val(v) {} operator int() const { return val; } bool operator>(const MyClass& o) const { return val > o.val; } }; int main( ){ MyClass obj1(5), obj2(3); // 自定义类型对象 max(obj1, obj2);// 调用模板函数max(MyClass,MyClass) max(obj1, 10);// 调用普通int函数(obj1→int + 10(int),模板推导失败) return 0; }max(obj1, obj2)可以直接匹配模板(完全匹配),匹配int max需要转换类型。
max(obj1, 10);种,obj1和10的类型不同,匹配模板失败,只能转换obj1类型为int后匹配普通函数。
虽然obj1可以通过operator int() const { return val; } 重写成int型,但可惜模板推导阶段,编译器根本不会主动用operator int() 做转换,只 “机械” 推导实参的原始类型。