《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. Observer Pattern

    Motivation We can not talk about Object Oriented Programming without considering the state of the ob ...

  2. poj 3735 Training little cats(矩阵快速幂,模版更权威,这题数据很坑)

    题目 矩阵快速幂,这里的模版就是计算A^n的,A为矩阵. 之前的矩阵快速幂貌似还是个更通用一些. 下面的题目解释来自 我只想做一个努力的人 @@@请注意 ,单位矩阵最初构造 行和列都要是(猫咪数+1) ...

  3. MVC中SelectList和@Html.DropDownList("MainDuty_UserId","请选择")的运用

    Models.Project model = projectdb.dbSet.SingleOrDefault(e => e.Project_ID == id);            ViewB ...

  4. Javascript实现两张图片的延迟加载

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  5. hdu 1352 I Conduit!

    计算几何,主要是排序问题,其他都很好做…… ;}

  6. PHP中的抽象类与接口

    抽象类 php5支持抽象类和抽象方法.类前加 abstract, 此类就成为抽象类,无法被实例化,此类天生就是用来被继承的,给子类提供了一个类的模板; 类方法前加 abstract,是抽象方法,抽象方 ...

  7. Java文件解压

    import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import ...

  8. java调用phantomjs采集ajax加载生成的网页

    java调用phantomjs采集ajax加载生成的网页 日前有采集需求,当我把所有的对应页面的链接都拿到手,准备开始根据链接去采集(写爬虫爬取)对应的终端页的时候,发觉用程序获取到的数据根本没有对应 ...

  9. jdbc知识问答 分类: 面试 2015-07-10 22:05 5人阅读 评论(0) 收藏

    1 JDBC连接数据库6步 Load the JDBC Driver Establish the Database Connection Create a Statement Object Execu ...

  10. Android的底层库libutils介绍

    第一部分 libutils概述 libutils是Android的底层库,这个库以C++实现,它提供的API也是C++的.Android的层次的C语言程序和库,大都基于libutils开发. libu ...