簡介 繼承(封裝、多態)是面向對象編程三大特性之一,繼承的思想就是擯棄代碼的冗餘,實現更好的重用性。 繼承從字面上理解,無外乎讓人想到某人繼承某人的某些東西,一個給一個拿。這個語義在生活中,就像 家族繼承財產,爺爺將財產繼承給兒女,兒女在將財產繼承給子孫,有些東西可以繼承有些的東西只繼承給 某人。映 ...
簡介
繼承(封裝、多態)是面向對象編程三大特性之一,繼承的思想就是擯棄代碼的冗餘,實現更好的重用性。
繼承從字面上理解,無外乎讓人想到某人繼承某人的某些東西,一個給一個拿。這個語義在生活中,就像
家族繼承財產,爺爺將財產繼承給兒女,兒女在將財產繼承給子孫,有些東西可以繼承有些的東西只繼承給
某人。映射到編程當中,其思想也大致如此。
通過示例引出繼承的作用
在代碼中定義個三個類:Cat貓、Dog狗、Cattle牛。
從類圖上可以看出紅色標識區域,三個類的定義出現了大量的冗餘(欄位、屬性、方法),那麼在編寫代碼時就會出現大量的重覆代碼。
試想一下,隨著業務功能的擴展,可能會出現更多類,那麼冗餘(重覆的代碼)會更多。比如出現同樣會造成冗餘的類:
Pig豬、Panda熊貓、Sheep羊......等等。這些類同樣會有相同的特征:名稱、性別、年齡、奔跑(欄位、屬性、方法)。
如何解決此類冗餘問題 —— 使用繼承
繼承的思想:
當我們定義了多個類,這多個類都存在重覆的成員(共性)。我們可以將這些重覆的成員單獨的提取封裝到一個類中,作為這些具有相同特征類的父類。
將此思想作用於上述的三個類
提取共性:可以直觀看出重覆的具有共性的項目有:1.欄位和屬性(年齡、姓名、性別)、2.方法(奔跑)。
封裝到一個類:如何定義這個類?Cat貓、Dog狗、Cattle牛有明顯共同的特性,就是他們都是動物,故可以抽象定義一個Animal動物類。
如何在代碼中實現繼承
class Animal { private string name; public string Name { get { return name; } set { name = value; } } private string gender; public string Gender { get { return gender; } set { gender = value; } } private int age; public int Age { get { return age; } set { age = value; } } public void Run() { Console.WriteLine("奔跑。。。"); } } class Cat:Animal { public void CatchMouse() { Console.WriteLine("抓老鼠。。。"); } } class Dog:Animal { public void GuardHouse() { Console.WriteLine("看家護院。。。"); } } class Cattle:Animal { public void Plowland() { Console.WriteLine("耕田。。。"); } }
通過一個簡單的 :(冒號)實現了繼承關係。
實現繼承後產生了兩個角色:1.子類(派生類)、2.父類(基類)
代碼中子類刪除父類提取的重覆性成員。
實現繼承後的關係如下圖:
實現繼承後每個子類僅保留了自己特有的特性,大大減少了冗餘。
繼承後的能力
子類的共性成員都被父類提取了,那麼子類要使用怎麼辦?
子類繼承父類後,將會隱式繼承父類的所有成員,但不包括構造函數。
在繼承後,訪問其父類成員,會受到訪問修飾符的限制。故,修飾為private的私有成員不會訪問到。
繼承的特性
1.繼承的單根性:
一個子類只能有一個父類,就好比一個人只有一個父親。
2.繼承的傳遞性:
例如, ClassC 派生自 ClassB,並且 ClassB 派生自 ClassA,則 ClassC 會繼承在 ClassB 和 ClassA 中聲明的成員。
依次順序可以不斷向上取。
圖例:
繼承被後的秘密 —— 子類和父類的構造函數(難點)
給父類編寫了一個構造函數,示例代碼如下:
1 class Animal 2 { 3 public Animal(string name,string gender,int age) 4 { 5 this.Name = name; 6 this.Gender = gender; 7 this.Age = age; 8 } 9 10 private string name; 11 public string Name 12 { 13 get { return name; } 14 set { name = value; } 15 } 16 17 private string gender; 18 public string Gender 19 { 20 get { return gender; } 21 set { gender = value; } 22 } 23 24 private int age; 25 public int Age 26 { 27 get { return age; } 28 set { age = value; } 29 } 30 31 public void Run() 32 { 33 Console.WriteLine("奔跑。。。"); 34 } 35 36 private void ri() 37 { } 38 39 } 40 41 class Cat:Animal 42 { 43 public void CatchMouse() 44 { 45 Console.WriteLine("抓老鼠。。。"); 46 } 47 } 48 49 class Dog:Animal 50 { 51 public void GuardHouse() 52 { 53 Console.WriteLine("看家護院。。。"); 54 } 55 } 56 57 class Cattle:Animal 58 { 59 public void Plowland() 60 { 61 Console.WriteLine("耕田。。。"); 62 } 63 }
嘗試運行:
為什麼會提示報這個錯誤?意思說父類不能沒有一個無參的構造函數。
學過構造函數的應該都會知道,類在沒有指定任何構造函數的情況下,程式預設會指派一個無參的構造函數。
上述的例子由於我們手動添加的那個構造函數,預設的構造函數就被清除掉了。
在暫且不知道原因的情況下,我們嘗試補全那個無參的構造函數,在進行生成代碼,此時編譯通過沒有報錯。
根據此特征我們可以推測子類和父類的構造函數一定有關係,但一定不是繼承關係
嘗試調用剛剛定義的父類無參構造函數,在調用列表並沒有顯示,只顯示了類自身的一個無參構造函數。
證明瞭子類不能繼承父類的構造函數。
通過調試代碼監視子類實例化對象的過程,看它到底和父類的構造函數發生了什麼。
通過調試發現在創建子類對象時的代碼執行邏輯如下:
子類會首先去預設執行父類的無參構造函數,然後在執行自己的構造函數
這條定論就很好的解釋了,為什麼在上述例子為什麼會出現的錯誤。但是子類又為什麼要先去執行父類的構造函數?
解釋:
因為子類繼承了父類的成員,這一項描述只能說明子類擁有的權利,並不代表子類去執行了。
在原則上要使用類的成員,必須要通過類的實例對象去調用。所以子類要調用到父類的成員,就必須去通過調用
父類的構造函數,在子類的內部創建一個父類的對象,以便自己去調用父類的成員。
總結:
子類始終要使用父類的一個構造函數在自己內部創建一個父類對象,為了調用父類的成員。
子類預設調用父類的無參構造函數,所以在顯示編寫一個有參構造函數時導致父類沒有了無參構造函數,從而編譯出錯。
在子類中使用顯示調用父類構造函數
作用1:
提高代碼重用性,子類無需在類中定義,直接使用父類的。
作用2:
上述例子講過子類在實例化對象時會調用父類的預設無參構造函數,因為子類的目的就是通過父類構造函數創建一個對象。
通過這樣顯示的調用,那麼在父類有沒有無參構造函數都沒什麼關係了。
子類中存在和父類中相同的成員
示例:
根據VS給我們提示的消息,我們可以看出,當代碼中存在子類的成員和父類的成員相同的時候,子類的成員將父類的成員隱藏了。
隱藏過後子類將無法訪問到父類的成員。如果是刻意為之,我們可以使用new 關鍵字顯示的說明,從而提高可讀性。
指定new關鍵字:
此時提示的波浪線已消除。
其他註意點
在C#中,所有的類都直接或間接的繼承自object類(當我們定義一個類的時候,如果沒有給該類指定繼承一個類,那麼這個類就繼承了object類)。