目錄:
【C#小知識】C#中一些易混淆概念總結--------數據類型存儲位置,方法調用,out和ref參數的使用
----------------------------------分割線--------------------------------------
這次主要分享的內容是關於繼承的知識。
首先,我們先來看看繼承;
既然有繼承,就要有父類和子類,來看下面的一段代碼:
class Person { private int nAge; protected string strName; double douHeight; public string strEateType; public void Hello() { Console.WriteLine("我可以說Hello!"); } public void Run() { Console.WriteLine("我可以跑!"); } } class Student : Person { }
然後我在Main()函數中實例化子類的對象,代碼如下:
static void Main(string[] args) { Student stu1 = new Student(); }
那麼在這個過程中內存中發生了些什麼呢?
我們先來看misl的中間代碼,看看那能發現些什麼
由此我們可以發現子類繼承了父類的所有成員包括Private和Protect,並為這些成員開辟了空間來存儲。
我們再來實例化我們的子類,然後訪問父類的字段和方法,會發現,如下的現象
所以雖然子類為父類的所有成員在堆中都開辟了空間,但是私有成員(Private)和受保護的成員(protected)訪問不到。
所以在內存中的情況如下圖:
看下面的代碼,我們來探究一下在子類中this關鍵字和base關鍵字所訪問的類的成員有哪些,代碼如下:
class Student : Person { private string strClass; private string strAddress; public void Address(string cla, string adre) { //這裡的this關鍵字調用了子類的成員和父類的非似有成員 this.strClass = "五"; this.strAddress = "北京"; this.strName = "子強"; //這裡base關鍵字調用了是父類的非似有成員 base.strName = "強子"; Console.WriteLine("我是{0}年紀,來自{1}", cla, adre); } public void Sing() { this.strClass = ""; Console.WriteLine("我可以唱歌!"); } }
所以在子類中this關鍵字和base關鍵字的訪問范圍的示意圖如下:
二,關於子類對象的構造函數和父類構造函數的執行順序
我們分別為父類和子類添加顯式的構造函數,代碼如下
class Person { private int nAge; protected string strName; double douHeight; public string strEateType; //父類的構造函數 public Person() { Console.WriteLine("我是父類的構造函數"); } public void Hello() { Console.WriteLine("我可以說Hello!"); } public void Run() { Console.WriteLine("我可以跑!"); } } class Student : Person { private string strClass; private string strAddress; //子類的構造函數 public Student () { Console.WriteLine("我是子類的構造函數"); } }
我們使用VS的單步調試,來看父類和子類顯式構造函數的執行順序,如下圖(動態圖片,可以看到過程):
很容易的可以發現,當創建子類對象的時候
①先調用了子類的構造函數
②調用了父類的構造函數
③執行了父類的構造函數
④執行了子類的構造函數
那麼為什麼會這樣呢?
我嘗試通過反編譯看源碼來解釋這個原因,但是反編譯的結果如下,
沒有發現有什麼特別的地方可以解釋這個原因。
最後還是查閱微軟的MSDN官方文檔找到了答案(原文地址點擊這裡)
根據微軟官方的代碼示例,那麼下面的代碼的效果也是相同的
//子類的構造函數 public Student () { Console.WriteLine("我是子類的構造函數"); } //這裡的代碼和上面的代碼效果是相同的 public Student() :base() { Console.WriteLine("我是子類的構造函數"); }
也就是說只要在子類顯式的聲明了無參的構造函數,在實例化子類的對象是,子類的無參構造函數都會去調用父類無參的構造函數。
那麼,如果父類沒有這個無參的構造函數則會報錯。
如下面的代碼:
class Person { private int nAge; protected string strName; double douHeight; public string strEateType; //父類的構造函數 //public Person() //{ // Console.WriteLine("我是父類的構造函數"); //} //父類的有參數的構造函數,這裡覆蓋了無參的構造函數 public Person (string str) { Console.WriteLine("我是父類的構造函數{0}",str); } public void Hello() { Console.WriteLine("我可以說Hello!"); } public void Run() { Console.WriteLine("我可以跑!"); } } class Student : Person { private string strClass; private string strAddress; //子類的無參構造函數 public Student () { Console.WriteLine("我是子類的構造函數"); } public Student(string strName) { Console.WriteLine("我的名字叫{0}",strName); } }
這時候編譯會報錯,
因為在父類中有參數的構造函數覆蓋了無參數的構造函數,所以在子類的無參數的構造函數沒辦法回調父類的無參數的構造函數初始化父類的成員變量。所以報錯。
那麼在初始化子類的時候,為什麼要調用父類的構造函數呢?
在初始化子類之前需要通過構造函數初始化父類的成員變量
父類的構造函數先於子類的構造函數執行的意義是什麼呢?
當在父類的構造函數中和子類的構造函數中為父類的非私有成員變量賦不同默認值。當實例化子類,子類要調用構造函數初始化成員變量,如果先執行了子類的構造函數,再執行父類的構造函數,父類成員字段的值會覆蓋子類成員字段的值。但是我們想得到的是子類的屬性值。所以為了解決數據沖突,父類的構造函數要先於子類的構造函數執行。
如下面的代碼:
class Person { private int nAge; private string strName; double douHeight; public string strEateType; // 父類的構造函數 public Person() { //再父類中對strEateType賦初始值 this.strEateType = "吃飯"; Console.WriteLine("我是父類的構造函數{0}", strEateType); } } class Student : Person { private string strClass; private string strAddress; //子類的構造函數 public Student() { //在子類中對strEateType賦初始值 this.strEateType = "吃面條"; Console.WriteLine("我是子類的構造函數{0}",strEateType); } }
這時候我們通過,聲明子類對象訪問strEateType的值,如下:
Student stu1 = new Student(); //stu1. string str = stu1.strEateType.ToString(); Console.WriteLine(str); Console.ReadKey();
這裡肯定是要打印出子類的屬性strEateType的值,如果先執行子類構造函數對strEateType賦值,然後父類的構造函數賦值覆蓋strEateType的初始值。那麼打印出的將是父類成員字段的值。所以,父類的構造函數先於子類的構造函數執行。
打印結果如下:
三,子類是否可以有和父類的同名方法
看下面的代碼,我們聲明一個父類Person:
class Person { private int nAge; private string strName; double douHeight; public string strEateType; public readonly string strrrr; // 父類的構造函數 public Person() { this.strEateType = "吃飯"; Console.WriteLine("我是父類的構造函數{0}", strEateType); } public Person(string str) { this.strName = str; Console.WriteLine("我是父類的構造函數{0}", str); } public void Hello() { Console.WriteLine("我可以說地球人的Hello!"); } public void Run() { Console.WriteLine("我可以跑!"); } }
聲明一個子類繼承Person,代碼如下:
class Worker:Person { public void Hello() { Console.WriteLine("我是工人會說Hello!"); } public new void Run() { Console.WriteLine("我是工人我會奔跑!"); } }
然後實例化Worker對象,打印Hello方法,結果如下圖:
這是為什麼呢?編譯器已經告訴了我們,如下圖:
看出來是子類的方法隱藏了父類的方法。
既然子類可以定義和父類同名的方法,那麼是否可以定同名的字段呢?答案是肯定的,而且會像同名方法一樣,子類同名字段會隱藏父類同名的字段。
畢業實習交流群:221376964。你也可以關注我的新浪微博進行交流。