函数重载

电脑编程

函数重载(英語:function overloading)或方法重载,是某些编程语言(如 C++C#JavaSwiftKotlin 等)的特性,允许创建多个具有不同实现的同名函数。对重载函数的调用会运行其适用于调用上下文的具体实现,即允许一个函数调用根据上下文执行不同的任务。

例如,doTask()doTask(object o) 是重载函数。调用后者必须传入一个 object 参数,而调用前者时则不需要参数。一个常见的错误是在第二个函数中为 object 分配一个缺省值,这将会导致意义模糊的调用错误,因为编译器不知道使用这两种方法中的哪一种。

另一个例子是 Print(object o) 函数,它根据是打印文本还是照片来执行不同的操作。这两个不同的功能可以重载为 Print(text_object T); Print(image_object P)。如果我们为程序中将要“打印”的所有对象编写重载的打印函数,就不必担心 object 的类型,再次调用相应的函数,调用始终是:Print(something)

支持函数重载的语言

支持函数重载的语言包括但不限于以下几种:

函数重载规则

  • 多个函数定义使用相同的函数名称
  • 函数参数的数量或类型必须有区别

函数重载是静态多态的一种类别,其使用某种“最佳匹配”算法解析函数调用,通过找到形式参数类型与实际参数类型的最佳匹配来解析要调用的具体函数。该算法的细节因语言而异。

函数重载通常与静态类型编程语言(在函数调用中强制执行类型检查)有关。重载函数实际上只是一组具有相同名称的不同函数。具体调用使用哪个函数是在编译期决定的。

Java 中,函数重载也被称为编译时多态和静态多态。

函数重载不应与在运行时进行选择的多态形式混淆,例如通过虚函数而不是静态函数。

示例:C++中的函数重载

#include <iostream>

int Volume(int s) {  // 立方体的体积。
  return s * s * s;
}

double Volume(double r, int h) {  // 圆柱体的体积。
  return 3.1415926 * r * r * static_cast<double>(h);
}

long Volume(long l, int b, int h) {  // 长方体的体积。
  return l * b * h;
}

int main() {
  std::cout << Volume(10);
  std::cout << Volume(2.5, 8);
  std::cout << Volume(100l, 75, 15);
}

在上面的例子中,每个零件的体积是使用名为 Volume 的三个函数之一进行计算的,根据实际参数的不同数量和类型进行选择。

构造器重载

在某些面向对象编程语言中,用于创建对象实例的构造函数也可能被重载。在许多语言中,构造函数的名称是由类的名称预先确定的,因此似乎只能有一个构造函数。每当需要多个构造函数时,它们将会被实现为重载函数。在 C++ 中,缺省构造函数不带参数,使用其适当的缺省值实例化对象成员。例如,用 C++ 编写的餐厅账单对象的缺省构造函数可能会将小费设置为 15%:

Bill()
    : tip(0.15), // 百分比
      total(0.0)
{ }

这样做的缺点是对于创建好的 Bill 对象,更改其值需要两步才能完成。下面显示了在主程序中对象的创建和对其值的更改:

Bill cafe;
cafe.tip = 0.10;
cafe.total = 4.00;

通过重载构造函数,我们可以在创建对象的同时传递 tiptotal 这两个参数。这表现为带有两个参数的重载构造函数。这个重载的构造函数和我们之前使用的原始构造函数一样放置在类中。使用哪一个取决于创建新 Bill 对象时提供的参数数量(无,或两个):

Bill(double tip, double total)
    : tip(tip),
      total(total)
{ }

现在,创建新 Bill 对象的函数可以将两个值传递给构造函数,一步到位设定好数据成员。下面显示了对象的创建和对其值的设定:

Bill cafe(0.10, 4.00);

这对于提高程序效率和缩减代码长度很有用。

构造函数重载的另一个原因可能是强制执行强制性数据成员。在这个例子中,缺省构造函数被声明为 private 或 protected(或者最好是 C++11 起加入的 deleted),以使其无法从外部访问。对于上面的 Billtotal 可能是唯一的构造函数参数 – 因为 Bill 没有为 total 提供实用的缺省值 – 而 tip 的缺省值为 0.15

注意事项

两个问题与函数重载相互影响并使其复杂化:名称解析(因为作用域)和隐式类型转换

如果在一个作用域中声明了一个函数,然后在内部作用域中声明了另一个同名函数,则有两种正常的可能的重载行为:内部声明掩盖了外部声明(无论签名如何),或者内部声明和外部声明都包含在重载中,只有在签名匹配时,内部声明才会屏蔽外部声明。第一个取自 C++:“在 C++ 中,没有跨作用域的重载。”[5] 因此,要获得不同作用域中声明的函数的重载集,需要将外部作用域中的函数显式导入到内部作用域,使用 using 关键字。

隐式类型转换使函数重载复杂化,因为如果参数类型与重载函数之一的签名不完全匹配,但可以在类型转换后匹配,则解析取决于选择哪种类型转换。

这些能够以令人困惑的方式组合:例如,在内部作用域中声明的不精确匹配可以掩盖在外部作用域中声明的精确匹配。[5]

例如,有一个派生类,其重载函数带有一个 double 或一个 int,使用基类中带有 int 的函数,在 C++ 中可以这样写:

class B {
 public:
  void F(int i);
};

class D : public B {
 public:
  using B::F;
  void F(double d);
};

如果不包含 using 关键字,会导致传递给派生类中 Fint 参数被转换成 double ,以匹配派生类中的函数,而不是基类中的函数;包含 using 导致派生类中的重载,以匹配基类中的函数。

附加说明

如果一个方法设计有过多的重载,开发者可能很难通过阅读代码来辨别正在调用哪个重载。如果某些重载参数的类型是其他可能参数的继承类型(例如“对象”),则尤其如此。IDE 可以执行重载解析并显示(或导航到)正确的重载。

基于类型的重载也会妨碍代码维护,其中代码更新可能会意外更改编译器选择的方法重载。[6]

另见

参考

  1. ^ Kotlin language specification. kotlinlang.org. [2021-09-13]. (原始内容存档于2022-05-16). 
  2. ^ 37.6. Function Overloading. PostgreSQL Documentation. 2021-08-12 [2021-08-29]. (原始内容存档于2021-08-29) (英语). 
  3. ^ Database PL/SQL User's Guide and Reference. docs.oracle.com. [2021-08-29]. (原始内容存档于2022-04-29) (英语). 
  4. ^ Nim Manual. nim-lang.org. [2021-09-13]. (原始内容存档于2021-06-15) (英语). 
  5. ^ 5.0 5.1 Stroustrup, Bjarne. Why doesn't overloading work for derived classes?. [2021-09-13]. (原始内容存档于2020-07-02). 
  6. ^ Bracha, Gilad. Systemic Overload. Room 101. 3 September 2009 [2021-09-13]. (原始内容存档于2017-04-05). 

外部链接