Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >彻底理清重载函数匹配

彻底理清重载函数匹配

作者头像
编程珠玑
发布于 2019-09-02 14:30:47
发布于 2019-09-02 14:30:47
68400
代码可运行
举报
文章被收录于专栏:编程珠玑编程珠玑
运行总次数:0
代码可运行

前言

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

匹配过程

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

确定候选函数

候选函数也就是和被调用的函数同名,并且其声明在调用点可见。举个简单的例子。 假设有两个文件,1.cpp和2.cpp,内容分别如下: 1.cpp:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//函数1
 void f(int a,short b)
 {
     cout<<"func0"<<endl;
 }

2.cpp:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 #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;
 }

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

确定可行函数

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

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

确定最佳匹配函数

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 func1

最佳匹配原则

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

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

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

  • 1.精确匹配,包括实参类型和形参类型相同,实参从数组或函数转换成对应的指针类型,向实参添加顶层const或从实参删除顶层const
  • 2.通过const转换实现的匹配
  • 3.通过类型提升实现的匹配
  • 4.通过算数类型转换实现的匹配
  • 5.通过类类型转换实现的匹配

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

精确匹配

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

通过const转换实现的匹配

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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(当前情况它也只有一个可选了)。 运行结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
call function 2                                   

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
call function 1                                      
通过类型提升实现的匹配

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

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

我们来看一个示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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。运行结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
call function 2

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
call function 1
通过算术类型转换实现的匹配

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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。这个时候,哪个才是最佳匹配呢?我们看运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
call function 1

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

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

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

二义性示例

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 #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都是可行函数,但它们都没有在任意一个参数上比对方更好,因此将会产生二义性,编译时将会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
error: call of overloaded ‘f(int, int)’ is ambiguous

总结

  • 调用重载函数时,应当避免强制类型转换。
  • 设计重载函数时应避免可能产生的二义性。
  • 如果无法找到可行函数,编译器将报错。
  • 设计重载函数的时候,希望避免需要用到上面的知识,而在定位问题时能够利用上面的知识很快定位问题。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++ 函数重载】—— 现代编译技术下的多态表达与性能优化
如上代码,我们通过指针传参实现了一个交换两个int变量的Swap函数。 那么问题来了,如果我们需要交换两个float变量呢?我们需要交换两个char 变量呢?这个函数显然已经不适用了,我们需要实现新的函数来满足交换的需求!
换一颗红豆
2025/03/05
1750
【C++ 函数重载】—— 现代编译技术下的多态表达与性能优化
c++基础之函数
距离上次更新又过了一周,又该更新新的读书笔记了。本次更新的主要是c++中函数部分的内容
Masimaro
2021/03/11
5800
C++之函数重载
函数重载是:函数名相同,但是函数参数不同的函数之间的关系。函数重载只能通过函数参数的不同来实现,这包含参数个数不同和参数类型不同。 !!! 重载不是面向对象的特征。
zy010101
2020/04/08
7590
【笔记】《C++Primer》—— 第6章:函数
函数这一节内容又多又杂,但是相当有用,尤其是其中关于引用的应用和最后的调试部分。可能会比较长,等下一节写完就来做个小总结。
ZifengHuang
2020/07/29
7360
【笔记】《C++Primer》—— 第6章:函数
第 14 章 重载运算与类型转换
第 14 章 重载运算与类型转换 标签(空格分隔): C++Primer 学习记录 运算符重载 类型转换 ---- 第 14 章 重载运算与类型转换 14.1 基本概念 14.2 输入和输出运算符 14.3 算术和关系运算符 14.4 赋值运算符 14.5 下标运算符 14.6 递增和递减运算符 14.7 成员访问运算符 14.8 函数调用运算符 14.9 重载、类型转换与运算符 ---- 14.1 基本概念 重载的运算符是具有特殊名字的函数,他们的名字由关键字 operator和其后要定义的运算符号共
用户1653704
2018/06/07
9190
【笔记】《C++Primer》—— 第14章:重载运算和类型转换
这一章介绍了对运算符的重载和类型转换,其中最重要的是对各种运算符的运用,14.8对function类的运用和14.9对类型转换时可能产生的二义性的理解,其余的内容不多,这篇看起来很多节但其实只是因为内容比较散而已。
ZifengHuang
2020/07/29
6870
【笔记】《C++Primer》—— 第14章:重载运算和类型转换
【C++】入门基础介绍(下)输入输出,函数重载,缺省与引用
iostream是 Input Output Stream 的缩写,是标准输入、输出流库,定义了标准的输入、输出对象。 包含C++标准库是不需要+.h后缀。
fhvyxyci
2024/10/08
1400
【C++】入门基础介绍(下)输入输出,函数重载,缺省与引用
《C++Primer》第六章 函数
函数体是一个语句块,形参和函数体内部定义的变量统称为局部变量local variable,仅在函数的作用域内可见,同时局部变量还会隐藏hide在外层作用域中同名的其他声明中。
TOMOCAT
2020/12/02
7540
【C++ 函数模板】—— 模板参数推导、实例化策略与编译优化
上篇文章我们讲到C++的函数重载,包括函数重载的条件,原理以及一些易错事项,那么本文我们为大家介绍C++中泛型编程的主要方式——模板。
换一颗红豆
2025/03/14
2870
【C++ 函数模板】—— 模板参数推导、实例化策略与编译优化
什么是函数重载?
函数重载指的是一个作用域内的几个函数名字相同但是形参列表不同。这些函数执行操作类似,但是接受的形参类型不一样,编译器会根据传递的实参类型选择对应的函数调用。本文将简单介绍C++中的函数重载。
编程珠玑
2019/09/02
2.4K0
第6章 函数
第6章 函数 ---- 第6章 函数 6.1 函数基础 6.2 参数传递 6.3 返回类型和 return语句 6.4 函数重载 6.5 特殊用途语言特性 6.6 函数匹配 6.7 函数指针 ---
用户1653704
2018/06/07
1.3K0
【C++】重载函数
简而言之,就是函数的名字是一样的,在参数和类型,类型顺序上是不同的,这样的函数叫做重载函数。
啊QQQQQ
2024/11/19
880
适合具备 C 语言基础的 C++ 教程(十)
在上一则教程中,叙述了抽象类以及动态链接库的相关内容,本节来叙述一下抽象类界面的相关内容,以及本节即将引入一个新的概念,模板。
wenzid
2021/03/04
7240
适合具备 C 语言基础的 C++ 教程(十)
函数模板与同名的非模板函数不可以重载(重载的定义)
关于函数的重载机制,是一个比较复杂的问题,其中涉及到了优先级定义和最佳匹配等问题,如果要阐述清楚,恐怕不是一两篇文章就能说的明白。但是如果掌握了一些常用的“规律”,对于了解程序对重载函数是如何进行选择也有很大的好处,本文尝试将自己理解的知识,结合下面简单的例子简略的说说函数重载机制,文章的摘录部分列出了一些关于程序如何选择重载函数的规则。: )
全栈程序员站长
2022/07/26
9140
C++入门(函数重载、缺省参数、引用)
在自然语言中,相同的一个词可能有多重含义,人们可以通过上下文来判断这个词的具体意思,在C++中也存在这种现象,这种现象叫做函数重载。
用户11305458
2024/10/09
1620
C++入门(函数重载、缺省参数、引用)
【C++掌中宝】深入理解函数重载:概念、规则与应用
函数重载是 C++ 中一项强大的特性,它允许程序员在同一作用域内定义多个同名函数,通过不同的参数类型或数量来区分这些函数。函数重载提高了代码的灵活性和可读性,使相同操作在不同上下文中可以使用统一的函数名,从而避免重复定义不同名字的函数。本文将深入探讨函数重载的概念、规则,编译器如何处理重载,以及使用中的注意事项。
Crossoads
2024/10/22
2380
【C++掌中宝】深入理解函数重载:概念、规则与应用
《C++Primer》第十四章 重载运算与类型转换
我们定义重载的运算符时,必须首先决定它是声明为类的成员函数还是声明为一个普通的非成员函数:
TOMOCAT
2020/11/24
9420
【C++】踏上C++的学习之旅(二):缺省参数和函数重载(内含函数重载的底层原理)
在我们学习C++的命名空间之后 ,我们知道这是一个解决C语言中无法解决的问题,这个问题被我们称之为“命名冲突”。
埋头编程
2024/10/20
1270
【C++】踏上C++的学习之旅(二):缺省参数和函数重载(内含函数重载的底层原理)
C++:19---重载与模板、模板特例化
一、重载与模板 函数模板可以被另一个模板或一个普通非模板函数重载 如果涉及函数模板,则函数匹配规则会有以下的约束: 如果同样好的函数中只有一个是非模板函数,则选择此函数 如果同样好的函数中没有非模板函数,而有多个函数模板,则其中一个模板比其他模板更特例化,则选择此模板 否则,调用有歧义 ①对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例 ②候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板 ③可行函数(模板与非模板)按类型转换(如果对此调用需要的话)来排序。当然,可以用于函数模板
用户3479834
2021/02/03
1.5K0
c++之重载函数学习总结
从上面报错的结果里面有一个单词ambiguous(意思是梦棱两可的),也就是说默认参数这种使用时不允许的。
用户6280468
2022/03/21
4140
推荐阅读
相关推荐
【C++ 函数重载】—— 现代编译技术下的多态表达与性能优化
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验