函數多載

电脑编程

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

外部連結

編輯