函数匹配

void f();
void f(int );
void f(int,int);
void f(double,double=3.14);
f(5.6);//调用void f(double,double)

确定候选函数和可行函数

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数。候选函数具备两个特征:一是与被调用的函数同名;二是其声明在调用点可见。在这个例子中,有4个名为f的候选函数。

第二步考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是美国实参的类型与对应的形参类型相同,或者能转换成形参的类型。

注意:如果函数有默认实参,则我们在调用该函数时传入的实参数量可能少于它实际使用的实参数量

如果没有找到可行函数,编译器将报告无匹配函数的错误。

寻找最佳匹配(如果有的话)

函数匹配的第三步是从可行函数中选择与本次调用最匹配的函数。在这一过程中,逐一检查函数调用提供的实参,寻找形参类型与实参类型最匹配的那个可行函数。

在我们的例子中,调用只提供了一个(显式的)实参,它的类型是double。如果调用f(int),实参将不得不从double转换成int。另一个可行函数f(double,double)则与实参精确匹配。精确匹配比需要类型转换的匹配更好,因此,编译器把f(5.6)解析成对含有两个double形参的函数的调用,并使用默认值填补我们未提供的第二个实参。

含有多个形参的函数匹配

当实参的数量有两个或更多时,函数匹配就比较复杂了。对于前面那些名为f的函数,我们分析如下的调用会发生什么情况:

(42,2.56);

选择可行函数的方法和只有一个实参时一样,编译器选择那些形参数量满足要求且实参类型和形参类型能够匹配的函数。此例中,可行函数包括f(int,int)和f(double,double)。接下来,编译器依次检查每个实参以确定哪个函数是最佳匹配。如果有且只有一个函数满足下列条件,则匹配成功:

  • 该函数每个实参的匹配都不劣于其他可行函数需要的匹配。
  • 至少有一个实参的匹配优于其他可行函数提供的匹配。

如果检查了所有实参之后没有任何一个函数脱颖而出,则该调用时错误的。编译器将报告二义性调用的信息。

在上面的调用这,只考虑第一个实参时我们发现函数f(int,int)能精确匹配:要想匹配第二个函数,int类型的实参必须转换成double类型。显然需要内置类型转换的匹配劣于精确匹配,因此仅就第一个实参来说,f(int,int)比f(double,double)更好。

接着考虑第二个实参,此时f(double,double)是精确匹配:要调用f(int,int)必须将2.56从double类型转换成int类型。因此仅第二个实参来说,f(double,double)更好。

编译器最终将因为这个调用具有二义性而拒绝其请求:因为每个可行函数各自在一个实参上实现了更好的匹配,从整体上无法判断孰优孰劣。看起来我们似乎可以通过强制类型转换其中一个实参来实现函数的匹配,但是在设计良好的系统中,不应该对实参进行强制类型转换。

实参类型转换

为了确定最佳匹配,编译器将实参类型到形参类型的转换划分为几个等级,具体排序如下所示:

1 精确匹配,包括以下几种:

  • 实参类型和形参类型相同
  • 实参从数组类型或函数类型转换成对应的指针类型
  • 向实参添加顶层const或者从实参中删除顶层const

2 通过const转换实现的匹配

3 通过类型提升实现的匹配

4 通过算术转换或指针转换实现的匹配

5 通过类类型转换实现的匹配

需要类型提升和算术类型转换的匹配

分析函数调用前,我们应该知道小整型一般都会提升到int类型或者更大的整数类型。假设有两个函数,一个接受int,另一个接受short,则只有当调用提供的是short类型的值时才会选择short版本的函数。有时候,即使实参时一个很小的整数值,也会直接将它提升成int类型:此时使用short版本反而会导致类型转换:

void ff(int);

void ff(short);

ff('a');   //char提升成int,调用ff(int)

所有算术类型转换的级别都一样。例如,从int到unsigned int的转换并不比从int到double的转换级别高。举个具体的例子:

void manip(long);

void manip(float);

manip(3.14);  //错误:二义性调用

字面值3.14的类型是double,它既能转换成龙也能转换成float。因为存在两种可能的算数类型转换,所有该调用具有二义性。

函数匹配和const实参(都是底层const)

如果重载函数的区别在于它们的引用类型的形参是否引用了const,或者指针类型的形参是否执行const,则当调用发生时编译器通过实参是否是常量来决定选择哪个函数。

Record lookup(Account &);  //函数的参数是Account的引用

Record lookup(const Account &);   //函数的参数是一个常量引用

const Account a;

Account b;

lookup(a);    //调用lookup(const Account&)

lookup(b);   //调用lookup(Account&)

在第一个调用中,我们传入的是const对象a。因为不能把普通引用绑定到const对象上,所以此例中唯一可行的函数是以常量引用作为形参的那个函数,并且调用该函数与实参a精确匹配。

在第二个调用这,我们传入的是非常量对象b。对于这个调用来说,两个函数都是可行的,因为我们既可以使用b初始化常量引用也可以用它初始化非常量引用。然而,用非常量对象初始化常量引用需要类型转换,接受非常量 形参的版本则与b精确匹配。因此,应该选用非常量版本的函数。

指针类型的形参也类似。如果两个函数的唯一区别是它的指针形参指向常量或非常量,则编译器能通过是否是常量决定选用哪个函数:如果实参时指向常量的指针,调用形参是const *的函数;如果实参是指向非常量的指针,调用形参是普通指针的函数。

原文地址:https://www.cnblogs.com/wuchanming/p/3897345.html