C/C++ 重载函数

1 定义

函数重载是允许多个函数共享一个函数名,但是针对不同参数类型提供不同的操作.

2 怎么重载一个函数名

在C++中,可以为两个或多个函数提供相同的名字,只要他们的每个参数表唯一就行,也就是或者参数的个数不同,或者参数的类型不同。

当一个函数名在一个特殊的域中被声明为多次时,编译器按照如下步骤解释第二个以及后续的声明。

  1. 如果两个函数的参数表中参数的个数或类型不同时,则认为两个函数是重载的。
    C/C++ 重载函数
  2. 如果两个函数的返回类型和参数表精确匹配,则第二个声明被视为第一个的重复声明。注意这里要求参数表和返回类型都完全相同,如返回类型unsigned int和int是不同的。如果两个函数参数表相同但是返回类型不同,C++会认为第二个函数声明错误。如图所示:
    C/C++ 重载函数
  3. 如果在两个函数的参数表中,只有缺省值不同,那么第二个声明被视为第一个的重复声明只需要写一个实现即可。如果只有typedef定义的变量类型不同,那么也认为是重复的。
    C/C++ 重载函数
  4. 对于const和volatile修饰符,若修饰的是普通参数,也就是除了指针和引用的那些变量,带修饰符的和不带修饰符的函数被认为是相同的;但是对于指针和引用参数,带修饰符和不带修饰符的函数被认为是不同的。如下所示:
    C/C++ 重载函数
    C/C++ 重载函数
     

3 重载与域

  1. 声明为局部的函数将隐藏而不是重载一个全局域中的函数。
    C/C++ 重载函数
  2. 不同命名空间成员的函数不能相互重载。也就是不同类与空间域内的函数互不可见,不能互相重载。
  3. using声明和指示符可以使另一个命名空间的成员在另一个中可见。注意用户不能在 using声明中为一个函数指定参数表。
    C/C++ 重载函数
  4. 如果using声明向一个域中引入例如一个函数,而该函数中已经具有同名函数且具有相同的参数表,则该using声明就是错误的。
    C/C++ 重载函数
  5. using指示符使命名空间成员就像在命名空间之外被声明的一样,通过去掉命名空间的边界,using指示符把所有声明加入到当前命名空间被定义的域中。如果在当前域中声明的函数与某个命名空间成员函数名字相同则该命名空间成员函数被加入到
    重载函数集合中。
    C/C++ 重载函数
    C/C++ 重载函数

4 指向重载函数的指针

指向具体某一重载函数的指针需要提供与重载函数相同的参数数目与类型。如下所示,pf1指向extern void ff(unsigned int).

C/C++ 重载函数

5 重载解析的三个步骤

重载解析有三个步骤,具体步骤如下:

  1. 确定函数调用考虑的重载函数的集合,确定函数调用中实参的属性。也就是先找到与调用函数名字相同的函数,也就是候选函数集,再确定被调用函数的实参类型
  2. 从重载函数中选择函数,该函数可以在给出实参个数和类型的情况下对指定的实参进行调用。也就是从候选函数中,选出参数数目和参数类型与调用函数的实参个数与类型相匹配的函数,也就是可行函数。
  3. 选择与调用函数最匹配的函数。也就是从可行函数中选出实参到形参最匹配,也就是进行类型转换最少的一个函数

在进行详细重载解析之前,我们需要先了解一下参数类型转换。

6 参数类型转换

在函数重载解析的第二步中,编译器确定可以应用在函数调用的实参上的、将其转换成每个可行函数相应参数类型的转换,并将转换化分成等级,这种等级有两种可能:精确匹配和与一个类型转换匹配。

6.1 精确匹配

精确匹配也并不是完全相同,发生以下转换也认为是精确匹配。枚举类型定义了一个惟一的类型,它只与枚举类型中的枚举值以及被声明为该枚举类型的对象精确匹配

  • 从左值到右值的转换
    这里指的是将定义的变量传入函数,是一个将左值转换为右值的过程。这里被认为是精确匹配。
    C/C++ 重载函数
    但是若将变量传入形参为引用的函数则没有发生从左值到右值的转换。下例则没发生左值到右值的转换。
    C/C++ 重载函数
  • 从数组到指针的转换
    函数参数没有数组类型,取而代之的是参数被转换成指向数组首元素的指针。这里发生数组到指针的转换,属于精确匹配。
    C/C++ 重载函数
  • 从函数到指针的转换
    由于函数也是一个指针,故将函数放入调用函数指针的函数内时,发生从函数到指针的转换,这种转换属于精确匹配。
    C/C++ 重载函数
    C/C++ 重载函数
  • 限定修饰符转换
    限定修饰符转换只影响指针。不加限定修饰符的指针被加限定修饰符的函数调用,发生限定修饰符转换,属于精确匹配。
    C/C++ 重载函数

6.2 与一个类型匹配转换

6.2.1 提升

  • char,unsigned char或short型的实参被提升为int型。如果int型的字长比short整型长,则unsigned short被提升为int,否则提升为unsigned int。注意提升只指被提升到int,其他到long,unsigned long不属于提升,属于标准转换。
  • float型的实参被提升为double。
  • 枚举类型的实参被提升为第一个能表示其所有枚举常量的类型:int 、unsigned int、long或者unsigned long。
    C/C++ 重载函数
  • 布尔型实参被提升为int.

6.2.2 标准转换

注意所有标准转换都是等级的,例如从char 到unsigned char 的转换并不比从char到double 的转换优先级高,类型之间的接近程度不被考虑。

  • 整值类型转换从任何整值类型或枚举类型向其他整值类型的转换(不包括前面提升部分中列出的转换);
  • 浮点转换从任何浮点类型到其他浮点类型的转换(不包括前面提升部分中列出的转换);
  • 浮点—整值转换从任何浮点类型到任何整值类型或从任何整值类型到任何浮点类型的转换;
  • 指针转换整数值0 到指针类型的转换和任何类型的指针到类型void*的转换;
    整数0可以被转换为任何指针类型,这样创建的指针被称为空指针。但是枚举类型的0不能被转换为指针类型。
    C/C++ 重载函数
    因为void*是通用的数据类型指针,该种类型转换允许任何指针类型的实参转换为void*的参数。
    C/C++ 重载函数
  • bool 转换从任何整值类型浮点类型枚举类型或指针类型到bool 型的转换.

6.2.3引用对转换的影响

  • 实参是引用参数合适的初始值,在这种情况下实参是函数的精确匹配。下例是精确匹配。
    C/C++ 重载函数
  • 实参不能初始化引用参数。注意,临时变量不能初始化不带const的引用。如下所示对frd()的调用是错误的,实参类型是int 必须被转换成double 以匹配引用参数的类型。该转换的结果是个临时值,因为这种引用不是const 型的所以临时值不能被用来初始化该引用。
    C/C++ 重载函数
    由于实参是引用参数合适的初始值时被认为精确匹配,实参与形参相同时也是精确匹配,故当有带引用和不带应用的函数同时存在时会引发二义性,设计重载时需要考虑这一点。
    C/C++ 重载函数

7 函数重载解析细节

在了解了精确匹配和转换之后,我们可以对函数重载细节进行进一步剖析。

7.1 候选函数

有两种方法找到候选函数,分别为

  1. 该函数的声明在调用点上可见。也就是根据上面重载域中定义的各种规则,在调用点可见的函数。
    C/C++ 重载函数
     
  2. 如果函数实参的类型是在一个命名空间中被声明的,则该命名空间中与被调用函数同名的成员函数也将被加入到候选函数集中。如下takeC函数所示:
    C/C++ 重载函数

 7.2 可行函数

可行函数是这样的函数:对于每个实参,都存在到函数参数表中相应的参数类型之间的转换。如下所示:

C/C++ 重载函数
C/C++ 重载函数

7.3 最佳可行函数

最佳可行函数是与实参类型匹配最好的可行函数,对于每个可行函数来说,每个实参的类型转换都划分了等级来决定每个实参与其相应参数的匹配程度,等级分别为精确匹配>提升>标准转换>自定义转换。最佳可行函数是满足下列条件的可行函数。

1 用在实参上的转换不比调用其他可行函数所需的转换更差;
2 在某些实参上的转换要比其他可行函数对该参数的转换更好。

一般类型转换不是单一的类型转换,而是由多个转换类型组合而成。也称为转换序列。一个转换序列潜在的由下列转换构成:

C/C++ 重载函数

如果对两个转换序列进行比较,则最差的转换类型决定转换序列的好坏,如下所示:

C/C++ 重载函数

C/C++ 重载函数

此外,若两个转换序列前两个相同,但是一个有限定修饰符转换,一个不需要限定修饰符转换,则不需要限定修饰符转换的匹配性更好。如下所示:
C/C++ 重载函数

C/C++ 重载函数