一. 繼承基礎知識
為了提高軟件模塊的可復用性和可擴充性,以便提高軟件的開發效率,我們總是希望能夠利用前人或自己以前的開發成果,同時又希望在自己的開發過程中能夠有足夠的靈活性,不拘泥於復用的模塊。C#這種完全面向對象的程序設計語言提供了兩個重要的特性--繼承性inheritance 和多態性polymorphism。
繼承是面向對象程序設計的主要特征之一,它可以讓您重用代碼,可以節省程序設計的時間。繼承就是在類之間建立一種相交關系,使得新定義的派生類的實例可以繼承已有的基類的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起類的新層次。
現實世界中的許多實體之間不是相互孤立的,它們往往具有共同的特征也存在內在的差別。人們可以采用層次結構來描述這些實體之間的相似之處和不同之處。
圖1 類圖
上圖反映了交通工具類的派生關系。最高層的實體往往具有最一般最普遍的特征,越下層的事物越具體,並且下層包含了上層的特征。它們之間的關系是基類與派生類之間的關系。
為了用軟件語言對現實世界中的層次結構進行模型化,面向對象的程序設計技術引入了繼承的概念。一個類從另一個類派生出來時,派生類從基類那裡繼承特性。派生類也可以作為其它類的基類。從一個基類派生出來的多層類形成了類的層次結構。
注意:C#中,派生類只能從一個類中繼承。這是因為,在C++中,人們在大多數情況下不需要一個從多個類中派生的類。從多個基類中派生一個類這往往會帶來許多問題,從而抵消了這種靈活性帶來的優勢。
C#中,派生類從它的直接基類中繼承成員:方法、域、屬性、事件、索引指示器。除了構造函數和析構函數,派生類隱式地繼承了直接基類的所有成員。看下面示例:
Vehicle 作為基類,體現了"汽車"這個實體具有的公共性質:汽車都有輪子和重量。Car 類繼承了Vehicle 的這些性質,並且添加了自身的特性:可以搭載乘客。
二、C#中的繼承符合下列規則:
1、繼承是可傳遞的。如果C從B中派生,B又從A中派生,那麼C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object 類作為所有類的基類。
2、派生類應當是對基類的擴展。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。
3、構造函數和析構函數不能被繼承。除此以外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
4、派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這並不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
5、類可以定義虛方法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實現類可以展示出多態性。
6、派生類只能從一個類中繼承,可以通過接呂實現多重繼承。
下面的代碼是一個子類繼承父類的例子:
程序運行輸出:
父類構造函數。子類構造函數。I'm a Parent Class。
上面的一個類名為ParentClass, main函數中用到的類名為ChildClass。要做的是創建一個使用父類ParentClass現有代碼的子類ChildClass。
1.首先必須說明ParentClass是ChildClass的基類。
這是通過在ChildClass類中作出如下說明來完成的:"public class ChildClass : ParentClass"。在派生類標識符後面,用分號":" 來表明後面的標識符是基類。C#僅支持單一繼承。因此,你只能指定一個基類。
2.ChildClass的功能幾乎等同於ParentClass。
因此,也可以說ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,調用print( ) 方法的結果,就驗證這一點。該子類並沒有自己的print( )方法,它使用了ParentClass中的 print( )方法。在輸出結果中的第三行可以得到驗證。
3.基類在派生類初始化之前自動進行初始化。ParentClass 類的構造函數在ChildClass的構造函數之前執行。
[1] [2] [3] [4] [5] 下一頁
指定創建派生類實例時應調用的基類構造函數。
程序運行輸出:
姓名: 張三
編號: 111-222-333-444
成員ID: ABC567EFG23267
示例:派生類同基類進行通信。
程序運行輸出:
From Derived
Child Constructor.
I'm a Parent Class.
I'm a Child Class.
I'm a Parent Class.
說明:
1.派生類在初始化的過程中可以同基類進行通信。
上面代碼演示了在子類的構造函數定義中是如何實現同基類通信的。分號":"和關鍵字base用來調用帶有相應參數的基類的構造函數。輸出結果中,第一行表明:基類的構造函數最先被調用,其實在參數是字符串"From Derived"。
2.有時,對於基類已有定義的方法,打算重新定義自己的實現。
Child類可以自己重新定義print( )方法的實現。Child的print( )方法覆蓋了Parent中的 print 方法。結果是:除非經過特別指明,Parent類中的print方法不會被調用。
3.在Child類的 print( ) 方法中,我們特別指明:調用的是Parent類中的 print( ) 方法。
方法名前面為"base",一旦使用"base"關鍵字之後,你就可以訪問基類的具有公有或者保護權限的成員。 Child類中的print( )方法的執行結果出現上面的第三行和第四行。
4.訪問基類成員的另外一種方法是:通過顯式類型轉換。
在Child類的Main( )方法中的最後一條語句就是這麼做的。記住:派生類是其基類的特例。這個事實告訴我們:可以在派生類中進行數據類型的轉換,使其成為基類的一個實例。上面代碼的最後一行實際上執行了Parent類中的 print( )方法。
上一頁 [1] [2] [3] [4] [5] 下一頁
如果我們嘗試寫下面的代碼
class C: B{ }
C#會指出這個錯誤,告訴你B 是一個密封類,不能試圖從B 中派生任何類。
(3) 密封方法
我們已經知道,使用密封類可以防止對類的繼承。C#還提出了密封方法(sealedmethod) 的概念,以防止在方法所在類的派生類中對該方法的重載。對方法可以使用sealed 修飾符,這時我們稱該方法是一個密封方法。
不是類的每個成員方法都可以作為密封方法密封方法,必須對基類的虛方法進行重載,提供具體的實現方法。所以,在方法的聲明中,sealed 修飾符總是和override 修飾符同時使用。請看下面的例子代碼:
類B 對基類A 中的兩個虛方法均進行了重載,其中F 方法使用了sealed 修飾符,成為一個密封方法。G 方法不是密封方法,所以在B 的派生類C 中,可以重載方法G,但不能重載方法F。
(4) 使用 new 修飾符隱藏基類成員
使用 new 修飾符可以顯式隱藏從基類繼承的成員。若要隱藏繼承的成員,請使用相同名稱在派生類中聲明該成員,並用 new 修飾符修飾它。
請看下面的類:
在派生類中用 MyVoke名稱聲明成員會隱藏基類中的 MyVoke方法,即:
但是,因為字段 x 不是通過類似名隱藏的,所以不會影響該字段。
通過繼承隱藏名稱采用下列形式之一:
a、引入類或結構中的常數、指定、屬性或類型隱藏具有相同名稱的所有基類成員。
b、引入類或結構中的方法隱藏基類中具有相同名稱的屬性、字段和類型。同時也隱藏具有相同簽名的所有基類方法。
c、引入類或結構中的索引器將隱藏具有相同名稱的所有基類索引器。
注意:在同一成員上同時使用 new 和 override 是錯誤的。同時使用 new 和 virtual 可保證一個新的專用化點。在不隱藏繼承成員的聲明中使用 new 修飾符將發出警告。
示例1:在該例中,基類 MyBaseC 和派生類 MyDerivedC 使用相同的字段名 x,從而隱藏了繼承字段的值。該例說明了 new 修飾符的使用。同時也說明了如何使用完全限定名訪問基類的隱藏成員。
輸出: 100 55 22
如果移除 new 修飾符,程序將繼續編譯和運行,但您會收到以下警告:
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
如果嵌套類型正在隱藏另一種類型,如下例所示,也可以使用 new 修飾符修改此嵌套類型。
上一頁 [1] [2] [3] [4] [5] 下一頁
te int pX ;
1、繼承中關於可訪問域的一些問題
基類的所有成員(實例構造函數、析構函數和靜態構造函數除外)都由派生類型繼承。這甚至包括基類的私有成員。但是,私有成員的可訪問域只包括聲明該成員的類型的程序文本。在下面的示例中
類 B 繼承類 A 的私有成員 x。因為該成員是私有的,所以只能在 A 的"類體"中對它進行訪問。因此,對 b.x 的訪問在 A.F 方法中取得了成功,在 B.F 方法中卻失敗了。
2、繼承中關於屬性的一些問題
和類的成員方法一樣,我們也可以定義屬性的重載、虛屬性、抽象屬性以及密封屬性的概念。與類和方法一樣,屬性的修飾也應符合下列規則:
屬性的重載
1. 在派生類中使用修飾符的屬性,表示對基類中的同名屬性進行重載。
2. 在重載的聲明中,屬性的名稱、類型、訪問修飾符都應該與基類中被繼承的屬性一致。
3. 如果基類的屬性只有一個屬性訪問器,重載後的屬性也應只有一個。但如果基類的屬性同時包含get 和set 屬性訪問器,重載後的屬性可以只有一個,也可以同時有兩個屬性訪問器。
注意:與方法重載不同的是,屬性的重載聲明實際上並沒有聲明新的屬性,而只是為已有的虛屬性提供訪問器的具體實現。
虛屬性
1. 使用virtual 修飾符聲明的屬性為虛屬性。
2. 虛屬性的訪問器包括get 訪問器和set 訪問器,同樣也是虛的。
抽象屬性
1. 使用abstract 修飾符聲明的屬性為抽象屬性。
2. 抽象屬性的訪問器也是虛的,而且沒有提供訪問器的具體實現。這就要求在非虛的派生類中,由派生類自己通過重載屬性來提供對訪問器的具體實現。
3. abstract 和override 修飾符的同時使用,不但表示屬性是抽象的,。而且它重載了基類中的虛屬性這時屬性的訪問器也是抽象的。
4. 抽象屬性只允許在抽象類中聲明。
5. 除了同時使用abstract 和override 修飾符這種情況之外,static, virtual, override和abstract 修飾符中任意兩個不能再同時出現。
密封屬性
1. 使用sealed 修飾符聲明的屬性為密封屬性。類的密封屬性不允許在派生類中被繼承。密封屬性的訪問器同樣也是密封的。
2. 屬性聲明時如果有sealed 修飾符,同時也必須要有override 修飾符。
從上面可以看出,屬性的這些規則與方法十分類似。對於屬性的訪問器,我們可以把get 訪問器看成是一個與屬性修飾符相同、沒有參數、返回值為屬性的值類型的方法,把set 訪問器看成是一個與屬性修飾符相同、僅含有一個value 參數、返回類型為void 的方法。看下面的程序:
上一頁 [1] [2] [3] [4] [5] 下一頁
{ return m_sex ; }
上面的例子中聲明了"人"這個類,人的姓名Name 和性別Sex 是兩個只讀的虛屬性:身份證號Card 是一個抽象屬性,允許讀寫,因為類People 中包含了抽象屬性Card,所以People 必須聲明是抽象的。下面我們為住宿的客人編寫一個類類從People 中繼承。再看下面的程序:
在類Customer 中,屬性Name、 Sex 和Card 的聲明都加上了override 修飾符,屬性的聲明都與基類People 中保持一致。Name 和Sex 的get 訪問器,Card 的get 和set訪問器都使用了base 關鍵字來訪問基類People 中的訪問器屬性。Card 的聲明重載了基類People 中的抽象訪問器。這樣,在Customer 類中沒有抽象成員的存在,Customer可以是非虛的。
3、繼承中對使用可訪問性級別的限制 下表匯總了對使用聲明的可訪問性級別的限制。
示例:以下示例包含不同類型的錯誤聲明。每個聲明後的注釋指示了預期的編譯器錯誤。
using System ;
delegate int MyDelegate( ) ;
class B
{ // 定義一個私有的函數:
static int MyPrivateMethod()
{ return 0 ; }
}
public class A
{ // 字段定義:
public B myField = new B();// 錯誤: 類型B與A字段A.myField級別不同
// 構造函數:
public readonly B myConst = new B(); //錯誤: 類型B是僅讀的
//方法:
public B MyMethod()
{
return new B();
}
//屬性:
public B MyProp
{
set { }
}
public static B operator + (A m1, B m2)
{
return new B();
}
static void Main()
{
Console.Write("Compiled successfully");
}
}
上一頁 [1] [2] [3] [4] [5]