簡介編程
繼承(封裝、多態)是面向對象編程三大特性之一,繼承的思想就是擯棄代碼的冗餘,實現更好的重用性。函數
繼承從字面上理解,無外乎讓人想到某人繼承某人的某些東西,一個給一個拿。這個語義在生活中,就像this
家族繼承財產,爺爺將財產繼承給兒女,兒女在將財產繼承給子孫,有些東西能夠繼承有些的東西只繼承給spa
某人。映射到編程當中,其思想也大體如此。調試
經過示例引出繼承的做用code
在代碼中定義個三個類:Cat貓、Dog狗、Cattle牛。對象
從類圖上能夠看出紅色標識區域,三個類的定義出現了大量的冗餘(字段、屬性、方法),那麼在編寫代碼時就會出現大量的重複代碼。blog
試想一下,隨着業務功能的擴展,可能會出現更多類,那麼冗餘(重複的代碼)會更多。好比出現一樣會形成冗餘的類:繼承
Pig豬、Panda熊貓、Sheep羊......等等。這些類一樣會有相同的特徵:名稱、性別、年齡、奔跑(字段、屬性、方法)。get
如何解決此類冗餘問題 —— 使用繼承
繼承的思想:
當咱們定義了多個類,這多個類都存在重複的成員(共性)。咱們能夠將這些重複的成員單獨的提取封裝到一個類中,做爲這些具備相同特徵類的父類。
將此思想做用於上述的三個類
提取共性:能夠直觀看出重複的具備共性的項目有: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類)。