C++函数模板的模板参数类型转换技术
2014-11-06何远强李全艳彭海平龚红仿
何远强++李全艳++彭海平++龚红仿
摘 要:模板是C++为用户提供的一种功能非常强大的、能方便的实现类属编程的工具。该文讨论了利用重载函数模板的方法实现模板参数类型的显式转换机制,并阐明了在实际应用中必须注意的问题。
关键词:C++ 函数模板 重载函数 模板参数 类型转换
中图分类号:TP312 文献标识码:A 文章编号:1674-098X(2014)04(a)-0019-02
模板是C++为用户提供的一种功能非常强大的、能方便的实现类属编程的工具,它允许用户构造类属函数——模板函数。但在实际编程中,我们并不是直接定义模板函数,而是定义一类函数的抽象——函数模板,它以任意的数据类型T为参数及函数返回值,用将T实例化的模板参数再实例化函数模板,所得的函数就是模板函数。因而,函数模板并不是一个完全的函数,而是代表一类函数,只有用模板参数实例化后成为模板函数,才能完成具体的函数功能。
1 问题的提出
在模板函数实例化为函数模板时,尽管模板参数T可以实例化成各种类型,但是采用模板参数T的各参数之间必须保持完全一致的类型。我们先看下面的例子:
template
T max(T x,T y)
{ return (x>y)?x:y;
}
void main()
{ int i=10;
char c=a;
float f=4.374;
cout< cout< cout< cout< cout< cout< cout< } 在上面的例子中,定义的函数模板是比较两个数的大小,数据类型既可以是系统预定义类型,也可以是用户自定义类型。这里,由函数模板生成了三个模板函数,分别是用模板实参int、char、float将类型参数T实例化而得。 我们可以看出,只有当参数类型完全一致时,得到的模板函数才是正确的。当参数类型不一致时,例如 max(i,c),系统将提醒我们找不到与max(int,char)相匹配的函数定义。然而,在C++中,int类型和char类型之间、flot类型与int类型之间、float类型与double类型之间等,都可以隐式转换,而且这种转换是非常普遍的。因而,完全可以把函数max(int,char)认为是函数max(int,int)。但是,模板类型没有这种识别能力,不具有隐式类型转换的功能。 在实际编程的过程中,经常会使用函数模板解决功能相同的一类问题,既可以大大减少程序代码、避免重复的定义,又可以增强程序的清晰性和可读性,这就涉及到如何实现模板参数类型转换机制的问题。 2 重载模板函数 解决这个问题的方法是允许函数模板参与重载,即可以用非模板函数重载一个同名的函数模板,有两种表述方式: 其一,利用函数模板的函数体。非模板函数对函数模板的重载定义是通过借用函数模板的函数体。需要定义重载时,只需声明,不用给出函数体,当执行此重载版本时会自动调用函数模板的函数体。例如,在上例中,可以作如下声明: int max(int,int); 这样就完成了重载声明,此重载函数虽然借用了函数模板的函数体,但它支持数据类型间的隐式转换。经过这样和重载定义后,使得max(int,char), max(char,int), max(float,int), max(int,float), max(double,int), max(double,float)等一系列函数变成为合理的和正确的调用。 其二,重新定义函数体。对于要重新定义函数体的重载函数,所带参数的类型可以随意,就像一般的重载函数一样定义。例如,在上例中,比较两个字符串的大小,可以重栽定义如下: char *max(char *x,char *y) {return(strcmp(x,y)>0)?x:y;} 但是,在一个实际的函数调用时,它既可以和一个重载函数相匹配或是参数隐式转换后与某一重载函数相匹配,又可以与某一模板函数相匹配。究竟调用哪一个函数,需按照一定的规则安排先后次序。这些规则就是函数模板与同名的非模板函数的重载在调用时均需遵循的约定: (1)首先寻找一个参数完全匹配的函数,如果找到了就调用它。 (2)在(1)失败后,寻找一个函数模板,使其实例化,产生一个匹配的模板函数,若找到了,就调用它。 (3)在上面均失败后,再试一试低一级的对函数的重载方法,即通过类型转换可产生参数匹配,若找到了,就调用它。 (4)若以上均失败,则得出是一个错误调用的判定。 我们看一个重载函数模板的例子,根据上面的规则来判断各函数的调用情况: #include #include template T max(T x,T y) { cout<<"This is a template function! The max is: "; return (x>y)?x:y;
}
class point //定义类类型
{ float x,y;
public:
point(float x=0,float y=0);
float getx(){return x;}
float gety(){return y;}
float point_sqrt();
friend int operator>(point px,point py); //用友元重载”>”运算符
}
//point类成员函数定义
point::point(float x,float y)
{ point::x=x;
point::y=y;
}
float point::point_sqrt()
{ return sqrt(x*x+y*y);}
int operator>(point px,point py)
{ if (px.point_sqrt()>py.point_sqrt()) return 1;
else return 0;
}
//重载函数定义
int max(int x,int y) //重新定义函数体
{ cout<<"This is the overload function with int,int! The max is: ";
return (x>y)?x:y;
}
char max(int x,char y) //重新定义函数体
{cout<<"This is the overload function with int,char! The max is:";
return (x>y)?x:y;
}
void func(int i,char c,float f)
{cout< cout< cout< cout< cout< cout< } void main() { int i; char c; float f; …… //输入i,c,f func(i,c,f); //调用func函数实现模板参数类型的转换 point pa(2,5),pb(3,4),pc; pc=max(pa,pb); //调用模板函数用point类类型实例化 cout<<"("< cout< } 我们分析上面的程序,在主函数main()中,调用func(i,c,f)函数。在func(i,c,f)函数中调用max(i,i)时,由于两个参数均为整型,按照规则,首先查找完全匹配的函数进行调用,因此它调用的是int max(int,int)重载版本。 调用max(c,c)时,首先没有找到完全匹配的函数,因此对函数模板进行实例化,它应该调用函数模板的实例化版本:char max(char,char)。 调用max(i,c)时,首先,查找完全匹配的版本,它与char max(int,char)完全匹配,因此就调用这个重载函数版本。 调用max(c,i)时,既找不到完全匹配的函数版本,又找不到能与之匹配的模板函数,所以只好试第三步,看对参数类型转换后能否有匹配的,将max(c,i)中的c转换成int型后与int max(int,int)匹配,因此就执行这个重载函数版本。 调用max(f,f)时,找不到与它完全匹配的函数,那么,函数模板实例化后的版本中的模板函数float max(float,float)可与它匹配,因此它就调用模板函数。 调用max(f,i)时,既找不到完全匹配的函数,又找不到合适的模板函数,只好对其参数进行类型转换,其中f转换为int类型后,与int max(int,int)相匹配,因此就调用这个重载函数版本。 在主函数main()中,调用max(pa,pb)函数,由于两个参数均为用户自定义point类类型,找不到与它完全匹配的函数,那么,函数模板实例化后的版本中的模板函数point max(point,point)可与它匹配,因此它就调用模板函数。 在point类类型定义中,定义两点分别到原点(0,0)的距离,作为两点比较大小的依据,如某点离原点越远,则认为该点越大。point类的成员函数point_sqrt()求点到原点的距离,友员函数int operator>(point px,point py)用来重载”>”运算符,判断point类对象的大小。 3 需要注意的问题 其一,利用模板函数的函数体重载定义非模板函数时,只需声明,不用给出函数体,且声明时必须注意各模板参数的实参类型必须一致。如:int max(int,int)等。 其二,重新定义重载函数体时,特别要注意避免产生预期的和非预期的二义性。例如,若对函数模板有这样两个重载函数: int max(int,int); //重载声明 char max(int x,char y) //重新定义 { …} 当进行函数调用时有这样一个调用形式:max(i,f); 此处i为int类型,f为float类型,系统无法决定该调用与这两个重载函数中的哪一个相联系,既可以将f转换成int类型后调用max(int,int),又可以将f转换成char类型后调用max(int,char)。这个函数调用就存在着二义性。 4 结语 该文讨论了利用重载函数模板的方法实现模板参数类型的显式转换机制,并说明了在实际应用中必须注意的问题。事实上,在实际编程中,情况可能更复杂些,如用户自定义结构类型、联合类型、类类型参数的转换等,均需视具体情况给出适当的辅助函数以实现模板参数类型的转换。 参考文献 [1] 王燕.面向对像的理论与C++实践[M].北京:清华大学出版社,2002. [2] 钱能.C++程序设计教程[M].北京:清华大学出版社,2009. [3] 郑阿奇.Visual c++教程[M].北京:机械工业出版社,2008. [4] [美]H.M.Deitel,P.J.Deitel著,C++大学教程[M].2版.邱仲潘,译.北京:电子工业出版社,2003.