前言
看過我之前復習的隨筆知道都是基礎之上的語法,但是當我腦海開啟回憶基礎知識時,尤其是構造函數中先后執行順序以及原因卻是模棱兩可,於是開始邊編寫邊操筆來記敘下來。如果你正在學習基礎語法或者是復習基礎語法的路上,這篇文章或許對你亦有幫助(當然msdn也有相關定義,但是個人覺得要是看完定義后再去摸索下,或許會理解的更透徹吧)。【特此注意:高手請繞過道而走!】
繼承之構造函數
首先我們定義一個Person類,並給個構造函數。代碼如下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public Person() { Console.WriteLine("父類構造函數"); } }
再寫個Bob類繼承該父類,同時給個構造函數,其代碼如下:
public class Bob : Person { public Bob() { Console.WriteLine("子類構造函數"); } }
接下來就在控制台實例化Bob類 Bob b = new Bob(); 看調用構造函數先后執行順序。結果輸出如下:
從這輸出來看你會不會妄下結論說先調用的父類構造函數再調用子類的構造函數呢?如果你這樣說的話,也就是說當我們實例化對象子類時,但是它去執行了父類的構造函數,好像有點神奇。好吧,我們接下來繼續看,我們再定義一個Student類繼承該Bob類。代碼如下:
public class Student : Bob { public Student() { Console.WriteLine("學生類構造函數"); } }
然后在控制台實例化Student類 Student s = new Student(); ,結果打印出:
依然是調用的父類構造函數,所以現在就下結論:子類繼承父類,實例化子類對象,首先調用的是父類構造函數。好,結論似乎言之過早,我們來斷點調試下不就可以得出答案所在了嗎。 看下面圖片,一步步斷點截圖:
第一步:不用說
第二步:調用子類構造函數即Bob類
第三步:調用父類構造函數即Person類
第四步:執行父類的構造函數
最后一步:執行子類構造函數
所以總結如下:子類繼承父類時構造函數執行的先后順序:
- 調用子類構造函數
- 調用父類構造函數
- 執行父類構造函數
- 執行子類構造函數
但是此時問題來了,為什么我們實例化子類對象時,最后去調用了父類的構造函數?其實是有道理可循的,我忘記了base關鍵字的存在,當你在子類編寫構造函數時,如果沒有顯式的調用基類的構造函數,那么會默認在其后面添加 一個 :base() 以此來調用基類的無參構造函數。所以上述問題就解決了。
msdn上所說base關鍵字概念有兩點:
-
調用基類上已被其他方法重寫的方法。
-
指定創建派生類實例時應調用的基類構造函數。
接下來如果我們在父類中將無參構造函數修改為有參構造函數會怎樣呢,這是我們再生成出現如下錯誤:
因為上面已經說的很清楚了,如果父類沒有無參構造函數,但是子類默認會帶調用父類無參的構造函數所以會出錯。所以解決辦法 就是在子類中構造函數顯示的用 :base 來調用父類有參構造函數即可,具體不再演示。
但是問題又來了:為什么要讓父類構造函數優先於子類構造函數執行呢??????請看下面給出合理解釋。
我們將整個代碼進行整合如下來討論下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public Person(string name,int age) { Console.WriteLine("父類構造函數"); } } public class Bob : Person { public Bob(string name,int age):base(name,11) (1) { this.Name = name; this.Age = age; Console.WriteLine("子類構造函數"); } } class Program { static void Main(string[] args) { Bob b = new Bob("1",12); (2) Console.ReadKey(); } }
我們只需看上述代碼中標記為紅色的(1)和(2),如果我們現在實例化Bob類並傳參 age = 12 ,此時我們當然希望的是Bob類中age = 12,如果此時先執行子類那完了,因為有 :base(name,11) ,所以此時的 age = 11 ,這樣不就造成了意想不到的結果了嗎,明明傳的12,結果為11,結果數據產生嚴重的沖突,也就是數據的不一致。如果先執行父類構造函數,此時子類age為11,但是當執行到子類構造函數時,因為我傳的是12所以其age就為12。這才是我們需要的結果。
靜態構造函數
我們繼續就上述例子中的Person類為例進行分析,代碼如下:
public class Person { public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public Person() { Console.WriteLine("實例構造函數"); } static Person() { Console.WriteLine("靜態構造函數"); } }
為了更好演示,我們實例化對象兩次代碼如下:
Person p = new Person();
Person p1 = new Person();
控制台運行結果如下:
從上述顯示結果中至少可以看出:在調用構造函數之前就已經調用了靜態構造函數。 那么我們比調用構造函數更早的時期是什么時候呢?啊,容我想想,直接聲明該對象的變量(即直接第一次在加載該類下的所有成員時),不實例化就可以了。於是乎我們直接在控制台中聲明變量即可,如下:
Person p2;
運行,結果是什么都沒有如下:
說明此時未調用靜態構造函數, 那就是在此之后,現在是調用實例構造函數之前即對象不能實例化,也就是說訪問實例成員指定也是不行了,那么要是如果訪問靜態成員,看行不行,我們在Person類中添加
public static int age;
現在我們在控制台來訪問該靜態成員 Person.age = 1; 結果運行如下:
說明靜態構造函數: 在類的成員第一次被訪問之前,就會調用靜態構造函數 。 如果還是不能理解我們直接這樣做,在Person類中定義一個已經賦值的字段。如下
public int temp = 0;
此時我們在控制台中實例化對象 Person p = new Person(); ,再運行下看看結果會怎樣呢?一步步調試:
第一步:顯然沒有調用
第二步執行到賦值的字段時:
同樣也證明了上面的結果。
補充
如果對靜態成員不太理解下面就靜態成員定義以及靜態成員和實例成員區別做一點概括吧。
靜態成員
靜態成員在類第一次加載時被創建。
靜態成員只會被創建一次,所以有且僅有一份。
靜態成員創建在靜態存儲區中,所以一旦被創建直到程序退出才會被回收。
靜態成員與實例成員區別
生命周期:靜態成員是從類第一次被加載時到程序完全退出時,但是實例成員則是在對象被創建時到該對象成為垃圾被垃圾回收器回收時。
存儲位置:靜態成員存儲在靜態存儲區中,實例成員存儲在堆空間相應的對象中。