目录:多态之----
函数重载,函数模版重载,重载解析
术语“多态”指的是有多种形式,因此函数多态允许函数可以有多种形式。术语“函数重载”指的是可以有多个同名的函数,因此对名称进行了重载。这两个属于是同一回事,但我们通常使用函数重载。
比如:
Void print(const char *str, int width); //#1
Void print(double d, int width); //#2
Print(“Pancakes”, 15); //use #1
Print(1999.0, 10); //use #2
如果year是unsigned
Print(year, 6)则不与任何类型匹配,但其不会自动停止使用某个函数,因为C++将尝试使用标准类型转换强制进行匹配。如果#2是print()唯一的原型,则函数动用把year转换成double。
但是!!如果还有一个void print(long l, int width),这就有2种匹配方式(year->long, year->double, ),在这种情况下,C++将拒绝这种函数调用。
Double cube(double x);
Double cube(double &x);
在此处不可以使用函数重载。比如cout<<cube(x),参数与这两个都匹配,因此编译器无法辨别使用哪一个。
Void aa(char *bits); //overload
Void aa(const char *cbits); //overload
Void bb(char *bits); //not overloaded
Void cc(const char *bits); //not overloaded
Const char p1[20] = “asd”;
Char p2[20] = “qwe”;
aa(p1); //aa(const char *);
aa(p2); //aa(char *);
bb(p1); //not match
bb(p2); //bb(char *);
cc(p1); //cc(const char *);
cc(p2); //cc(const char *);
(非const 值赋给const 变量是合法的,反之是非法的)
何时使用重载:当函数基本执行相同任务,但使用不同形式的数据时,才应采用函数重载。
名称修饰:
C++如何跟踪每一个重载函数呢?它给这些函数制定了秘密身份。C++执行了一些神奇的操作-----名称修饰或名称矫正,它根据函数原型的形参类型对每个函数名进行加密。
Long MyFunctionFoo(int, float) -> ?MyFunctionFoo@@YAXH
对原始名称进行的表面看起来无意义的修饰将对参数数目和类型进行编码。添加的一组符号随函数特征而异,而修饰时使用的约定随编译器而异。
二:函数模版
1:基本知识:
函数模版是通用的函数描述,它们使用泛型来定义函数。通过将类型作为参数传递给模版,可使编译器生成该类型的函数。有时也称其为通用编程。
Template <tyname AnyType> //也可以 ,Template <class AnyType>
注意,函数模版不能缩短可执行程序。最终代码不包含任何模版,而只包含生成的实际函数。使用模版使生成多个函数更简单可靠。
2:重载模版:
Template <typename T>
Void my_swap(T &a, T &b);
Template <typenameT>
Void my_swap(T *a, T *b, int n);
模版的局限性:假定代码执行了a=b,如果T为数组,则假设不成立。
有一种是运算符重载,另一种则为具体化模版。
Struct job
Void print(const char *str, int width); //#1
{
Char name[40];
Double salary;
Int floor;
};
假设只希望交换salary和floor,则不能用 temp=a,a=b,b=temp来做,所以无法使用模版重载来提供其他代码,所以要提供一个称为显示具体化。编译器将使用该定义,而不再寻找模版。
//not template function prototype
Void swap(job &, job &);
//template prototype
Void swap(T &, T &);
//explicit specialization for the job type
Template <> void swap<job>(job &, job &);
Template <> void swap<job>(job &, job &)
{
Double u,v;
Swap(u,v);//use template
Job a,b;
Swap(a,b);//use void swap<job>(job &, job&)
}
Swap<job>中的job是可选的,因为函数的参数类型表明这是job的一个具体化,因此可以写成template <> void swap(job &, job &); //simpler form
3:实例化和具体化
在代码中包含函数模版本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模版为特定类型生成函数定义时,得到的是模版实例。这中实例化方式被成为隐式实例化,因为编译器之所以知道定义,是由于程序调用swap()函数提供了int参数。
最初只支持隐式,现在C++还允许显式实例化
如:template void swap<int>(int, int); //显示实例化,使用swap()模版生成int类型的函数定义。
显示具体化使用两个等价声明之一:
Template <> void swap<int>(int &, int &);
Template <> void swap(int &, int &); //意思是不要使用swap()模版来生成函数定义,而应使用专门为int类型显式地定义的函数定义
显示具体化声明在关键字template后有<>,显示实例化没有。
还可以通过在程序中使用函数来创建显示实例化。
Temlpate <class T>
T add(T a, T b)
{return a+b;}
...
Int m=6;double x=0.2;
Cout << add<double>(x,m)<<endl; //explicit instantiation
这里的模版与函数调用add(m,x)不匹配,因为该模版要求两个参数类型相同。但通过用add<double>(x,m),可强制为double类型实例化。但是对于swap(T &, T &),用于第一个形参的类型的double &,不能指向int变量m。
三:重载解析
对于函数重载,函数模版,函数模版重载,C++需要一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析。
第一步:创建候选函数列表。其中包含与被调用函数的名称相同的函数和模版函数。
第二步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数为此有一个隐式转换序列,其中包含参数类型与相应形参类型完全匹配情况。
第三步:确定是否有最佳的可行函数。有则使用,无则调用该函数出错。
May(‘B’);
Void may(int); //#1
Float may(float,float=3); //#2
Void may(char); //#3
Char *may(const char *); //#4
Char may(const char &); //#5
Template<class T> void may(const T &); //#6
Template<class T> void may(T *); //#7
只考虑特征标,不考虑返回类型。其中#4 #7不行,正数不能被隐式地转换为指针类型。 之后从最佳到最差顺序为:
1.完全匹配,但常规函数优于模版。
2.提升转换(char和short->int, float->double)。
3.标准转换(int->char, long->double)。
4.用户定义的转换,如类声明中定义的转换。
函数#1优于#2,因为char->int式提升转换,char->float式标准转换。
#3 #5 #6都优于#1 #2,因为它们都是完全匹配的。
#3 #5优与#6,因为#6式模版。
#3 #5两个完全匹配所以错误。Ambiguous(二义性)
Const和非const之间的区别只使用于指针和引用指向的数据。
非模版函数优先模版函数。都是模版则校具体的优先。
用于找出最具体的模版的规则被函数模版的部分排序规则,
自己选择:
T lesser(T a, T b)
{return a<b?a:b}
Int lesser(int a, int b)
{...}
提供一个模版和一个标准函数。函数调用与模版和非模版都匹配,因此选择非模版。
而这样调用lesser<>(m,n),<>指出编译器应选择模版函数,使用int代替T对模版进行实例化。
而lesser<int>(x,y)要求进行显示实例化,用int代替T,x,y被强制转换为int。