C++函数匹配机制

函数匹配

在大多数情况下,我们容易确定某次调用应该选用哪个重载函数。

然而,当几个重载函数的形参数量相等以及某些形参的类型可以由其他类型转换得来时,这项工作就不那么容易了。

以下面这组函数及其调用为例:

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


确定候选函数和可行函数

候选函数

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数。

候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见

在这个例子中,有4个名为f的候选函数。

void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);

可行函数

第二步考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。

可行函数也有两个特征:

  • 一是其形参数量与本次调用提供的实参数量相等,
  • 二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。

我们能根据实参的数量从候选函数中排除掉两个。

不使用形参的函数和使用两个int形参的函数显然都不适合本次调用,这是因为我们的调用只提供了一个实参,而它们分别有0个和两个形参。

使用一个int 形参的函数和使用两个double形参的函数是可行的,它们都能用一个实参调用,其中最后那个函数本应该接受两个double值,但能因为它各有一个默认实参,所以只用一个实参也能调用它。

如果函教含有默认实参,则我们在调用该画就明体Note 入的实参数量可能少于它实际使用的实参数量。

在使用实参数量初步判别了候选函数后,接下来考察实参的类型是否与形参匹配。

和一般的函数调用类似,实参与形参匹配的含义可能是它们具有相同的类型,也可能是实参和形参类型满足转换规则。

在上面的例子中,剩下的两个函数都是可行的:

  • f(int)是可行的,因为实参类型double能转换成形参类型int。
  • f(double, double)是可行的,因为它的第三个形参提供了默认值,而第一个形参的类型正好是double,与函数使用的实参类型完全一致。

可行函数就只有

void f(int);
void f(double, double = 3.14);

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

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

函数匹配的第三步是从可行函数中选择与本次调用最匹配的函数。

在这一过程中,逐检查函数调用提供的实参,寻找形参类型与实参类型最匹配的那个可行函数。

下一节将介绍“最匹配”的细节,它的基本思想是,实参类型与形参类型越接近,它们匹配得越好。

在我们的例子中,调用只提供了一个(显式的)实参,它的类型是double。

如果调用f(int),实参将不得不从double转换成int。

另一个可行函数f(double,double)则与实参精确匹配。

精确匹配比需要类型转换的匹配更好,因此,编译器把f(5.6)解析成对含有两个double形参的函数的调用,并使用默认值填补我们未提供的第二个实参。

我们可以验证一下

#include<iostream>
using namespace std;
void f(){}
void f(int){}
void f(int, int){}
void f(double, double = 3.14) { cout << "调用f(double,double)版本" << endl; }int main()
{f(5.6); // 调用 void f(double, double)
}

 

含有多个形参的函数匹配

当实参的数量有两个或更多时,函数匹配就比较复杂了。

对于前面那些名为f的函数,我们来分析如下的调用会发生什么情况:

f(42,2.56);

选择可行函数的方法和只有一个实参时一样,编译器选择那些形参数量满足要求且实参类型和形参类型能够匹配的函数。

此例中,可行函数包括f(int,int)和f(double,double)。

接下来,编译器依次检查每个实参以确定哪个函数是最佳匹配。

如果有且只有一个函数满足下列条件,则匹配成功:

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

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

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

接着考虑第二个实参2.56时,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;调用 f(int)


所有算术类型转换的级别都一样。例如,从int 向unsigned int的转换并不比int向double的转换级别高。

举个具体点的例子,考虑

void manip(long);
void manip(float);
manip(3,14); //错误:二义性调用


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

函数匹配和const实参

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

#include<iostream>
using namespace std;void lookup(int&) {cout << "调用int&版本" << endl;
}
void lookup(const int&) {cout << "调用const int&版本" << endl;
}//调用1ookup (Account&)
int main()
{const int a = 0;int b;lookup(a);lookup(b);// 调用 lookup(const Account&)
}

 

在第一个调用中,我们传入的是const对象a。

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

在第二个调用中,我们传入的是非常量对象b。

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

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

如果实参是指向常量的指针,调用实参是const*的函数;如果实参是指向非常量的指针,调用形参是普通指针的函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/790749.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

013——超声波模块驱动开发(基于I.MX6uLL与SR04)

目录 一、 模块介绍 1.1 产品特色 1.2 产品实物图 1.3 接口定义 1.4 测距调节 1.5 模块工作原理 1.6 注意 二、 编码思路 三、 驱动程序 四、 应用程序 五、 Makefile 六、 其它及实验 一、 模块介绍 超声波测距模块是利用超声波来测距。模块先发送超声波&#xf…

gitlab代码迁移,包含历史提交记录、标签、分支

1、克隆现有的GitLab仓库&#xff08;http://localhost:8888/aa/bb/cc.git&#xff09;到本地&#xff0c;包括所有分支和标签 git clone --bare http://localhost:8888/aa/bb/cc.git 2、在gitlab上创建一个空的仓库&#xff08;http://localhost:7777/aa/bb/cc.git&#xff…

微服务连接不上rabbitmq解决

1.把端口port: 15672改成port&#xff1a;5672 2&#xff1a;virtual-host: my_vhost一定对应上

Android Studio 打开Logcat界面

在平时调试过程中查看调试日志需要打开 Android Studio Logcat界面。 每次安装AS都会忘记&#xff0c;自己备注一下。 AS->View->Tool Windows->Logcat

AR/VR技术对制造业劳动力危机的影响

借助 AR/VR 的力量缩小现代制造业的技能差距 数字化转型仍然是企业的首要任务&#xff0c;其许多方面都需要人工干预。然而&#xff0c;推动此类举措所需的技术工人日益短缺。这就造成了我们所说的“制造业劳动力危机”。 制造业应当如何&#xff1a; 制造业用工危机正在影响…

uniapp微信小程序真机图片不显示

不同设备可能出现部分设备显示不了图片&#xff0c;解决办法&#xff1a;图片地址直接使用&#xff0c;不要拼接&#xff1a; https://images.weserv.nl/?urlhttp

无法打开pycharm虚拟环境

问题&#xff1a;在pycharm的terminal中执行pip命令&#xff0c;但是下载的包没有安装到该项目的虚拟环境中。 激活虚拟环境&#xff0c;打开terminal&#xff0c;执行myenv\Scripts\activate&#xff0c;显示执行出错 无法加载文件 D:\Project\RF_Project\venv\Scripts\acti…

如何在Java中,使用jackson实现json缩进美化

导入的maven依赖 <!--json--> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.10.0</version> </dependency>示例代码 json要是String类型 public…

数据库管理工具 DBeaverUE for Mac激活版

DBeaverUE for Mac是一款功能强大且易于使用的数据库管理工具&#xff0c;专为Mac用户设计。它支持多种数据库类型&#xff0c;如MySQL、PostgreSQL、Oracle等&#xff0c;使得用户可以轻松管理和操作各种数据库。 软件下载&#xff1a;DBeaverUE for Mac激活版下载 DBeaverUE …

Node.js介绍

Node.js 是一个开源和跨平台的 JavaScript 运行时环境。它是几乎任何类型的项目的流行工具&#xff01;

[C#]使用OpencvSharp去除面积较小的连通域

【C介绍】 关于opencv实现有比较好的算法&#xff0c;可以参考这个博客OpenCV去除面积较小的连通域_c#opencv 筛选小面积区域-CSDN博客 但是没有对应opencvsharp实现同类算法&#xff0c;为了照顾懂C#编程同学们&#xff0c;因此将 去除面积较小的连通域算法转成C#代码。 方…

Django複習總結

①Django是框架。那麼什麼是框架&#xff1a; 框架很像是一個骨架&#xff0c;帶有很多默認器官的骨架。我們可以根據需要改寫、複寫這些器官。 從而實現自己所需要的功能。 ②Django是MVC模型\MVT模型&#xff1a; MVC模型&#xff1a;M&#xff1a;models模型層 V&#…

uni-app开发微信小程序使用BLE低功耗蓝牙正确步骤

文章目录 前言连接逻辑建议 参考资料&#xff1a;https://www.hc01.com/downloads 前言 微信小程序通过蓝牙连接设备&#xff0c;所以需要使用到BLE连接。 思路&#xff1a; 小程序连接BLE的步骤已经知道设备的BLE名称、服务id、特征值ID。需要根据蓝牙模块提供商的说明书去…

链表之单链表

上一篇博客我们学习了线性表中的顺序表&#xff0c;这一篇博客让我们继续往下了解线性表的链表&#xff0c;链表分为好几种结构&#xff0c;活不多说&#xff0c;让我们开始学习吧&#xff01; 目录 1.链表 2.链表的结构 3.单链表的实现 1.链表 1.概念&#xff1a;它是一种物…

大创项目推荐 深度学习 python opencv 火焰检测识别 火灾检测

文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…

HTML - 请你谈一谈 iframe的优缺点

难度级别:中级及以上 提问概率:50% iframe是一个HTML标签,它可以在一个网页中嵌入另外一个网页,甚至是把其他的网站嵌入进来。在之前的很长时间里,内部管理系统都在使用iframe,做为菜单切换的主体模板区域框架。iframe包含一个src属性,…

php运行python脚本失败怎么解决

假设有文件&#xff1a;php_test.php python_test.py 在php文件中运行Python&#xff1a; exec("python python_test.py", $array, $ret); 如果运行Python出错并不能保存在数组array中&#xff0c;因此应该把标准错误重定向到文件中&#xff0c;以上代码改写如下&a…

【芯片验证】通关寄存器与ral_model —— 寄存器生成流程中加入backdoor后门配置

前言 【芯片验证】通关寄存器与ral_model —— backdoor后门访问实操测试-CSDN博客 上一篇文章中,我们通过在环境中配置后门路径的方式来实现了寄存器的后门访问,但是在实际应用中,无论寄存器RTL文件、例化还是寄存器模型大概率都是工具生成的,比如在本专栏中实现的gen_r…

Docker实战教程 第2章 Docker基础

3-1 Docker介绍 什么是Docker 虚拟化&#xff0c;容器 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&…

自动驾驶中各种坐标系辨析

坐标系辨析 0. 地球椭圆体1. 大地坐标系2. eci地心惯性坐标系3. 地心地固坐标系(ECEF坐标系&#xff0c;E系)4. 站心坐标系(ENU坐标系)5. UTM坐标系6. LTM坐标系7. IMU坐标系8. 代码部分8.1 LLA(大地坐标系坐标、经纬度海拔)坐标转LTM系(ENU系)下的三维笛卡尔坐标8.2 LLA坐标转…