《C++ Primer》笔记,整理关于函数重载与函数匹配的笔记。

函数重载

void func(int a); //原函数

void func(double a); //正确:形参类型不同
void func(int a, int b); // 正确:形参个数不同
int func(int a); //错误:只有返回类型不同 typedef int int32;
void func(int32 a); //与原函数等价:形参类型相同
void func(const int a); //与原函数等价:顶层 const 将被忽略
void func(int); //与原函数等价:只是省略了形参名字

函数重载有如下的规则:

  • 名字相同,形参类型不一样。
  • 不允许两个函数除了返回类型外其他所有的要素都相同。
  • 顶层const的形参无法和没有顶层const的形参区分。

其中返回类型不同时编译时会出错,而类型别名、项层const、省略形参名字只是重复声明而已,只要不定义,编译就不会出错,比如:

//只定义了其中一个
void func(int a);
void func(const int a) {}

函数匹配

名字查找

函数匹配的第一步便是名字查找(name lookup),确定候选函数

名字查找有两方面:

  • 常规查找(normal lookup)
  • 实参决定的查找(argument-dependent lookup,ADL)

所有函数调用都会进行常规查找,只有函数的实参包括类类型对象或指向类类型对象的指针/引用的时候,才会进行实参决定的查找。

常规查找

void func(int a);   //1
namespace N
{
//作用域
void func() {} //2
void func(double a) {} //3
...
void test1()
{
func(); //候选函数为函数2和3
} void test2()
{
using ::func; //将函数1加入当前作用域
func(); //候选函数为函数1
}
...
}

从函数被调用的局部作用域开始,逐渐向上层寻找被调用的名字,一旦找到就停止向上寻找,将找到的所有名字加入候选函数。

此外,using语句可以将其他作用域的名字引用到当前作用域。

ADL查找

void func() {} //1
//第一个实参所在命名空间
namespace Name1 {
class T {
friend void func(T&) {} //2
};
void func(T) {} //3
}
//第二个实参的间接父类所在命名空间
namespace Name00 {
class T00 {
friend void func(int) {} //4
};
void func() {} //5
}
//第二个实参父类所在命名空间
namespace Name0 {
class T0:public Name00::T00 {
friend void func(int) {} //6
};
void func() {} //7
}
//第二个实参所在命名空间
namespace Name2 {
class T:public Name0::T0 {
friend void func(T&) {} //8
};
void func(T) {} //9
}
void test()
{
Name1::T t1;
Name2::T t2;
//9个函数全是候选函数
//第1个函数是normal lookup找到的
//后8个函数全是argument-dependent lookup找到的
func(&t1,t2);
}

从第一个类类型参数开始,依次遍历所有类类型参数。对于每一个参数,进入其类型定义所在的作用域(类内友元函数也包括在内),并依次进入其基类、间接基类……定义所在的作用域,查找同名函数,并加入候选函数。

注意:在继承体系中上升的过程中,不会因为找到同名函数就停止上升,这不同于常规查找。

类中的运算符重载也遵循 ADL 查找,其候选函数集既包括成员函数,也应该包括非成员函数。

namespace N
{
class A
{
public:
void operator+(int a) {} //1
};
void operator+(A &a, int a) {} //2
};
void operator+(A &a, int a) {} //3 void test()
{
N::A a;
a + 1; //1、2、3都是候选函数
}

确定可行函数

第二步便是从候选函数中选出可行函数,选择的标准如下:

  • 形参数量与本次调用提供的实参数量相等
  • 每个实参的类型与对应的形参类型相同,或者能转换成形参类型
//以下为候选函数
void func(int a, double b) {} //可行函数
void func(int a, int b) {} //可行函数:实参可转化成形参类型
int func(int a, double b) {} //可行函数 void func(int a) {} //非可行函数:形参数量不匹配
void func(int a, int b[]) {} //非可行函数:实参不能转换成形参 void test()
{
func(1, 0.1);
}

寻找最佳匹配

从可行函数中选择最匹配的函数,如果有多个形参,则最佳匹配条件为:

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

否则,发生二义性调用错误。

//可行函数
void func(int a, float b) {}
void func(int a, int b) {} void test()
{
func(1, 0.1); //二义性错误:double 向 int 的转换与向 float 的转换一样好
func(1, 1); //调用 void func(int a, int b)
}

为了确定最佳匹配,实参类型到形参类型的转换等级如下:

  1. 精确匹配:

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

一般不会存在这个阶段不会同时存在两个以上的精确匹配,因为两个精确的匹配在本质上是等价的,在定义重载函数时,编译器可能就报出重定义的错误了。

挑几个重点的来详细说一下。

指针转换实现的匹配

  • 0 或nullptr能转换成任意指针类型。
  • T * 能转换成 void *const void *转换成const void*
  • 派生类向基类类型的转换。
  • 函数与函数指针的形参类型必须精确匹配。

类类型转换实现的匹配

两个类型提供相同的类型转换将产生二义性问题。

struct B;
struct A
{
A() = default;
A(const B&); //把一个 B 转换成 A
}; struct B
{
operator A() const; // 也是把一个 B 转换成 A
}; A f(const A&); B b;
A a = f(b); //二义性错误:f(B::operator A()) 还是 f(A::A(const B&)) A a1 = f(b.operator A()); //正确:使用 B 的类型转换运算
A a2 = f(A(b)); //正确:使用 A 的构造函数

类当中定义了多个参数都是算术类型的构造函数或类型转换运算符,也会产生二义性问题。

struct A
{
A(int = 0);
A(double);
operator int() const;
operator double() const;
}; void f(long double); A a;
f(a); //二义性错误:f(A::operator int()) 还是 f(A::operator double())? long l;
A a2(l); //二义性错误:A::A(int) 还是 A::A(double)? short s;
A a3(s); //正确:使用 A::A(int)

当我们使用两个用户定义的类型转换时,如果转换函数之前或之后存在标准类型转换,则标准类型转换将决定最佳匹配到底是哪个。

部分参考:http://particle128.com/posts/2013/11/name-lookup.html

原文地址:http://simpleyyt.github.io/2016/12/17/function-overload-and-match

C++ 函数重载与函数匹配的更多相关文章

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

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

  2. C++中函数重载和函数覆盖的区别

    C++中经常会用到函数的重载和覆盖,二者也在很多场合都拿出来进行比较,这里我就对二者的区别做点总结: 函数重载: 函数重载指的是函数名相同.函数特征值不同的一些函数,这里函数的特征值指的是函数的参数的 ...

  3. c++中的函数重载、函数重写、函数重定义

    目录 一.函数重载 二.函数重写 三.函数重定义 为了更加深刻的理解 函数重载.重写.重定义,我们可以带着如下这两个问题去思考: 1.子类中是否可以定义父类中的同名成员?为什么? 可以,因为子类与父类 ...

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

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

  5. C++函数重载和函数模板

    1.函数重载 这是小菜鸟写的一个例子. 函数重载应该注意以下几点: 1.1重载函数有类似的功能: 1.2只能以参数的类型(形参个数和类型)来重载函数, int max(int a,int b);flo ...

  6. C++学习笔记之模板(1)——从函数重载到函数模板

    一.函数重载 因为函数重载比较容易理解,并且非常有助于我们理解函数模板的意义,所以这里我们先来用一个经典的例子展示为什么要使用函数重载,这比读文字定义有效的多. 现在我们编写一个交换两个int变量值得 ...

  7. C++ 函数的扩展④--函数重载与函数指针

    //函数扩展--函数重载与函数指针 #include<iostream> using namespace std; //函数参数类型不同 void Fuc(char * b){ print ...

  8. 位运算+引用+const+new/delete+内联函数、函数重载、函数缺省参数

    update 2014-05-17 一.位运算 应用: 1.判断某一位是否为1 2.只改变其中某一位,而保持其它位都不变 位运算操作: 1.& 按位与(双目): 将某变量中的某些位(与0位与) ...

  9. C++对C语言的拓展(5)—— 函数重载和函数指针结合

    1.函数指针的介绍 函数指针指向某种特定类型,函数的类型由其参数及返回类型共同决定,与函数名无关.举例如下: int add(int nLeft,int nRight);//函数定义 该函数类型为in ...

随机推荐

  1. Tries

    trie,又称前缀树或字典樹,是一种有序树,用于保存关联数组,其中的键通常是字符串.与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定.一个节点的所有子孙都有相同的前缀,也就是这个节 ...

  2. ubantu安装jdk来配置hadoop

    1.将jdk-7u5-linux-x64.tar.gz拷贝到/usr/lib/jdk/目录下面,这里如果没有jdk文件夹,则创建该文件夹,命令: sudo mkdir jdk //创建文件夹jdk s ...

  3. SDUT1586 计算组合数(组合数)

    这个题数据量小,不容易超时. #include<stdio.h> long long fac(int n) { ; ; i <= n ; i++) { m = i*m; } retu ...

  4. POJ2503Babelfish

    http://poj.org/problem?id=2503 这个题一开始是想用字典树,发现太麻烦..... #include<cstdio> #include<cstring> ...

  5. lintcode 中等题:A + B Problem A + B 问题

    题目: 中等 A + B 问题 给出两个整数a和b, 求他们的和, 但不能使用 + 等数学运算符. 如果 a=1 并且 b=2,返回3 注意 你不需要从输入流读入数据,只需要根据aplusb的两个参数 ...

  6. linux上应用程序的执行机制

    linux上应用程序的执行机制 执行文件是如何在shell中被"执行"的.本文中尽可能少用一些源码,免得太过于无 聊,主要讲清这个过程,感兴趣的同学可以去查看相应的源码了解更多的信 ...

  7. hibernate--关联映射(一对多)

    在对象模型中,一对多的关联关系,使用集合来表示. 实例场景:班级对学生:Classes(班级)和Student(学生)之间是一对多的关系. 对象模型: 多对一.一对多的区别: 多对一关联映射:在多的一 ...

  8. Android开发之android:process=":remote"

    由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象.在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将 ...

  9. 购买使用Linode VPS必须知晓的十个问题

    Linode是国外非常著名的VPS商之一,目前在国内站长圈中备受推崇.有许多站长已经购买了Linode VPS,但是部分站长由于中英语言不通,对Linode的政策不了解,从而造成了许多不必要的损失.本 ...

  10. windows线程同步

    一.前言 之前在项目中,由于需要使用到多线程,多线程能够提高执行的效率,同时也带来线程同步的问题,故特此总结如下. 二.windows线程同步机制 windows线程同步机制常用的有几种:Event. ...