標籤:
在編程中也沿用了繼承的概念,在物件導向編程中,如C++和C#中都有類的繼承。 繼承(加上封裝和多態性)是物件導向的編程的三個主要特性(也稱為“支柱”)之一。 繼承用於建立可重用、擴充和修改在其他類中定義的行為的新類。 其成員被繼承的類稱為“基類”,繼承這些成員的類稱為“衍生類別”。 衍生類別只能有一個直接基類。 但是,繼承是可傳遞的。 如果 ClassB 派生出 ClassC,ClassA 派生出 ClassB,則 ClassC 會繼承 ClassB 和 ClassA 中聲明的成員。
v寫在前面
無論是其他社區還是園子裡,關於繼承類的文章或者文章已經可以說是數不勝數了,關於繼承類的話題也是舊調重彈了。寫這篇博文只是為了加深一下自己對繼承的理解,所謂讀書百遍其意自見嘛。這篇博文中部分內容來自我的讀書(C#進階編程從開始接觸C#到現在我已經是第五遍讀了,每次讀都有不一樣的收穫。)筆記,和一些自己的理解。
vC#繼承
1.實現繼承
如果要聲明派生自另一個類的一個類,就可以使用下面的文法:
class LearningClass : BasicClass { }
如果類(或結構)也派生自介面,則用逗號分隔列表中的基類和介面:
class LearningClass : BasicClass, BasicInterface, BasicInterface2 { }
對於結構,文法如下:
struct LearningStruct : BasicInterface, BasicInterface2 { }使用結構的一個限制是結構不支援繼承,但每個結 構都自動派生自system,ValueType。實際上還應更仔細一些:不能編碼實作類別型層次的結構,但結 構可以實現介面。換言之,結構並不支援實現繼承,但支援介面繼承。事實上,定義結構和類可以 總結為:
- 結構總是派生自system。ValueType,它們還可以派生自任意多個介面。
- 類總是派生自使用者選擇的另一個類,它們還可以派生自任意多個介面。
在類定義中沒有指定基類,鈄編譯器就假定sysm.叻ect是基類。因此下面的兩段代碼產生相同的結果:
class LearningClass : Object { } class LearningClass { }
2.虛方法virtual:
有時候我們繼承一個基類的某些方法希望能重新定義這個方法,我們可以把一個基類函式宣告virtual,就可以在任何衍生類別中重寫這個函數:
public class BasicClass { public virtual string GetMessage() { return "Basic class message"; } }
C#中虛函數的概念與標準00P的概念相同:可以在衍生類別中重寫虛函數。在調用方法時,會調用該類對象的合適方法。在C#中,函數在預設情況下不是虛擬,但(除了建構函式以外)可以顯 式地聲明為virtual。這遵循C++的方式,即從效能的角度來看,除非顯式指定,否則函數就不是虛 擬的。而在Java中,所有的函數都是虛擬。但C#的文法與C++的文法不同,因為C#要求在派生 類的函數重寫另一個函數時,要使用override關鍵字顯式聲明:
class LearningClass : BasicClass { public override string GetMessage() { return "Learning class message"; } }
重寫方法的文法避免了C++中很容易發生的潛在運行錯誤:當衍生類別的方法簽名無意中與基類 版本略有差別時,該方法就不能重寫基類的方法:在C#中,這會出現一個編譯錯誤,因為編譯器會 認為函數已標記為override,但沒有重寫其基類的方法。
成員欄位和靜態函數都不能聲明為virtual,因為這個概念只對類中的執行個體函數成員有意義。
3.隱藏方法:
如果簽名相同的方法在基類和衍生類別中都進行了聲明,但該方法沒有分別聲明為virtual和 override,衍生類別方法就會隱藏基類方法。
在大多數清況下,是要重寫方法,而不是隱藏方法,因為隱藏方法會造成對於給定類的執行個體調用 錯誤方法的危險。但是,如下面的例子所示,C#文法可以確保開發人員在編譯時間收到這個潛在錯誤的 警告,從而使隱藏方弦【如果這確實是使用者的本匐更加安全。這也是類庫開發人員得到的版本方面的 · 好處
此時,編譯時間系統會發出警告。在C#中,要隱藏一個方法應使用new關鍵字聲明,如下所示:
public class LearningClass : BasicClass { public new string GetMessage() { return "Learning class message"; } }
4.調用函數的基類版本:
C#有一種特殊的文法用於從衍生類別中調用方法的基類版本:base.()。例如,假定 衍生類別中的一個方法要返回基類的方法20%的傳回值,就可以使用下面的文法:
public class BasicClass { public virtual float GetPrice() { return 1.5f; } } public class LearningClass : BasicClass { public float GetPrice() { return base.GetPrice() * 0.2f; } }
ps:可以使用base.()文法調用基類中的任何方法,不必從同一個方法的重載 中調用它。
5.抽象類別和抽象函數:
C#允許把類和函式宣告為abstract。抽象類別不能執行個體化,而抽象函數不能直接實現,必須在非抽 象的衍生類別中重寫。顯然,抽象函數本身也是虛擬(儘管也不需要提供virtual關鍵字,實際上,如 果提供了該關鍵字,就會產生一個語法錯誤。如果類包含抽象函數,則該類也是抽象的,也必須聲 明為抽象的:
public abstract class BasicClass { public abstract float GetPrice(); } public class LearningClass : BasicClass { public override float GetPrice() { return 0.2f; } }
6.密封類和密封方法:
C#允許把類和方法聲明為sealed。對於類,這表示不能繼承該類;對於方法,這表示不能重寫該方法。
在把類或方法標記為sealed時,最可能的情形是:如果要對庫、類或自己編寫的其他類範圍 之外的類或方法進行操作,則重寫某些功能會導致代碼混亂。也可以因商業原因把類或方法標記為 sealed,以防第三方以違反授權協議的方式擴充該類。但一般情況下,在把類或成員標記為sealed 時要小心,因為這麼做會嚴重限制它的使用方式。即使認為它不能對繼承自一個類或重寫類的某個 成員發揮作用,仍有可能在將來的某個時刻,有人會遇到我們沒有預料到的情形,此時這麼做就很 有用。.NET基底類別庫大量使用了密封類,使希望從這些類中派生出自己的類的第三方開發人員無法訪 問這些類。例如,string就是一個密封類。
把方法聲明為sealed也可以實作類別似的目的,但很少這麼做。
7.修飾符:
C#共有五種存取修飾詞:public、private、protected、internal、protected internal。作用範圍如下表:
| 存取修飾詞 |
說明 |
| public |
公有訪問。不受任何限制。 |
| private |
私人訪問。只限於本類成員訪問,子類,執行個體都不能訪問。 |
| protected |
保護訪問。只限於本類和子類訪問,執行個體不能訪問。 |
| internal |
內部訪問。只限於本項目內訪問,其他不能訪問。 |
| protected internal |
內部保護訪問。只限於本項目或是子類訪問,其他不能訪問 |
C#成員類型的可修飾及預設修飾符如下表:
| 成員類型 |
預設修飾符 |
可被修飾符 |
| enum |
public |
none |
| class |
private |
public、protected、internal、private、 protected internal |
| interface |
public |
none |
| struct |
private |
public、internal、private |
修飾符可以應用於類型的成員,而且有不同的用途:
| 修飾符 |
應用於 |
說明 |
| new |
函數成員 |
成員用相同的簽名隱藏繼承的成員 |
| static |
所有成員 |
成員不作用於類的具體執行個體 |
| virtual |
僅函數成員 |
成員可以由衍生類別重寫 |
| abstract |
僅函數成員 |
虛擬成員定義了成員的簽名,但沒有提供執行個體代碼 |
| override |
僅函數成員 |
成員重寫了繼承的虛擬或抽象成員 |
| sealed |
類,方法和屬性 |
對於類,不能繼承自密封類。對於屬性和方法,成員重寫已繼承的虛擬成員,但任何衍生類別中的任何成員都不能重寫該成員。該修飾符必須與override一起使用 |
| extern |
僅靜態[DllImport]方法 |
成員在外部用另一種語言實現 |
C# 繼承