天天看點

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

目錄:

<a href="http://yisuowushinian.blog.51cto.com/4241271/1352834" target="_blank">【C#小知識】C#中一些易混淆概念總結--------資料類型存儲位置,方法調用,out和ref參數的使用</a>

<a href="http://www.cnblogs.com/qq731109249/p/3525271.html" target="_blank"></a>

----------------------------------分割線--------------------------------------

這次主要分享的内容是關于繼承的知識。

首先,我們先來看看繼承;

既然有繼承,就要有父類和子類,來看下面的一段代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<code>class</code> <code>Person</code>

<code>    </code><code>{</code>

<code>        </code><code>private</code> <code>int</code> <code>nAge;</code>

<code>        </code><code>protected</code> <code>string</code> <code>strName;</code>

<code>        </code><code>double</code> <code>douHeight;</code>

<code>        </code><code>public</code> <code>string</code> <code>strEateType;</code>

<code>        </code><code>public</code> <code>void</code> <code>Hello()</code>

<code>        </code><code>{</code>

<code>            </code><code>Console.WriteLine(</code><code>"我可以說Hello!"</code><code>);</code>

<code>        </code><code>}</code>

<code>        </code><code>public</code> <code>void</code> <code>Run()</code>

<code>            </code><code>Console.WriteLine(</code><code>"我可以跑!"</code><code>);</code>

<code>    </code><code>}</code>

<code>    </code><code>class</code> <code>Student : Person</code>

然後我在Main()函數中執行個體化子類的對象,代碼如下:

staticvoid Main(string[] args)        

{        

   Student stu1 = new Student();    

  }

那麼在這個過程中記憶體中發生了些什麼呢?

我們先來看misl的中間代碼,看看那能發現些什麼

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

由此我們可以發現子類繼承了父類的所有成員包括Private和Protect,并為這些成員開辟了空間來存儲。

我們再來執行個體化我們的子類,然後通路父類的字段和方法,會發現,如下的現象

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

是以雖然子類為父類的所有成員在堆中都開辟了空間,但是父類的私有成員(Private)子類通路不到,

而受保護的成員(protected)可以通過執行個體化對象通路的到。

是以在記憶體中的情況如下圖:

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

看下面的代碼,我們來探究一下在子類中this關鍵字和base關鍵字所通路的類的成員有哪些,代碼如下:

19

20

<code>class</code> <code>Student : Person</code>

<code>        </code><code>private</code> <code>string</code> <code>strClass;</code>

<code>        </code><code>private</code> <code>string</code> <code>strAddress;</code>

<code>        </code><code>public</code> <code>void</code> <code>Address(</code><code>string</code> <code>cla, </code><code>string</code> <code>adre)</code>

<code>            </code><code>//這裡的this關鍵字調用了子類的成員和父類的非似有成員</code>

<code>            </code><code>this</code><code>.strClass = </code><code>"五"</code><code>;</code>

<code>            </code><code>this</code><code>.strAddress = </code><code>"北京"</code><code>;</code>

<code>            </code><code>this</code><code>.strName = </code><code>"子強"</code><code>;</code>

<code>            </code><code>//這裡base關鍵字調用了是父類的非似有成員</code>

<code>            </code><code>base</code><code>.strName = </code><code>"強子"</code><code>;</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是{0}年紀,來自{1}"</code><code>, cla, adre);</code>

<code>        </code><code>public</code> <code>void</code> <code>Sing()</code>

<code>            </code><code>this</code><code>.strClass = </code><code>""</code><code>;</code>

<code>            </code><code>Console.WriteLine(</code><code>"我可以唱歌!"</code><code>);</code>

是以在子類中this關鍵字和base關鍵字的通路範圍的示意圖如下:

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

二,關于子類對象的構造函數和父類構造函數的執行順序

我們分别為父類和子類添加顯式的構造函數,代碼如下

21

22

23

24

25

26

27

28

29

30

<code>        </code><code>//父類的構造函數</code>

<code>        </code><code>public</code> <code>Person()</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是父類的構造函數"</code><code>);</code>

<code>        </code><code>//子類的構造函數</code>

<code>        </code><code>public</code> <code>Student ()</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是子類的構造函數"</code><code>);</code>

我們使用VS的單步調試,來看父類和子類顯式構造函數的執行順序,如下圖(動态圖檔,可以看到過程):

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

很容易的可以發現,當建立子類對象的時候

①先調用了子類的構造函數

②調用了父類的構造函數

③執行了父類的構造函數

④執行了子類的構造函數

那麼為什麼會這樣呢?

我嘗試通過反編譯看源碼來解釋這個原因,但是反編譯的結果如下,

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

沒有發現有什麼特别的地方可以解釋這個原因。

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

根據微軟官方的代碼示例,那麼下面的代碼的效果也是相同的

<code>//子類的構造函數</code>

<code>//這裡的代碼和上面的代碼效果是相同的</code>

<code>        </code><code>public</code> <code>Student()</code>

<code>            </code><code>:</code><code>base</code><code>()</code>

也就是說隻要在子類顯式的聲明了無參的構造函數,在執行個體化子類的對象是,子類的無參構造函數都會去調用父類無參的構造函數。

那麼,如果父類沒有這個無參的構造函數則會報錯。

如下面的代碼:

31

32

33

34

35

36

37

38

39

<code>        </code><code>//public Person()</code>

<code>        </code><code>//{</code>

<code>        </code><code>//    Console.WriteLine("我是父類的構造函數");</code>

<code>        </code><code>//}</code>

<code>      </code><code>//父類的有參數的構造函數,這裡覆寫了無參的構造函數</code>

<code>        </code><code>public</code> <code>Person (</code><code>string</code> <code>str)</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是父類的構造函數{0}"</code><code>,str);</code>

<code>        </code><code>//子類的無參構造函數</code>

<code>        </code><code>public</code> <code>Student(</code><code>string</code> <code>strName)</code>

<code>            </code><code>Console.WriteLine(</code><code>"我的名字叫{0}"</code><code>,strName);</code>

這時候編譯會報錯,

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

因為在父類中有參數的構造函數覆寫了無參數的構造函數,是以在子類的無參數的構造函數沒辦法回調父類的無參數的構造函數初始化父類的成員變量。是以報錯。

那麼在初始化子類的時候,為什麼要調用父類的構造函數呢?

在初始化子類之前需要通過構造函數初始化父類的成員變量

父類的構造函數先于子類的構造函數執行的意義是什麼呢?

當在父類的構造函數中和子類的構造函數中為父類的非私有成員變量賦不同預設值。當執行個體化子類,子類要調用構造函數初始化成員變量,如果先執行了子類的構造函數,再執行父類的構造函數,父類成員字段的值會覆寫子類成員字段的值。但是我們想得到的是子類的屬性值。是以為了解決資料沖突,父類的構造函數要先于子類的構造函數執行。

<code>        </code><code>private</code> <code>string</code> <code>strName;</code>

<code>       </code><code>// 父類的構造函數</code>

<code>            </code><code>//再父類中對strEateType賦初始值</code>

<code>            </code><code>this</code><code>.strEateType = </code><code>"吃飯"</code><code>;</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是父類的構造函數{0}"</code><code>, strEateType);</code>

<code>            </code><code>//在子類中對strEateType賦初始值</code>

<code>            </code><code>this</code><code>.strEateType = </code><code>"吃面條"</code><code>;</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是子類的構造函數{0}"</code><code>,strEateType);</code>

這時候我們通過,聲明子類對象通路strEateType的值,如下:

<code>Student stu1 = </code><code>new</code> <code>Student();</code>

<code>            </code><code>//stu1.</code>

<code>            </code><code>string</code> <code>str = stu1.strEateType.ToString();</code>

<code>            </code><code>Console.WriteLine(str);</code>

<code>            </code><code>Console.ReadKey();</code>

這裡肯定是要列印出子類的屬性strEateType的值,如果先執行子類構造函數對strEateType指派,然後父類的構造函數指派覆寫strEateType的初始值。那麼列印出的将是父類成員字段的值。是以,父類的構造函數先于子類的構造函數執行。

列印結果如下:

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

三,子類是否可以有和父類的同名方法

看下面的代碼,我們聲明一個父類Person:

<code>       </code><code>public</code>  <code>readonly</code> <code>string</code> <code>strrrr;</code>

<code>        </code><code>// 父類的構造函數</code>

<code>        </code><code>public</code> <code>Person(</code><code>string</code> <code>str)</code>

<code>            </code><code>this</code><code>.strName = str;</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是父類的構造函數{0}"</code><code>, str);</code>

<code>            </code><code>Console.WriteLine(</code><code>"我可以說地球人的Hello!"</code><code>);</code>

聲明一個子類繼承Person,代碼如下:

<code>class</code> <code>Worker:Person</code>

<code>        </code><code>public</code> <code>void</code>  <code>Hello()</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是勞工會說Hello!"</code><code>);</code>

<code>        </code><code>public</code> <code>new</code> <code>void</code>  <code>Run()</code>

<code>            </code><code>Console.WriteLine(</code><code>"我是勞工我會奔跑!"</code><code>);</code>

然後執行個體化Worker對象,列印Hello方法,結果如下圖:

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

這是為什麼呢?編譯器已經告訴了我們,如下圖:

【C#小知識】C#中一些易混淆概念總結(五)---------深入解析C#繼承

看出來是子類的方法隐藏了父類的方法。

既然子類可以定義和父類同名的方法,那麼是否可以定同名的字段呢?答案是肯定的,而且會像同名方法一樣,子類同名字段會隐藏父類同名的字段。

     本文轉自yisuowushinian 51CTO部落格,原文連結:http://blog.51cto.com/yisuowushinian/1356975,如需轉載請自行聯系原作者