C++ 中的函数重载

笔记 · 2023-06-26 · 585 人浏览
C++ 中的函数重载

  在 C++ 中,同一作用域下,同一个函数名是可以定义多次的,前提是形参列表不同。这种名字相同但形参列表不同的函数,叫做“重载函数”。这是 C++ 相对 C 语言的重大改进,也是面向对象的基础。


定义重载函数

// 使用指针和长度作为形参
void printArray(const int* arr, int size)
{
    for (int i = 0; i < size; i++)
         cout << arr[i] << "\t";
    cout << endl;
}

// 使用数组引用作为形参
void printArray(const int(&arr)[6])

{
    for (int num : arr)
         cout << num << "\t";
    cout << endl;
}

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};

    printArray(arr, 6);           // 传入两个参数,调用第一种实现

    printArray(arr);              // 传入一个参数,调用第二种实现
}

这里需要注意:

  • 重载的函数,应该在形参的数量或者类型上有所不同
  • 形参的名称在类型中可以省略,所以只有形参名不同的函数是一样的
  • 调用函数时,编译器会根据传递的实参个数和类型,自动推断使用哪个函数
  • 主函数不能重载

有 const 形参时的重载

  当形参有 const 修饰时,要区分它对于实参的要求到底是什么,是否要进行值的拷贝。如果是传值参数,传入实参时会发生值的拷贝,那么实参是变量还是常量其实是没有区别的:

void fn(int x);
void fn(const int x);      // int常量做形参,与不加const等价

void fun(int* p);
void fun(int* const p);    // 指针常量做形参,与不加const等价

  这种情况下,const 不会影响传入函数的实参类型,所以跟不加 const 的定义是一样的;这叫做“顶层 const”。这时两个函数相同,无法进行函数重载。

  另一种情况则不同,那就是传引用参数。这时如果有 const 修饰,就成了“常量的引用”;对于一个常量,只能用常量引用来绑定,而不能使用普通引用。类似地,对于一个常量的地址,只能由“指向常量的指针”来指向它,而不能用普通指针。

void fn(int &x);
void fn(const int & x);     // 形参类型是常量引用,这是一个新函数

void fun(int* p);
void fun(const int* p);     // 形参类型是指向常量的指针,这是一个新函数

  这种情况下,const 限制了间接访问的数据对象是常量,这叫做“底层 const”。当实参是常量时,不能对不带 const 的引用进行初始化,所以只能调用常量引用做形参的函数;而如果实参是变量,就会优先匹配不带 const 的普通引用:这就实现了函数重载。

函数匹配

  如果传入的实参跟形参类型不同,只要能通过隐式类型转换变成需要类型,函数也可以正确调用。那假如有几个不同的重载函数,它们的形参类型可以进行自动转换,这时传入实参应该调用哪个函数呢?例如:

void f();
void f(int x);
void f(int x, int y);
void f(double x, double y = 1.5);

f(3.14);      // 应该调用哪个函数?

确定到底调用哪个函数的过程,叫做“函数匹配”

(1)候选函数

  函数匹配的第一步,即确定“候选函数”,也就是先找到对应的重载函数集。候选函数有两个要求:

  • 与调用的函数同名
  • 函数的声明,在函数的调用点是可见的
    所以上述例子中,一共有 4 个叫做 f 的函数,它们都为候选函数。

(2)可行函数

  接下来需要从候选函数中,选出跟传入的实参匹配的函数。这些函数叫做“可行函数”。可行函数也有两个要求:

  • 形参个数与调用传入的实参数量相等
  • 每个实参的类型与对应形参的类型相同,或者可以转换成形参的类型

  上述例子中,传入的实参只有一个,是一个 double 类型的字面值常量,所以可以排除 f()f(int, int) 。而剩下的 f(int)f(double, double = 1.5) 都是匹配的,所以有 2 个可行函数。

(3)寻找最佳匹配

  最后就是在可行函数中,选择最佳匹配。简单来说,实参类型与形参类型越接近,它们就匹配得越好。所以,能不进行转换就实际匹配的,要优于需要转换的。

  上面的例子中,f(int) 必须要将 double 类型的实参转换成 int,而 f(double, double = 1.5) 不需要,所以后者是最佳匹配,最终调用的就是它。第二个参数会由默认实参 1.5 来填补。

(4)多参数的函数匹配

  如果实参的数量不止一个,那么就需要逐个比较每个参数;同样,类型能够精确匹配的要优于需要转换的。这时寻找最佳匹配的原则如下:

  • 如果可行函数的所有形参都能精确匹配实参,那么它就是最佳匹配
  • 如果没有全部精确匹配,那么当一个可行函数所有参数的匹配,都不比别的可行函数差、并且至少有一个参数要更优,那它就是最佳匹配

(5)二义性调用

  如果检查所有实参之后,有多个可行函数不分优劣、无法找到一个最佳匹配,那么编译器会报错,这被称为“二义性调用”。例如:

f(10, 3.14);      // 二义性调用

  这时的可行函数为 f(int, int)f(double, double = 1.5)。第一个实参为 int 类型,f(int, int) 占优;而第二个实参为 double 类型,f(double, double = 1.5) 占优。这时两个可行函数分不出胜负,于是就会报二义性调用错误。

重载与作用域

  重载是否生效,跟作用域是有关系的。如果在内层、外层作用域分别声明了同名的函数,那么内层作用域中的函数会覆盖外层的同名实体,让它隐藏起来。

不同的作用域中,是无法重载函数名的。

#include<iostream>
using namespace std;

void print(double d)
{
    cout << "d: " << d << endl;
}

void print(string s)
{
    cout << "s: " << s << endl;
}

int main()
{
    // 调用之前做函数声明
    void print(int i);
    print(10);
    print(3.14);           // 将3.14转换为3,然后调用
    //print("hello");        // 错误,找不到对应参数的函数定义

    system("pause");
}

void print(int i)
{
    cout << "i: " << i << endl;
}

如果想让函数正确地重载,应该把函数声明放到同一作用域下:

#include<iostream>
using namespace std;

void print(int i)
{
    cout << "i: " << i << endl;
}

void print(double d)
{
    cout << "d: " << d << endl;
}

void print(string s)
{
    cout << "s: " << s << endl;
}

int main()
{
    print(10);
    print(3.14);           // 将3.14转换为3,然后调用
    print("hello");       

    system("pause");
}

学习链接

cpp
  1. xxcheng 2023-06-26

    C++的函数重载是一项强大而实用的特性,它允许在同一作用域中定义多个同名函数,通过参数的类型和数量的不同进行区分。这为代码编写提供了更灵活和可读性更好的选择。点赞!

    1. Justin_Wu (作者)  2023-06-26
      @xxcheng

      很有价值的总结,加深对重载的理解👍

Theme Jasmine by Kent Liao

本网站由 又拍云 提供CDN加速/云存储服务

鄂ICP备2023005457号    鄂公网安备 42011302000815号

欢迎来自 * · * 的用户