今天開始上班了。這幾天研究學習了一下思維導圖,感覺用它整理自己的知識非常的方便。所以,以後寫部落格完成一個知識點,都會用思維導圖做一個總結。也能讓大家對所要讀的內容有一個整體的把握。
我用的思維導圖軟體是FreeMind(免費的,但是得裝JDK),因為剛開始學習使用,很多操作技巧不是很熟練,做出來的導圖估計也不是很好,希望大家見諒。
首先,里氏替換原則。
這是理解多型所必須掌握的內容。對於里氏替換原則維基百科給出的定義如下:
為什麼子類可以替換父類的位置,而程式的功能不受影響呢?
當滿足繼承的時候,父類肯定存在非私有成員,子類肯定是得到了父類的這些非私有成員(假設,父類的的成員全部是私有的,那麼子類沒辦法從父類繼承任何成員,也就不存在繼承的概念了)。既然子類繼承了父類的這些非私有成員,那麼父類物件也就可以在子類物件中呼叫這些非私有成員。所以,子類物件可以替換父類物件的位置。
來看下面的一段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
class Program { static void Main(string[] args) { Person p = new Person(); Person p1 = new Student(); Console.ReadKey(); } } class Person { //父類的私有成員 private int nAge; public Person() { Console.WriteLine("我是Person建構函式,我是一個人!"); } public void Say() { Console.WriteLine("我是一個人!"); } } class Student : Person { public Student() { Console.WriteLine("我是Student建構函式,我是一個學生!"); } public void SayStude() { Console.WriteLine("我是一個學生!"); } } class SeniorStudent : Student { public SeniorStudent() { Console.WriteLine("我是SeniorStudent建構函式,我是一個高中生!"); } public void SaySenior() { Console.WriteLine("我是一個高中生!"); } } |
我們執行列印出的結果是:
根據前面的建構函式的知識很容易解釋這個結果。那麼我們在Main()函式中新增如下的程式碼:
1 2 3 4 5 6 7 8 9 |
static void Main(string[] args) { Person p = new Person(); p.Say(); Person p1 = new Student(); p1.Say(); Console.ReadKey(); } |
在訪問的過程中,可以發現p只可以訪問父類的say
而p1也只可以訪問父類的Say方法
其實在上面的程式碼中,就滿足了里氏替換原則。子類的Student物件,替換了父類Person物件的位置。
那麼它們在記憶體中發生了些什麼呢?如下圖:
由上可以知道,當一個父類的變數指向一個子類物件的時候只能通過這個父類變數呼叫父類成員,子類獨有的成員無法呼叫。
同理我們可以推理出,子類的變數是不可以指向一個父類的對像的
但是當父類變數指向一個子類變數的時候,可以不可以把父類的變數轉化成子類的物件呢?看下圖
關於引用型別的兩種轉換方式:
由上面的程式碼我們已經知道了一種轉換,就是在變數錢直接加需要轉換的型別,如下程式碼:
1 |
Student s2 = (Student)p1; |
那麼第二種轉換方式就是使用as關鍵字,如下程式碼:
1 2 3 4 5 |
//將指向子類物件的變數轉化成子類型別 Student s2 = (Student)p1; //使用as關鍵字,轉換失敗返回一個null值 Student s3 = p1 as Student; |
使用as關鍵字和第一種強制轉換的區別就是,第一種如果轉換失敗會拋異常,第二種轉換失敗則返回一個null值。
思維導圖總結如下:
二,虛方法
使用virtual關鍵字修飾的方法,叫做虛方法(一般都是在父類中)。
看下面的一段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class Person { private int nAge; public Person() { Console.WriteLine("我是Person建構函式,我是一個人!"); } //這裡定義了一個虛方法 public virtual void Say() { Console.WriteLine("我是一個人!"); } } class Student : Person { //子類使用override關鍵字改寫了父類的虛方法 public override void Say() { Console.WriteLine("我是一個學生!"); } public Student() { Console.WriteLine("我是Student建構函式,我是一個學生!"); } public void SayStude() { Console.WriteLine("我是一個學生!"); } } |
緊接著在main()函式中新增如下的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
static void Main(string[] args) { Person p = new Person(); p.Say(); Person p1 = new Student(); p1.Say(); Student s = new Student(); s.Say(); Console.ReadKey(); } |
列印結果如下:
我們很明顯的可以發現,第二個表示式滿足里氏替換原則,p1.Say()執行的應該是父類的Say()方法,但是這裡卻執行了子類的Say()方法。
這就是子類使用override關鍵字的Say()方法覆蓋了父類的用Virtual關鍵字修飾的Say()方法。
我們使用動態圖片看一下除錯過程,
①首先是沒有使用任何關鍵字:
由上可以看出直接跳入父類,執行了父類的Say()方法;
②再看使用virtual和override關鍵字的動態除錯圖片,如下:
可以看到直接到子類去執行override關鍵字修飾的Say()方法。
那麼如果父類使用virtual關鍵字修飾,而子類沒有重寫該方法時會怎麼樣呢?如下面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class Program { static void Main(string[] args) { Person p1 = new Student(); p1.Say(); Console.ReadKey(); } } class Person { private int nAge; public Person() { Console.WriteLine("我是Person建構函式,我是一個人!"); } //這裡定義了一個虛方法 public virtual void Say() { Console.WriteLine("我是一個人!"); } } class Student : Person { //子類中沒有出現override關鍵字修飾的方法 public void SayStude() { Console.WriteLine("我是一個學生!"); } } |
執行結果如下:
所以,如果子類找不到override方法,則會回溯到該子類的父類去找是否有override方法,知道回溯到自身的虛方法,並執行。
虛方法知識總結的思維導圖如下: