在编写函数时,针对于同一行为,我们希望能够使用相同的名字,而不是再又重新命名。这样做的好处就是能够减少同样行为函数的命名记忆,降低复杂性。可是这会引入另一个问题,这不会违反单一定义原则吗?C++中的重载机制帮我们实现了这一点,下面就看一下它是怎么区分这些函数的,那些又是它不能区分的?

1、参数的数量不同 √

#include <iostream>
int add(int x, int y)
{
    return x + y;
}

int add(int x, int y, int z)
{
    return x + y + z;
}

int main()
{
    std::cout << add(1, 2) << '\n';
    std::cout << add(1, 2, 3) << '\n';

    return 0;
}

在这里插入图片描述

#include <iostream>
typedef int INT;
using INT2 = int;

int add(INT x, INT2 y) // int add(int x, int y)
{
    return x + y;
}


int main()
{
    std::cout << add(1, 2) << '\n';

    return 0;
}

2、参数的类型不同 √

#include <iostream>
int add(int x, int y)
{
    return x + y;
}

double add(double x, double y)
{
    return x + y;
}

int main()
{
    std::cout << add(1, 2);
    std::cout << '\n';
    std::cout << add(1.2, 3.4);

    return 0;
}

在这里插入图片描述
像下面的混合形参都能区分

int add(int x, double y);
int add(double x, int y);

3、函数返回值不同 x

#include <iostream>

int add(int x)
{
    std::cout << x << '\n';
    return 0;
}

double add(int x)
{
    std::cout << x << '\n';
    return 0;
}

int main()
{
    add(5);

    return 0;
}

在这里插入图片描述
总结:编译器如何区分函数重载

函数属性 是否区分
参数数量
参数类型
返回类型

注意:对于不能用返回类型来区分函数重载,这是故意的,因为这涉及到函数返回的类型的用法,给分析重载带来不小的负担,而直接明了的根据函数参数数量和类型要轻松的多。

对于重载函数,可能有多个函数可能匹配函数调用。由于函数调用只能解析为其中一个,因此编译器必须确定哪个重载函数是最佳匹配。将函数调用与特定的重载函数匹配的过程称为重载解析。

#include <iostream>

void print(int x)
{
    std::cout << x << '\n';
}

void print(double d)
{
    std::cout << d << '\n';
}

int main()
{
    print('a');// char does not match int or double, so what happens?

    return 0;
}

当对重载函数进行函数调用时,编译器会逐步执行一系列规则来确定哪个重载函数(如果有)是最佳匹配。
在每个步骤中,编译器都会对函数调用中的参数应用一系列不同的类型转换。对于每个应用的转换,编译器都会检查是否有任何重载函数匹配。在应用所有不同的类型转换并检查匹配后,该步骤就完成了。结果可能是以下三种结果之一:

  • 未找到匹配的函数。编译器将按序列转到下一步。
  • 找到了一个匹配函数。该函数被认为是最佳匹配。匹配过程现已完成,后续步骤不再执行。
  • 找到了多个匹配的函数。编译器会抛出一个模糊匹配编译错误。

因为函数print(‘a’)调用的a是一个char类型,它匹配到了print(int ) 和print(double)两个函数,而char是以ascill码存在的,其char->int属于是整型提升,代价相比于char -> double这种整数和浮点数之间的转换,它会选择转换代价更小的char->int整型提升。
在这里插入图片描述
补充编译器在找最佳匹配时会分为两个阶段,第一判断是否位重载,第二会做些简单的转换(也被称为精准匹配,它们会修改类型,但不修改值)。这些简单的转换所遵循的规则:

  • 左值到右值的转换
  • 限定转换(例如,非常量到常量)
  • 非引用到引用转换

或许对于类型提升优先于类型转换这种还比较好理解型,但是对于long 到int, 或者是long 到 double,这种呢?long到int有可能引起窄化转化(做不到值保护的类型转换),而long到double这种不同类型转化代价会更大(整型和浮点型在存储中的方式是完全不一样的),像这样不能平衡他们优先级的,或者说比较模糊的会怎么转换呢?

  1 #include <iostream>
  2 void foo(unsigned int)
  3 {
  4 }
  5
  6 void foo(float)
  7 {
  8 }
  9
 10 int main()
 11 {
 12     foo(0);       // int can be numerically converted to unsigned int or to float
 13     foo(3.14159); // double can be numerically converted to unsigned int or to float
 14
 15     return 0;
 16 }

(代码复制中带有编译器中的行数,所以要删除掉才能运行)
在这里插入图片描述
尽管我们可能期望0解析为foo(unsigned int)并3.14159解析为foo(float),但这两个调用都会导致模糊匹配,当编译器不知道那个匹配得更好时,它就会报错。

解决模糊匹配有两种方法:

  • 第一种定义一个新的重载函数,这是最简单的方法。
  • 第二种是使用强制类型转换。比如static_cast<dest_type>(source_type)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐