本文將介紹以下內容:
什麼是覆寫,什麼是重載
覆寫與重載的區別
覆寫與重載在多態特性中的應用
1.引言
覆寫(override)與重載(overload),是成就.NET面向對象多態特性的基本技術之一,兩個貌似相似而實則不然的概念,常常帶給我們很多的誤解,因此有必要以專題來討論清楚其區別,而更重要的是關注其在多態中的應用。
在系列中,我們先後都有關於這一話題的點滴論述,本文以專題的形式再次做以深度討論,相關的內容請對前文做以參考。
2.認識覆寫和重載
從一個示例開始來認識什麼是覆寫,什麼是重載?
abstract class Base
{
//定義虛方法
public virtual void MyFunc()
{
}
//參數列表不同,virtual不足以區分
public virtual void MyFunc(string str)
{
}
//參數列表不同,返回值不同
public bool MyFunc(string str, int id)
{
Console.WriteLine("AAA");
return true;
}
//參數列表不同表現為個數不同,或者相同位置的參數類型不同
public bool MyFunc(int id, string str)
{
Console.WriteLine("BBB");
return false;
}
//泛型重載,允許參數列表相同
public bool MyFunc<T>(string str, int id)
{
return true;
}
//定義抽象方法
public abstract void Func();
}
class Derived: Base
{
//阻隔父類成員
public new void MyFunc()
{
}
//覆寫基類成員
public override void MyFunc(string str)
{
//在子類中訪問父類成員
base.MyFunc(str);
}
//覆寫基類抽象方法
public override void Func()
{
//實現覆寫方法
}
}
2.1 覆寫基礎篇
覆寫,又稱重寫,就是在子類中重復定義父類方法,提供不同實現,存在於有繼承關系的父子關系。當子類重寫父類的虛函數後,父類對象就可以根據根據賦予它的不同子類指針動態的調用子類的方法。從示例的分析,總結覆寫的基本特征包括:
在.NET中只有以virtual和abstract標記的虛方法和抽象方法才能被直接覆寫。
覆寫以關鍵字override標記,強調繼承關系中對基類方法的重寫。
覆寫方法要求具有相同的方法簽名,包括:相同的方法名、相同的參數列表和相同的返回值類型。
概念:虛方法
虛方法就是以virtual關鍵字修飾並在一個或多個派生類中實現的方法,子類重寫的虛方法則以override關鍵字標記。虛方法調用,是在運行時確定根據其調用對象的類型來確定調用適當的覆寫方法。.NET默認是非虛方法,如果一個方法被virtual標記,則不可再被static、abstrcat和override修飾。
概念:抽象方法
抽象方法就是以abstract關鍵字修飾的方法,抽象方法可以看作是沒有實現體的虛方法,並且必須在派生類中被覆寫,如果一個類包括抽象方法,則該類就是一個抽象類。因此,抽象方法其實隱含為虛方法,只是在聲明和調用語法上有所不同。abstract和virtual一起使用是錯誤的。
2.2 重載基礎篇
重載,就是在同一個類中存在多個同名的方法,而這些方法的參數列表和返回值類型不同。值得注意的是,重載的概念並非面向對象編程的范疇,從編譯器角度理解,不同的參數列表、不同的返回值類型,就意味著不同的方法名。也就是說,方法的地址,在編譯期就已經確定,是這一種靜態綁定。從示例中,我們總結重載的基本特征包括:
重載存在於同一個類中。
重載方法要求具有相同的方法名,不同的參數列表,返回值類型可以相同也可以不同(通過operator implicit 可以實現一定程度的返回值重載,不過不值得推薦)。
.NET 2.0引入泛型技術,使得相同的參數列表、相同的返回值類型的情況也可以構成重載。
3.在多態中的應用
多態性,簡單的說就是“一個接口,多個方法”,具體表現為相同的方法簽名代表不同的方法實現,同一操作作用於不同的對象,產生不同的執行結果。在.NET中,覆寫實現了運行時的多態性,而重載實現了編譯時的多態性。
運行時的多態性,又稱為動態聯編,通過虛方法的動態調度,在運行時根據實際的調用實例類型決定調用的方法實現,從而產生不同的執行結果。
class Base
{
public virtual void MyFunc(string str)
{
Console.WriteLine("{0} in Base", str);
}
}
class Derived: Base
{
//覆寫基類成員
public override void MyFunc(string str)
{
Console.WriteLine("{0} in Derived", str);
}
public static void Main()
{
Base B = new Base();
B.MyFunc("Hello");
Derived A = new Derived();
B = A;
B.MyFunc("Morning");
}
}
從結果中可知,對象B兩次執行B.MyFunc調用了不同的方法,第一次調用基類方法MyFunc,而第二次調用了派生類方法MyFunc。在執行過程中,對象B先後指向了不同的類的實例,從而動態調用了不同的實例方法,顯然這一執行操作並非確定於編譯時,而是在運行時根據對象B執行的不同類型來確定的。我們在此不分析虛擬方法的動態調度機制,而只關注通過虛方法覆寫而實現的多態特性,詳細的實現機制請參考本系列的其它內容。
編譯時的多態性,又稱為靜態聯編,一般包括方法重載和運算符重載。對於非虛方法來說,在編譯時通過方法的參數列表和返回值類型決定不同操作,實現編譯時的多態性。例如,在實際的開發過程中,.NET開發工具Visual Studio的智能感知功能就很好的為方法重載提供了很好的交互手段,例如:
從智能感知中可知方法MyFunc在派生類Derived中有三次重載,調用哪種方法由程序開發者根據其參數、返回值的不同而決定。由此可見,方法重載是一種編譯時的多態,對象A調用哪種方法在編譯時就已經確定。
4.比較,還是規則
如果基訪問引用的是一個抽象方法,則將導致編譯錯誤。
abstract class Base
{
public abstract void Func();
}
class Derived: Base
{
//覆寫基類抽象方法
public override void Func()
{
base.Func();
}
}
虛方法不能是靜態的、密封的。
覆寫實現的多態確定於運行時,因此更加的靈活和抽象;重載實現的多態確定於編譯時,因此更加的簡單和高效。二者各有特點與應用,不可替代。
在下表中,將覆寫與重載做以總結性的對比,主要包括:
規則 覆寫(override) 重載(overload) 存在位置 存在於有繼承關系的不同類中 存在於同一個類中 調用機制 運行時確定 編譯時確定 方法名 必須相同 必須相同 參數列表 必須相同 必須不同 返回值類型 必須相同 可以不相同 泛型方法 可以覆寫 可以重載
注:參數列表相同表示參數的個數相同,並且相同位置的參數類型也相同。
5.結論
深入的理解覆寫和重載,是對多態特性和面向對象機制的有力補充,本文從基本概念到應用領域將兩個概念進行一一梳理,通過對比整理區別,還覆寫和重載以更全面的認知角度,同時也更能從側面深入的了解運行時多態與編譯時多態的不同情況。