函數重載

电脑编程

函數重載(英語: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). 

外部連結