C#中的多态性
相信大家都對(duì)面向?qū)ο蟮娜齻€(gè)特征封裝、繼承、多態(tài)很熟悉,每個(gè)人都能說上一兩句,但是大多數(shù)都僅僅是知道這些是什么,不知道CLR內(nèi)部是如何實(shí)現(xiàn)的,所以本篇文章主要說說多態(tài)性中的一些概念已經(jīng)內(nèi)部實(shí)現(xiàn)的機(jī)理。 一、多態(tài)的概念 首先解釋下什么叫多態(tài):同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果,這就是多態(tài)性。換句話說,實(shí)際上就是同一個(gè)類型的實(shí)例調(diào)用“相同”的方法,產(chǎn)生的結(jié)果是不同的。這里的“相同”打上雙引號(hào)是因?yàn)檫@里的相同的方法僅僅是看上去相同的方法,實(shí)際上它們調(diào)用的方法是不同的。? 說到多態(tài),我們不能免俗的提到下面幾個(gè)概念:重載、重寫、虛方法、抽象方法以及隱藏方法。下面就來一一介紹他們的概念。 1、重載(overload):在同一個(gè)作用域(一般指一個(gè)類)的兩個(gè)或多個(gè)方法函數(shù)名相同,參數(shù)列表不同的方法叫做重載,它們有三個(gè)特點(diǎn)(俗稱兩必須一可以):
???????子類中的定義:
public override void EatFood(){Console.WriteLine("Cat吃東西");//base.EatFood();}
3、虛方法:即為基類中定義的允許在派生類中重寫的方法,使用virtual關(guān)鍵字定義。如: public virtual void EatFood(){Console.WriteLine("Animal吃東西");}
5、隱藏方法:在派生類中定義的和基類中的某個(gè)方法同名的方法,使用new關(guān)鍵字定義。如在基類Animal中有一方法Sleep(): public void Sleep(){Console.WriteLine("Animal Sleep");}
- 方法名必須相同
- 參數(shù)列表必須不相同
- 返回值類型可以不相同
?
????????2、重寫(override):子類中為滿足自己的需要來重復(fù)定義某個(gè)方法的不同實(shí)現(xiàn),需要用override關(guān)鍵字,被重寫的方法必須是虛方法,用的是virtual關(guān)鍵字。它的特點(diǎn)是(三個(gè)相同):- 相同的方法名
- 相同的參數(shù)列表
- 相同的返回值。
???????子類中的定義:
public override void EatFood(){Console.WriteLine("Cat吃東西");//base.EatFood();}
| tips:經(jīng)常有童鞋問重載和重寫的區(qū)別,而且網(wǎng)絡(luò)上把這兩個(gè)的區(qū)別作為C#做常考的面試題之一。實(shí)際上這兩個(gè)概念完全沒有關(guān)系,僅僅都帶有一個(gè)“重”字。他們沒有在一起比較的意義,僅僅分辨它們不同的定義就好了。 |
?
注意:虛方法也可以被直接調(diào)用。如: Animal a = new Animal();a.EatFood();?
運(yùn)行結(jié)果: 4、抽象方法:在基類中定義的并且必須在派生類中重寫的方法,使用abstract關(guān)鍵字定義。如: public abstract class Biology{public abstract void Live();}public class Animal : Biology{public override void Live(){Console.WriteLine("Animal重寫的抽象方法");//throw new NotImplementedException();} }?
注意:抽象方法只能在抽象類中定義,如果不在抽象類中定義,則會(huì)報(bào)出如下錯(cuò)誤:?????| 虛方法和抽象方法的區(qū)別是:因?yàn)槌橄箢悷o法實(shí)例化,所以抽象方法沒有辦法被調(diào)用,也就是說抽象方法永遠(yuǎn)不可能被實(shí)現(xiàn)。 |
?
????????????則在派生類Cat中定義隱藏方法的代碼為: new public void Sleep(){Console.WriteLine("Cat Sleep");}?
????????????或者為: public new void Sleep(){Console.WriteLine("Cat Sleep");}?
????????注意:(1)隱藏方法不但可以隱藏基類中的虛方法,而且也可以隱藏基類中的非虛方法。 (2)隱藏方法中父類的實(shí)例調(diào)用父類的方法,子類的實(shí)例調(diào)用子類的方法。 (3)和上一條對(duì)比:重寫方法中子類的變量調(diào)用子類重寫的方法,父類的變量要看這個(gè)父類引用的是子類的實(shí)例還是本身的實(shí)例,如果引用的是父類的實(shí)例那么調(diào)用基類的方法,如果引用的是派生類的實(shí)例則調(diào)用派生類的方法。 好了,基本概念講完了,下面來看一個(gè)例子,首先我們新建幾個(gè)類: public abstract class Biology{public abstract void Live();}public class Animal : Biology{public override void Live(){Console.WriteLine("Animal重寫的Live");//throw new NotImplementedException();}public void Sleep(){Console.WriteLine("Animal Sleep");}public int Sleep(int time){Console.WriteLine("Animal在{0}點(diǎn)Sleep", time);return time;}public virtual void EatFood(){Console.WriteLine("Animal EatFood");}}public class Cat : Animal{public override void EatFood(){Console.WriteLine("Cat EatFood");//base.EatFood();}new public void Sleep(){Console.WriteLine("Cat Sleep");}//public new void Sleep()//{// Console.WriteLine("Cat Sleep");//}}public class Dog : Animal{public override void EatFood(){Console.WriteLine("Dog EatFood");//base.EatFood();}}?
下面來看看需要執(zhí)行的代碼: class Program{static void Main(string[] args){//Animal的實(shí)例Animal a = new Animal();//Animal的實(shí)例,引用派生類Cat對(duì)象Animal ac = new Cat();//Animal的實(shí)例,引用派生類Dog對(duì)象Animal ad = new Dog();//Cat的實(shí)例Cat c = new Cat();//Dog的實(shí)例Dog d = new Dog();//重載a.Sleep();a.Sleep(23);//重寫和虛方法a.EatFood();ac.EatFood();ad.EatFood();//抽象方法a.Live();//隱藏方法a.Sleep();ac.Sleep();c.Sleep();Console.ReadKey();}}?
首先,我們定義了幾個(gè)我們需要使用的類的實(shí)例,需要注意的是 (1)Biology類是抽象類,無法實(shí)例化; (2)變量ac是Animal的實(shí)例,但是指向一個(gè)Cat的對(duì)象。因?yàn)镃at類型是Animal類型的派生類,所以這種轉(zhuǎn)換沒有問題。這也是多態(tài)性的重點(diǎn)。 下面我們來一步一步的分析: (1) //重載a.Sleep();a.Sleep(23);?
很明顯,Animal的變量a調(diào)用的兩個(gè)Sleep方法是重載的方法,第一句調(diào)用的是無參數(shù)的Sleep()方法,第二句調(diào)用的是有一個(gè)int 參數(shù)的Sleep方法。注意兩個(gè)Sleep方法的返回值不一樣,這也說明了重寫的三個(gè)特征中的最后一個(gè)特征——返回值可以不相同。 運(yùn)行的結(jié)果如下: (2) //重寫和虛方法a.EatFood();ac.EatFood();ad.EatFood();?
??????? 在這一段中,a、ac以及ad都是Animal的實(shí)例,但是他們引用的對(duì)象不同,a引用的是Animal對(duì)象,ac引用的是Cat對(duì)象,ad引用的是Dog對(duì)象,這個(gè)差別會(huì)造成執(zhí)行結(jié)果的什么差別呢,請(qǐng)看執(zhí)行結(jié)果: 第一句Animal實(shí)例,直接調(diào)用Animal的虛方法EatFood,沒有任何問題。 在第二、三句中,雖然同樣是Animal的實(shí)例,但是他們分別指向Cat和Dog對(duì)象,所以調(diào)用的Cat類和Dog類中各自重寫的EatFood方法,就像是Cat實(shí)例和Dog實(shí)例直接調(diào)用EatFood方法一樣。這個(gè)也就是多態(tài)性的體現(xiàn):同一操作作用于不同的對(duì)象,可以有不同的解釋,產(chǎn)生不同的執(zhí)行結(jié)果。 (3) //抽象方法a.Live();?
這個(gè)比較簡單,就是直接重寫父類Biology中的Live方法,執(zhí)行結(jié)果如下: (4) //隱藏方法a.Sleep();ac.Sleep();c.Sleep();?
在分析隱藏方法時(shí)要和虛方法、重寫相互比較。變量 a 調(diào)用 Animal 類的 Sleep 方法以及變量 c 調(diào)用 Cat 類的 Sleep 方法沒有異議,但是變量 ac 引用的是一個(gè) Cat 類型的對(duì)象,它應(yīng)該調(diào)用 Animal 類型的 EatFood 方法呢,還是 Cat 類型的 EatFood 方法呢?答案是調(diào)用父類即Animal的EatFood方法。執(zhí)行結(jié)果如下: 大多數(shù)的文章都是介紹到這里為止,僅僅是讓我們知道這些概念以及調(diào)用的方法,而沒有說明為什么會(huì)這樣。下面我們就來深入一點(diǎn),談?wù)劧鄳B(tài)背后的機(jī)理。 二、深入理解多態(tài)性 要深入理解多態(tài)性,就要先從值類型和引用類型說起。我們都知道值類型是保存在線程棧上的,而引用類型是保存在托管堆中的。因?yàn)樗械念惗际且妙愋?#xff0c;所以我們僅僅看引用類型。 現(xiàn)在回到剛才的例子,Main函數(shù)時(shí)程序的入口,在JIT編譯器將Main函數(shù)編譯為本地CPU指定時(shí),發(fā)現(xiàn)該方法引用了Biology、Animal、Cat、Dog這幾個(gè)類,所以CLR會(huì)創(chuàng)建幾個(gè)實(shí)例來表示這幾個(gè)類型本身,我們把它稱之為“類型對(duì)象”。該對(duì)象包含了類中的靜態(tài)字段,以及包含類中所有方法的方法表,還包含了托管堆中所有對(duì)象都要有的兩個(gè)額外的成員——類型對(duì)象指針(Type Object Point)和同步塊索引(sync Block Index)。 可能上面這段對(duì)于有些沒有看過相關(guān)CLR書籍的童鞋沒有看懂,所以我們畫個(gè)圖來描述一下: 上面的這個(gè)圖是在執(zhí)行Main函數(shù)之前CLR所做的事情,下面開始執(zhí)行Main函數(shù)(方便起見,簡化一下Main函數(shù)): //Animal的實(shí)例Animal a = new Animal();//Animal的實(shí)例,引用派生類Cat對(duì)象Animal ac = new Cat();//Animal的實(shí)例,引用派生類Dog對(duì)象Animal ad = new Dog();a.Sleep();a.EatFood();ac.EatFood();ad.EatFood();?
下面實(shí)例化三個(gè)Animal實(shí)例,但是他們實(shí)際上指向的分別是Animal對(duì)象、Cat對(duì)象和Dog對(duì)象,如下圖: 請(qǐng)注意,變量ac和ad雖然都是Animal類型,但是指向的分別是Cat對(duì)象和Dog對(duì)象,這里是關(guān)鍵。 當(dāng)執(zhí)行a.Sleep()時(shí),由于Sleep是非虛實(shí)例方法,JIT編譯器會(huì)找到發(fā)出調(diào)用的那個(gè)變量(a)的類型(Animal)對(duì)應(yīng)的類型對(duì)象(Animal類型對(duì)象)。然后調(diào)用該類型對(duì)象中的Sleep方法,如果該類型對(duì)象沒有Sleep方法,JIT編譯器會(huì)回溯類的基類(一直到Object)中查找Sleep方法。 當(dāng)執(zhí)行ac.EatFood時(shí),由于EatFood是虛實(shí)例方法,JIT編譯器調(diào)用時(shí)會(huì)在方法中生成一些額外的代碼,這些代碼會(huì)首先檢查發(fā)出調(diào)用的變量(ac),然后跟隨變量的引用地址找到發(fā)出調(diào)用的對(duì)象(Cat對(duì)象),找到發(fā)出調(diào)用的對(duì)象對(duì)應(yīng)的類型對(duì)象(Cat類型對(duì)象),最后在該類型對(duì)象中查找EatFood方法。同樣的,如果在該類型對(duì)象中沒有查找到EatFood方法,JIT編譯器會(huì)回溯到該類型對(duì)象的基類中查找。 上面描述的就是JIT編譯器在遇到調(diào)用類型的非虛實(shí)例方法以及虛實(shí)例方法時(shí)的不同執(zhí)行方式,也這是處理這兩類方法的不同方式造成了表面上我們看到的面向?qū)ο蟮娜齻€(gè)特征之一——多態(tài)性。 好了,本篇博文開始回顧了一些關(guān)于多態(tài)性的基本概念,然后解釋了多態(tài)性的內(nèi)部機(jī)理。內(nèi)部JIT編譯器的部分基本是參照《CLR via C#》書中的第四章的內(nèi)容,有這本書的同學(xué)可以回去翻翻看看。寫的不好的地方,請(qǐng)大家批評(píng)指正。移步我最優(yōu)惠網(wǎng),盡享全網(wǎng)折扣商品。
PS:我最優(yōu)惠網(wǎng)是一個(gè)匯集京東、天貓、Amazon等知名電商優(yōu)惠活動(dòng)和商品的網(wǎng)站,更精明的剁手,從我最優(yōu)惠開始
轉(zhuǎn)載于:https://www.cnblogs.com/yachao1120/p/6551367.html
總結(jié)
- 上一篇: 创建Mat对象
- 下一篇: 什么是FOF 专门投资于其他证券投资基金