細說C#繼承


簡介

繼承(封裝、多態)是面向對象編程三大特性之一,繼承的思想就是擯棄代碼的冗余,實現更好的重用性。

繼承從字面上理解,無外乎讓人想到某人繼承某人的某些東西,一個給一個拿。這個語義在生活中,就像

家族繼承財產,爺爺將財產繼承給兒女,兒女在將財產繼承給子孫,有些東西可以繼承有些的東西只繼承給

某人。映射到編程當中,其思想也大致如此。


 通過示例引出繼承的作用

在代碼中定義個三個類: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類)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM