C++深入学习之彻底理清重载函数匹配

前言

前面我们讲到了《函数重载》,有了函数重载之后,就需要确定某次调用需要选用哪个函数。这个过程可以称之为函数匹配或者重载确定。大多数情况下,我们都很容易能够确定某次调用需要选用哪个函数,但事实上不尽然。但通过本文将彻底理清重载函数匹配

匹配过程

为便于说明,将函数匹配分为三个阶段,确定候选函数,确定可行函数,确定最佳匹配函数。

确定候选函数

候选函数也就是和被调用的函数同名,并且其声明在调用点可见。举个简单的例子。

假设有两个文件,1.cpp和2.cpp,内容分别如下:

1.cpp:

//函数1
 void f(int a,short b)
 {
 cout<<"func0"<<endl;
 }

2.cpp:

#include<iostream>
 using namespace std;
 //函数2
 void f(int a,double b)
 {
 cout<<"func1"<<endl;
 }
 //函数3
void f(int a, int b)
 {
 cout<< "func2"<<endl;
 }
 //函数4
 void f1()
 {
 cout<<"func3"<<endl;
 }
 int main()
 {
 f(3,4.5);
 return 0;
 }

在这里,候选函数其实只有两个,其中第一个函数在其调用点不可见,而第四个函数和被调用的函数不同名,因此这两个都不是候选函数。

确定可行函数

可行函数指的是本次调用传入的实参能够被候选函数使用。它要满足两个条件, 一是形参数量和实参数量相同,二是每个实参的类型和对应形参类型相同或者能够转换成形参的类型。

还是前面的例子,实参的个数和类型与第二个函数完全匹配,而在经过算术转换之后,也能够与第三个函数匹配。

确定最佳匹配函数

最佳匹配的函数是最终调用的。最佳匹配最基本的思想是认为,实参类型越接近,它们就越匹配。还是前面的例子,实参要与第三个函数匹配,需要进行算术转换,而与第二个函数完全匹配,因此第二个函数是最佳匹配函数。最终的运行结果如下:

func1

最佳匹配原则

一般来说,精确匹配肯定比需要类型转换的匹配要更好,但是当形参有多个,并且无法完全精确匹配的时候,要确定最佳匹配函数就有点困难了。

但是有下面的原则:

  • 函数的每个实参的匹配都不能比其他可行函数更差
  • 函数至少有一个实参的匹配要比其他可行函数更好

那么问题又来了,什么是更好,什么又是更差呢?编译器将实参类型到形参类型的转换划分了等级:

1.精确匹配,包括实参类型和形参类型相同,实参从数组或函数转换成对应的指针类型,向实参添加顶层const或从实参删除顶层const

2.通过const转换实现的匹配

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

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

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

等级越前,匹配也就越好。接下来对上面的内容做一些解释。

精确匹配

精确匹配比较容易理解。关于顶层const问题,可以参考《函数重载》

通过const转换实现的匹配

所谓通过const转换实现的匹配,指的是通过加const限定词,能够与可行函数精确匹配。例如:

#include <iostream>
using namespace std;
//函数1
/*
int f(string &a)
{
 cout<<"call function 1"<<endl;
 return 0;
}*/
//函数2
int f(const string &a)
{
 cout<<"call function 2"<<endl;
 return 0;
}
int main()
{
 string test = "test";
 f(test);
 return 0;
}

在这里,test可以通过const转换,从而匹配函数2,将能够找到最佳匹配函数2(当前情况它也只有一个可选了)。
运行结果如下:

call function 2

如果把函数1的注释去掉再运行,就会发现,虽然第一个调用既能匹配函数1,也能匹配函数2,但是由于匹配函数2的时候,需要const转换,因此比精确匹配要差,最终,它会调用函数1。
去掉函数1的注释后,运行结果如下:

call function 1

通过类型提升实现的匹配

关于类型提升,这里不多做介绍。简单说明类型提升规则:

  • float将提升到double
  • char、short和相应的signed、unsigned类型将提升到int

我们来看一个示例:

#include <iostream>
using namespace std;
//函数1
/*
int f(short a)
{
 cout<<"call function 1"<<endl;
 return 0;
}*/
//函数2
int f(int a)
{
 cout<<"call function 2"<<endl;
 return 0;
}
int main()
{
 short a = 2;
 f(a);
 return 0;
}

同样地,我们暂时把函数1注释掉。由于a是short类型,但是通过类型提升,可以转换为int,因为它也能调用函数2。运行结果如下:

call function 2

但去掉函数1注释后,由于精确匹配优于通过类型提升的匹配,因此将会调用函数1,运行结果如下:

call function 1

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

short int和float,double等之间的转换,都是算术类型之间的转换。我们仍然来看一个例子:

#include <iostream>
using namespace std;
//函数1
int f(int a)
{
 cout<<"call function 1"<<endl;
 return 0;
}
//函数2
int f(double a)
{
 cout<<"call function 2"<<endl;
 return 0;
}
int main()
{
 short a = 2;
 f(a);
 return 0;
}

在这里,short类型的a既可以通过类型提升转换为int,也可以通过算术类型转换成为double。这个时候,哪个才是最佳匹配呢?我们看运行结果:

call function 1

对于这个结果,并不意外,因为前面我们已经说到,通过类型提升的转换是优于算术转换的,因而函数1是它的最佳匹配函数。

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

这里不多做介绍。我们也很容易理解。诸如父类和子类之间的转换都是如此。

二义性示例

前面基本能够找到最佳匹配,我们来看一个有多个可行函数,最后却没有最佳匹配的情况。

#include<iostream>
using namespace std;
//函数1
void f(double a,int b)
{
 cout<<"function 1"<<endl;
}
//函数2
void f(int a,double b)
{
 cout<<"function 2"<<endl;
}
int main()
{
 f(1,1);
 return 0;
}

函数1和函数2都是可行函数,但它们都没有在任意一个参数上比对方更好,因此将会产生二义性,编译时将会报错:

error: call of overloaded ‘f(int, int)' is ambiguous

总结

  • 调用重载函数时,应当避免强制类型转换。
  • 设计重载函数时应避免可能产生的二义性。
  • 如果无法找到可行函数,编译器将报错。
  • 设计重载函数的时候,希望避免需要用到上面的知识,而在定位问题时能够利用上面的知识很快定位问题。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • C++小知识:复制粘贴代码千万要小心

    错误代码: sampleCount VoiceKey::OnBackward (....) { ... int atrend = sgn(buffer[samplesleft - 2]- buffer[samplesleft - 1]); int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]- buffer[samplesleft - WindowSizeInt-2]); ... } 解释: "buffer[samplesleft – Wi

  • C/C++语言printf命令使用方法

    在C语言以及其它语言中我们经常看到printf命令,那么怎么使用该命令输出想要的结果呢?下面就为大家演示. 1.printf()函数是一个格式化输出函数, 用户可以通过该命令向标准输出设备输出指定的格式信息.使用的方法是: printf("格式化字符串", 变量).在使用前需要注意的是该命令被定义在stdio.h头文件中(C语言),所以需要在程序中加入#include<stdio.h>.printf()函数是一个格式化输出函数, 用户可以通过该命令向标准输出设备输出指定的格

  • C++利用链表模板类实现简易队列

    本文实例为大家分享了C++利用链表模板类实现一个队列的具体代码,供大家参考,具体内容如下 设计思想:MyQueue.h中对模板类进行声明和实现.首先定义结点的结构体,包含数据和指针域两部分.队列类定义中声明和实现了元素入队,出队,打印队首元素和队列等方法. 注意: 1)模板类的声明和定义不能分开(即不能分别放在.h和.cpp文件里). 2)声明新节点时,如果声明的节点是辅助操作的,可以不用new关键字,例如在析构函数中,直接用:Node<T>* temp:定义即可.如果声明一个新节点加入队列,

  • C++小知识:C/C++中不要按值传递数组

    错误的代码: ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) { memcpy( mat, src, sizeof( src ) ); } 说明: 有时候程序员会忘记 C/C++ 里数组不能按值传递给函数.当你试图这样做时,是数组的指针(第一个元素的地址)而不是整个数组被传递.我们还应该记住,方括号中的数字没有任何意义.它们仅仅是程序员所做的标志,记录了传递数组的『假定』大小.事实上,你也可以传递一个大小完全不同的数组.例如,下面的代码就会成功编译

  • C++小知识:大于0并不意味着等于1

    注:本文所指出的错误例子其实非常简单,任何 C++ 的初学者都能看懂.但是这个错误也非常典型,估计很多非常严肃的代码里面都存在这样的错误. 这个 Bug 是来自于 CoreCLR 源代码. 错误代码: bool operator( )(const GUID& _Key1, const GUID& _Key2) const { return memcmp(&_Key1, &_Key2, sizeof(GUID)) == -1; } 解释: 你不能将 memcpy().strc

  • C++小知识:不要节约代码行数

    本篇案例来自 KDE4 源码. 错误代码: void LDAPProtocol::del( const KUrl &_url, bool ) { .... if ( (id = mOp.del( usrc.dn() ) == -1) ) { LDAPErr(); return; } ret = mOp.waitForResult( id, -1 ); .... } 说明: 一些程序员竭尽全力想把更多的代码压缩到一行.他们尤其热衷于"if"条件语句,把赋值和比较一次性完成. 一个

  • C++小知识:用合适的工具来分析你的代码

    静态代码分析工具可简化编码过程,检测出错误并帮助修复.有个国外团队检测了 200 多个 C/C++ 开源项目,包括了 Php.Qt 和 Linux 内核等知名项目.于是他们每天分享一个错误案例,并给出相应建议.今天的案例来自 LibreOffice 项目. 错误代码 BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { .... CreateThread( NULL, 0, ParentM

  • C++小知识:用++i替代i++

    静态代码分析工具可简化编码过程,检测出错误并帮助修复.PVS-Studio 是一个用于 C/C++ 的静态代码分析工具.该团队检测了 200 多个 C/C++ 开源项目,包括了 Unreal Engine.Php.Haiku.Qt 和 Linux 内核等知名项目.于是他们每天分享一个错误案例,并给出相应建议. 这个 bug 是在 Unreal Engine 4 的源代码中发现的. 错误代码: void FSlateNotificationManager::GetWindows( TArray<

  • C++基础学习之函数重载的简单介绍

    前言 我们在平时写代码中会用到几个函数但是他们的实现功能相同,但是有些细节却不同.例如:交换两个数的值其中包括(int, float,char,double)这些个类型.在C语言中我们是利用不同的函数名来加以区分. void Swap1(int* a, int* b); void Swap2(float* a, float* b); void Swap3(char* a, char* b); void Swap4(double* a, double* b); 我们可以看出这样的代码不美观而且给程

  • C++小知识:不要去做编译器的工作

    对于C++编程的老鸟来说,有时候他们喜欢把一些东西按照编译器的工作原理进行改写,以便提高代码的运行效率.这么做确实高明,也能体现出程序员的水平,但是这么做也是有风险的.因为有时候你可能会因为一些简单的笔误,而造成非常难以察觉的错误.本文就给出了类似的例子. 这个Bug 出现在MySQL源代码中. 错误代码: static int rr_cmp(uchar *a,uchar *b) { if (a[0] != b[0]) return (int) a[0] - (int) b[0]; if (a[

随机推荐