92.当程序中有函数重载时,函数的匹配原则和顺序是什么?

发布时间 2023-07-11 15:37:50作者: CodeMagicianT

92.当程序中有函数重载时,函数的匹配原则和顺序是什么?

多数情况下,我们可以很容易的判断出该会调用哪一个重载函数,例如,调用的重载函数之间形参数量不同,形参的类型有明显的区别等。但是,当几个重载函数形参数量相等、具有默认形参以及形参又可以发生类型转换时,判断会调用哪个重载函数就显得不那么明了。了解重载函数的调用规则有助于我们的判断。

函数匹配可以划分为三个步骤,分别为:选定候选函数,选定可行函数,寻找最佳匹配。以下面的一组重载函数为例:

void f();
void f(int);
void f(int,int);
void f(double,double = 3.14);
f(3.14);  //调用 void f(double,double = 3.14) 
f(10,20);  //调用 void f(int,int)
f(10,3.14);  //错误,二义性调用

1.候选函数

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

f(3.14);  //具有4个候选函数
f(10 , 20);  //具有4个候选函数
f(10 , 3.14);  //具有4个候选函数

2.可行函数

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

f(3.14);  //具有2个可行函数,分别为 void f(int) 和 void f(double,double = 3.14)
f(10 , 20);  //具有2个可行函数,分别为 void f(int,int) 和 void f(double,double = 3.14)
f(10 , 3.14);  //具有2个可行函数,分别为 void f(int,int) 和 void f(double,double = 3.14)

函数匹配要想成功,必须具有可行函数,但具有可行函数,函数匹配却不一定能成功,产生该问题的原因就是下面将要说明的最佳匹配中的二义性调用问题。

同时,需要注意,默认形参对可行函数判断的影响,默认形参使得和实参数量上的匹配更加灵活。

3.最佳匹配

函数匹配的第三步是从可行函数中选择与本次调用最匹配的函数。在这一过程,逐一检查函数调用提供的实参,寻找形参类型与实参类型最匹配的那个可行函数。为了确定最佳匹配,编译器将实参类型到形参类型的转化划分成几个等级(从 1 到 5 匹配度逐步降低),具体排序如下所示:

1.精确匹配,包括以下情况(它们具有相同且最高的匹配度):

●实参类型和形参类型相同。
●实参从数组类型或函数类型转换成对应的指针类型
●向实参添加顶层const或者从实参中删除顶层const(形参的顶层cosnt是可以直接忽略掉的)

2.通过const转化实现匹配(底层cosnt的转换)

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

4.通过算术类型转换实现的匹配

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

f(3.14);    //有且只有一个精确匹配的重载函数,其它转化后的匹配方式都次之,所以最佳匹配为 f(double,double = 3.14)
f(10,20);    //有且只有一个精确匹配的重载函数,其它转化后的匹配方式都次之,所以最佳匹配为 f(int,int)
f(10,3.14);    //可以通过算术类型转化实现匹配,但匹配度相同的重载函数有两个,分别为 void f(int,int) 和 void f(double,double = 3.14),二义性调用,没有最佳匹配

只要不存在二义性问题,且具有可行函数,那么一定可以寻找到最佳匹配,函数匹配就能成功,反之,函数匹配成功也一定具有最佳匹配。

让我们来看些更细节的例子,来进一步理解最佳匹配

1.要进行转换的实参数量,会影响匹配度

#include<iostream>
using namespace std;
 
void f2(int, int)
{
    cout << "1" << endl;
}
 
void f2(int, double)
{
    cout << "2" << endl;
}
 
int main()
{
    f2(3.14, 3.14);    //调用第一个重载函数的话,两个实参都需要进行算术类型转换,而掉用第二个重载函数的话只需要对一个实参进行算术类型转换。
                       //第二个的匹配度更高,故调用第二个重载函数。
    return 0;
}

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

#include<iostream>
using namespace std;
 
void f3(int)
{
    cout << "1" << endl;
}
 
void f3(double)
{
    cout << "2" << endl;
}
 
int main()
{
    short val = 10;
    f3(val);        
    //调用第一个重载函数需要进行类型提升,调用第二个重载函数需要进行算术类型转换(实际上是先类型提升,再算术类型转换)
    //第一个重载函数有更高的匹配度,故调用第一个重载函数。
 
    return 0;
}

3.底层const对最佳匹配的影响

#include<iostream>
using namespace std;
 
void f4(int*)
{
    cout << "1" << endl;
}
 
void f4(const int*)
{
    cout << "2" << endl;
}
 
int main()
{
    int val2 = 10;
    const int val3 = 10;
    f4(&val2);        //可以和 void f4(int*) 精确匹配,也可以通过cosnt转换和 void f4(const int*)实现匹配,精确匹配更优,故调用第一个重载函数
    f4(&val3);        //可以和 void f4(const int*) 精确匹配,有且只有这一个重载函数可匹配,故调用第二个重载函数
 
    return 0;
}

参考资料来源:

[榛栗栗栗子](