参考《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,张海龙 袁国忠译,人民邮电出版社。C++ 使用重载解析策略来决定为函数调用使用哪一个函数定义。重载解析过程大致分为如下三步:

  • 第 1 步:创建候选函数列表,只要求函数名一样即可,对函数特征标以及是否为模板函数无要求;
  • 第 2 步:在上一步的基础上创建可行函数列表,包含特征标完全匹配的常规函数或模板函数、以及实参隐式转换后完全匹配的常规函数或模板函数,这些都是参数数目正确的函数;
  • 第 3 步:在上一步的基础上确定最佳匹配函数,若有则使用它,若没有则该函数调用失败。

下面以一个例子来说明这个重载过程:

//全部函数原型
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char * may(const char *); //原型#4
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
template<class T> void may(T *); //原型#7
void may(char, double); //原型#8
void mbk(float); //原型#9
char mkk(int, char); //原型#10
int mck(char); //原型#11
double myk(float); //原型#12
void mpk(char); //原型#13 //函数调用
may('B'); //函数定义
...

重载第 1 步:创建候选函数列表。即函数名称为 may 的常规函数和模板函数,候选函数列表如下:

//重载第1步:创建候选函数列表
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char * may(const char *); //原型#4
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6
template<class T> void may(T *); //原型#7
void may(char, double); //原型#8

重载第 2 步:创建可行函数列表。由于整数类型 char 不能被隐式地转换为指针类型 char *,因此函数 #4 和函数 #7 都被排除,而函数 #8 因为参数数目不匹配也会被排除。进行完全匹配时,C++ 允许下表这些无关紧要的转换,表中 Type 表示任意类型,例如 char &const char & 的转换也包含在内,表中 Type (argument-list) 意味着用作实参的函数名和用作形参的函数指针只要返回类型和参数列表相同,就是匹配的。

实参类型 形参类型
Type Type &
Type & Type
Type [] Type *
Type (argument-list) Type (*) (argument-list)
Type const Type
Type volatile Type
Type * const Type *
Type * volatile Type *

根据此表可知,剩下的函数中包含特征标完全匹配的常规函数 #3#5、特征标完全匹配的模板函数 #6(此时 T 可以被实例化为 char)、实参隐式转换后完全匹配的常规函数 #1#2。可行函数列表如下:

//重载第2步:创建可行函数列表
void may(int); //原型#1
float may(float, float = 3); //原型#2
void may(char); //原型#3
char may(const char &); //原型#5
template<class T> void may(const T &);//原型#6

重载第 3 步:确定最佳匹配函数。通常,从最佳到最差的顺序如下所述:

  1. 特征标完全匹配
  2. 类型需经隐式提升转换,例如 charshort 自动转换为 intfloat 自动转换为 double
  3. 类型需经隐式标准转换,例如 int 转换为 charlong 转换为 double
  4. 类型需经隐式自定义转换,例如类中用户定义的类型转换。

依此规则,函数 #3 和函数 #5、函数 #6 都是特征标完全匹配的最佳匹配函数,函数 #1 需经隐式提升转换,函数 #2 需经隐式标准转换,由此各函数最佳匹配程度为:(#3, #5, #6) > #1 > #2。当特征标完全匹配时,又有如下规则:

  • 指向非 const 数据的指针和引用优先与形参为非 const 指针和引用的函数匹配;
  • 优先与非模板函数匹配;
  • 同为模板函数时,优先与较具体的模板函数匹配。

依此规则,非模板函数 #3#5 最佳匹配程度要高于模板函数 #6 ,即各函数最佳匹配程度为:(#3, #5) > #6 > #1 > #2。最终出现了两个最佳匹配函数 #3#5 ,因此该函数调用失败,编译器将报错

//重载第 3 步:确定最佳匹配函数
void may(char); //原型#3
char may(const char &); //原型#5

下面展开来说上述几条完全匹配时的规则。

第 1 条:指向非 const 数据的指针和引用优先与形参为非 const 指针和引用的函数匹配,这一点需明确,const 和非 const 之间的区别只适用于指针和引用。下面 4 个函数都与函数调用是完全匹配的:

//函数原型
void recycle(int); //原型#1
void recycle(const int); //原型#2
void recycle(int &); //原型#3
void recycle(const int &);//原型#4 //函数调用
int x = 5;
recycle(x); //函数定义
...
  • 如果这 4 个函数同时存在,则无法完成重载,编译器会报多义性匹配的错误;
  • 如果只存在函数 #1#2,则无法完成重载,编译器会报重复定义的错误;
  • 如果只存在函数 #1#3,则无法完成重载,编译器会报多义性匹配的错误;
  • 如果只存在函数 #1#4,则无法完成重载,编译器会报多义性匹配的错误;
  • 如果只存在函数 #2#3,则无法完成重载,编译器会报多义性匹配的错误;
  • 如果只存在函数 #2#4,则无法完成重载,编译器会报多义性匹配的错误;
  • 如果只存在函数 #3#4,则函数调用时编译器将会选择 #3

第 2 条:优先与非模板函数匹配,这一点比较简单,当完全匹配的函数中,一个是非模板函数,另一个是模板函数时,非模板函数将优于模板函数,显式具体化、显式实例化、隐式实例化都属于模板函数。

第 3 条:同为模板函数时,优先与较具体的模板函数匹配,找出最具体的模板的规则被称为函数模板的部分排序规则(partial ordering rules)。这意味着显式具体化优先于常规模板函数,都为常规模板函数时,编译器优先选择实例化时类型转换更少的那一个。以下面的程序为例,调用方式 recycle(&ink) 既与模板 #1 匹配,此时 Type 将被解释为 blot *,也与模板 #2 匹配,此时 Type 将被解释为 blot,因此将这两个隐式实例 recycle<blot *>(blot *)recycle<blot>(blot *) 发送到可行函数池中。在选择最佳匹配函数时,#2 被认为是更具体的,因为它已经显式地指出,函数参数是指向 Type 的指针,相比于 #1,它对类型的要求更加地具体,在生成过程中所需要的转换更少,因此调用方式 recycle(&ink) 实际会匹配版本 #2

//两个常规模板函数
template <class Type> void recycle(Type t); //原型#1
template <class Type> void recycle(Type * t); //原型#2 //调用程序包含如下代码
struct blot {int a; char b[10];};
blot ink = {25, "spots"};
...
recycle(&ink); //使用版本#2 //函数定义
...

部分排序规则的另一个示例程序如下,它与上一个例子有异曲同工之妙。由于模板 #2 做了特定的假设:数组内容是指针,对类型的要求更加地具体,因此在调用时第一个参数若传入指针数组 pt,则将实际匹配函数 #2

//两个常规模板函数
template <typename T>
void ShowArray(T arr[], int n); //原型#1
template <typename T>
void ShowArray(T * arr[], int n); //原型#2 //调用程序包含如下代码
int things[6] = {13, 31, 103, 301, 310, 130};
int * pt[3] = {&things[0], &things[2], &things[4]};
ShowArray(things, 6); //使用版本#1
ShowArray(pt, 3); //使用版本#2 //函数定义
...

将有多个参数的函数调用与有多个参数的原型进行匹配时,编译器必须考虑所有参数的匹配情况。如果找到比其他可行函数都合适的函数,则选择该函数。一个函数要比其他函数都合适,其所有参数的匹配程度都必须不比其他函数差,同时至少有一个参数的匹配程度比其他函数都高。

在有些情况下,可通过编写合适的函数调用,来引导编译器做出程序员期望的选择。如下所示,其中模板函数返回两个值中较小的一个,非模板函数返回两个值中绝对值较小的那个。第一次调用时根据重载解析策略选择了非模板函数 #2;第二次调用时根据重载解析策略选择了模板函数 #1double 版本,属于模板函数的隐式实例化;第三次调用的 <> 指出,编译器应该选择模板函数,此时编译器会查看调用函数时的实参类型来进行实例化,也属于模板函数的隐式实例化;第四次调用的 <int> 显式指出,编译器应该使用模板函数的 int 实例化版本,此时属于模板函数的显式实例化。

#include <iostream>

//函数#1
template<class T>
T lesser(T a, T b)
{
return a < b ? a : b;
} //函数#2
int lesser(int a, int b)
{
a = a < 0 ? -a : a;
b = b < 0 ? -b : b;
return a < b ? a : b;
} //函数调用
int main()
{
using namespace std; int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9; //使用#2,结果为20
cout << lesser(m, n) << endl; //使用#1,double隐式实例化,结果为15.5
cout << lesser(x, y) << endl; //使用#1,int隐式实例化,结果为-30
cout << lesser<>(m, n) << endl; //使用#1,int显式实例化,结果为15
cout << lesser<int>(x, y) << endl; return 0;
}

C++ 函数重载解析策略的更多相关文章

  1. C++解析(16):友元与类中的函数重载

    0.目录 1.友元的尴尬能力 2.类中的函数重载 3.小结 1.友元的尴尬能力 什么是友元? 友元是C++中的一种关系 友元关系发生在函数与类之间或者类与类之间 友元关系是单项的,不能传递 友元的用法 ...

  2. C++ primer(八)--内联函数 引用变量 引用传递函数参数 函数重载/模板/模板具体化

    一.内联函数     常规函数和内联函数的区别在于C++编译器如何将他们组合到程序中.编译过程的最终产品是可执行程序--由一组机器语言指令组成.运行程序时,操作系统将这些指令载入到计算机内存中,因此每 ...

  3. C++ 函数重载,函数模板和函数模板重载,选择哪一个?

    重载解析 在C++中,对于函数重载.函数模板和函数模板重载,C++需要有一个良好的策略,去选择调用哪一个函数定义(尤其是多个参数时),这个过程称为重载解析. (这个过程将会非常复杂,但愿不要遇到一定要 ...

  4. c++函数重载---2

    原创博客:转载请标明出处:http://www.cnblogs.com/zxouxuewei/ 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函 ...

  5. C++的函数重载 转

    ——每个现象后面都隐藏一个本质,关键在于我们是否去挖掘 写在前面: 函数重载的重要性不言而明,但是你知道C++中函数重载是如何实现的呢(虽然本文谈的是C++中函数重载的实现,但我想其它语言也是类似的) ...

  6. C++重载解析

    重载解析(overloading resolution)的规则决定了编译器为一个函数调用选用哪个函数定义.一般过程如下: 将名称相同的函数/模板函数找到并创建候选列表 从中挑选参数数目正确,符合完全匹 ...

  7. C++普通函数与模板函数以及特化函数重载的优先级问题

    在面对C++模板的时候,需要十分注意,因为模板的复杂性有很多情况,所以最好学习模板的方法我个人认为就是用到就去学,用不到就尽量别去看各种奇门怪技,因为你就算看了,好不容易搞懂模板的实现内部了,包括元编 ...

  8. 小猪猪C++笔记基础篇(六)参数传递、函数重载、函数指针、调试帮助

    小猪猪C++笔记基础篇(六) ————参数传递.函数重载.函数指针.调试帮助 关键词:参数传递.函数重载.函数指针.调试帮助 因为一些事情以及自己的懒惰,大概有一个星期没有继续读书了,已经不行了,赶紧 ...

  9. C++函数重载实现的原理以及为什么在C++中使用用C语言编译的函数时,要在函数名称前面加上extern "C"声明

    C++相对于C语言而言支持函数重载是其极大的一个特点,相信在使用C语言的时候大家如果要写一个实现两个整型数据相加的函数还要写一个浮点型数据相加的函数,那么这两个函数的名字绝对不可以一样,这样无疑在我们 ...

随机推荐

  1. MySQL主从复制原理及搭建过程

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 复制概述 复制即把一台服务器上的数据通过某种手段同步到另外一台或多台从服务器上,使得从服务器在数据上与主服务器保持一致. ...

  2. 个人学习-STL深入学习01-vectory源码研习 // 需要补充

    参考资料: [1]博主:一枚程序员 STL源码剖析--vector https://www.cnblogs.com/sooner/p/3273395.html [2]博主:劲蜡鸡腿堡 vector源码 ...

  3. Python图像处理丨基于OpenCV和像素处理的图像灰度化处理

    摘要:本篇文章讲解图像灰度化处理的知识,结合OpenCV调用cv2.cvtColor()函数实现图像灰度操作,使用像素处理方法对图像进行灰度化处理. 本文分享自华为云社区<[Python图像处理 ...

  4. Excel 名称管理器是什么,并实现一个级联选择框

    名称 在 Excel 中,每一个单元格都有自己的名称.表格横向是字母,纵向是数字,组合起来就是一个单元格的名称. A13 所代表的是 A 列,13 行的单元格.把一组单元格组合起来,加上一个名称,在 ...

  5. [网鼎杯2018]Unfinish-1|SQL注入|二次注入

    1.进入题目之后只有一个登录界面,检查源代码信息并没有发现有用的信息,尝试万能密码登录也不行,结果如下: 2.进行目录扫描,发现了注册界面:register.php,结果如下: 3.那就访问注册界面, ...

  6. 有意思的方向裁切 overflow: clip

    本文将介绍一个新特性,从 Chrome 90 开始,overflow 新增的一个新特性 -- overflow: clip,使用它,轻松的对溢出方向进行控制. overflow: clip 为何 首先 ...

  7. Hnoi2014世界树

    题面 说明/提示 N<=300000, q<=300000,m[1]+m[2]+...+m[q]<=300000 题解 这道题一看 "m[1]+m[2]+...+m[q]& ...

  8. 长篇图解etcd核心应用场景及编码实战

    大家好啊,我是字母哥,今天写一篇关于etcd的文章,其实网上也有很多关于etcd的介绍,我就简明扼要,总结提炼,期望大家通过这一篇文章掌握etcd的核心知识以及编码技能! 本文首先用大白话给大家介绍一 ...

  9. Kingbase V8R6存储过程变量数据导出到操作系统文件

    Kingbase V8R6存储过程变量数据导出到操作系统文件 说明: KingbaseES V8R6如何将自定义过程中的变量数据导出到操作系统文件中. 本次案例数据库版本: test=# select ...

  10. 写个续集,填坑来了!关于“Thread.sleep(0)这一行‘看似无用’的代码”里面留下的坑。

    "我报名参加金石计划1期挑战--瓜分10万奖池,这是我的第2篇文章,点击查看活动详情" 你好呀,我是居家十三天只出了一次小区门的歪歪. 这篇文章是来填坑的,我以前写文章的时候也会去 ...