來自http://blog.csdn.net/softart/archive/2007/10/27/1846041.aspx程序員
多態(Polymorphism)是面向對象(Object-Oriented,OO)思想"三大特徵"之一,其他兩個分別是封裝(Encapsulation)和繼承(Inheritance)--可見多態的重要性。或者說,不懂得什麼是多態就不能說懂得面向對象。面試
多態是一種機制、一種能力,而非某個關鍵字。它在類的繼承中得以實現,在類的方法調用中得以體現。編程
先讓咱們看看MSDN裏給出的定義:ide
Through inheritance, a class can be used as more than one type; it can be used as its own type, any base types, or any interface type if it implements interfaces. This is called polymorphism. In C#, every type is polymorphic. Types can be used as their own type or as a Object instance, because any type automatically treats Object as a base type.函數
譯文:經過繼承,一個類能夠被看成不止一個數據類型(type)使用,它能夠被用作自身表明的數據類型(這是最經常使用的),還能夠被看成它的任意基類所表明的數據類型,乃至任意接口類型--前提是這個類實現了這個接口。這一機制稱爲"多態"。在C#中,全部的數據類型都是多態的。任意一個數據類型均可以被看成自身來使用,也能夠看成Object類型來使用(我懷疑原文有問題,那個instance多是原做者的筆誤),由於任何數據類型都自動以Object爲本身的基類。學習
呵呵,除非你已經早就知道了什麼是多態而後翻過頭來看上面一段話,否則我敢打保票--我是清清楚楚的,你是稀裏糊塗的。OK,不難爲你們了,我用幾個句子說明一下多態的思想。ui
咱們先把前文中提到的"接口"理解爲"一組功能的集合",把"類"理解爲功能的實現體。這樣的例子多了去了。咱們就拿生物界作比喻了:spa
功能集合1:呼吸系統.net
功能集合2:血液循環系統orm
功能集合3:神經系統
功能集合4:語言系統
類1:靈長類動物。此類實現了1到3功能集合。
類2:猴子類。繼承自類1。新添加了"爬樹"的功能。
類3:人類。繼承自類1。同時實現了功能集合4。
類4:男人類。繼承自類3。新添加了"寫程序"的功能。
類5:女人類。繼承自類3。新添加了"發脾氣"的功能。
做業:請你們把上面的關係用圖畫出來
OK,讓咱們看下面的話,判斷對錯:
1. 男人是男人 (√) 緣由:原本就是!
2. 男人是人 (√) 緣由:人類是男人類的基類
3. 男人是靈長類動物 (√)緣由:靈長類是男人類的更抽象層基類
4. 男人是會說話的 (√) 緣由:男人類的基類實現了語言系統
5. 女人是猴子 (×) 緣由:若是我這麼說,會被蹁死
6. 猴子是女人 (×) 緣由:女人不是猴子的基類
7. 人會寫程序 (×)緣由:寫程序方法是在男人類中才具體實現的
8. 女人會發脾氣 (√) 緣由:由於我說5..
哈哈!如今你明白什麼是多態了吧!實際上是很是簡單的邏輯思惟。上面僅僅是多態的一個概念,下面咱們經過代碼去研習一下程序中的多態究竟是什麼。
不少公司在面試的時候常拿下面幾個問題當開胃小菜:
1. 如何使用virtual和override?
2. 如何使用abstract和override?
3. "重寫"與"重載"同樣嗎?
4. "重寫"、"覆蓋"、"隱藏"是同一個概念嗎?
順便說一句:若是你肯定能把上面的概念很熟練的掌握,發個Mail給我(bladey@tom.com ),也許你能收到一份薪水和福利都不錯的Offer :p
今天咱們學習多態,其實就是解決問題1。前面已經提到過,多態機制是依靠繼承機制實現的。那麼,在常規繼承機制的基礎之上,在基類中使用virtual函數,並在其派生類中對virtual函數進行override,那麼多態機制就天然而然地產生了。
小議virtual:
呵呵,我這人比較笨--有個人老師和同窗爲證--學東西奇慢無比,因此當初在C++中學習virtual的歷程是我心中永遠揮之不去的陰影..倒黴就倒黴 在這個"虛"字上了。"實"的我還雲裏霧裏呢,更況且這"虛"的,"虛"的還沒搞清楚呢,"純虛"又蹦出來了, 我#@$%!^#&&!..
還好,我挺過來了..回顧這段學習歷程,我發現萬惡之源就是這個"虛"字。
在漢語中,"虛"就是"無","無"就是"沒有",沒有的事情就"不可說"、"不可講"--那還講個X??老師也頭疼,學生更頭疼。拜初中語文老師所賜,個人語言邏輯還算過關,總感受virtual function譯爲"虛函數"有點詞不達意。
找來詞典一查,virtual有這樣一個詞條:
Existing or resulting in essence or effect though not in actual fact, form, or name:
實質上的,實際上的:雖然沒有實際的事實、形式或名義,但在實際上或效果上存在或產生的:
例句:
the virtual extinction of the buffalo.
野牛實際上已經絕跡(隱含的意思是"儘管野牛還木有死光光,但從效果上來說..")
啊哦~~讓我想起一句話:
有的人活着他已經死了; 有的人死了他還活着..
不由有點驚歎於母語的博大精深--
virtual function中的virtual應該譯作"名不副實"而不是"虛"!
OK,下面就讓咱們看看類中的virtual函數是怎麼個"名不副實"法。
例子1: 非virtual / override程序
// 水之真諦 //
// http://blog.csdn.net/FantasiaX //
// 上善若水,潤物無聲 //
using System;
using System.Collections.Generic;
using System.Text;
namespace Sample
{
// 演員(類)
class Actor
{
public void DoShow()
{
Console.WriteLine("Doing a show...");
}
}
// 樂手(類),繼承自Actor類
class Bandsman : Actor
{
// 子類同名方法隱藏父類方法
// 其實標準寫法應該是:
// public new void DoShow(){...}
// 爲了突出"同名",我把new省了,編譯器會自動識別
public void DoShow()
{
Console.WriteLine("Playing musical instrument...");
}
}
// 吉他手(類),繼承自Bandsman類
class Guitarist : Bandsman
{
public new void DoShow()
{
Console.WriteLine("Playing a guitar solo...");
}
}
class Program
{
static void Main(string[] args)
{
// 正常聲明
Actor actor = new Actor();
Bandsman bandsman = new Bandsman();
Guitarist guitarist = new Guitarist();
// 通常狀況下,隨着類的承繼和方法的重寫
// 方法是愈來愈具體、愈來愈個性化
actor.DoShow();
bandsman.DoShow();
guitarist.DoShow();
Console.WriteLine("===========================");
//嘗試多態用法
Actor myActor1 = new Bandsman(); //正確:樂手是演員
Actor myActor2 = new Guitarist(); //正確:吉他手是演員
Bandsman myBandsman = new Guitarist(); //正確:吉他手是樂手
//仍然調用的是引用類型自身的方法,而非派生類的方法
myActor1.DoShow();
myActor2.DoShow();
myBandsman.DoShow();
}
}
}
代碼分析:
1. 一上來,演員類、樂手類、吉他手類造成一個繼承鏈。
2. 樂手類和吉他手類做爲子類,都把其父類的DoShow()方法"隱藏"了。
3. 特別強調:"隱藏"不是"覆蓋",後面要講的"重寫"纔是真正的"覆蓋"。
4. 隱藏是使用new修飾符實現的,但這個修飾符能夠省略。
5. 隱藏(Hide)的含意是:父類的這個函數實際上還在,只是被子類的同名"藏起來"了。
6. 重寫(override)與覆蓋是同一個含意,只是覆蓋並不是編程的術語,但"覆蓋"比較形象。
7. 主程序代碼的上半部分是常規使用方法,沒什麼好說的。
8. 主程序代碼的下半部分已經算是多態了,但因爲沒有使用virtual和override,多態最有價值的效果--個性化方法實現--沒有體現出來。後面的例子專門體現這一點。
例子2: 應用virtual / override,真正的多態
// 水之真諦 //
// http://blog.csdn.net/FantasiaX //
// 上善若水,潤物無聲 //
using System;
using System.Collections.Generic;
using System.Text;
namespace Sample
{
// 演員(類)
class Actor
{
// 使用了virtual來修飾函數
// 此函數已經"名不副實"了
public virtual void DoShow()
{
Console.WriteLine("Doing a show...");
}
}
// 樂手(類),繼承自Actor類
class Bandsman : Actor
{
// 使用了override來修飾函數
// 此函數將取代(重寫)父類中的同名函數
public override void DoShow()
{
Console.WriteLine("Playing musical instrument...");
}
}
// 吉他手(類),繼承自Bandsman類
class Guitarist : Bandsman
{
public override void DoShow()
{
Console.WriteLine("Playing a guitar solo...");
}
}
class Program
{
static void Main(string[] args)
{
// 正常聲明
Actor actor = new Actor();
Bandsman bandsman = new Bandsman();
Guitarist guitarist = new Guitarist();
// 通常狀況下,隨着類的承繼和方法的重寫
// 方法是愈來愈具體、愈來愈個性化
actor.DoShow();
bandsman.DoShow();
guitarist.DoShow();
Console.WriteLine("===========================");
//嘗試多態用法
Actor myActor1 = new Bandsman(); //正確:樂手是演員
Actor myActor2 = new Guitarist(); //正確:吉他手是演員
Bandsman myBandsman = new Guitarist(); //正確:吉他手是樂手
// Look!!!
// 調用的是引用類型所引用的實例的方法
// 引用類型自己的函數是virtual的
// 看似"存在",實際已經被其子類重寫(不是隱藏,而是被kill掉了)
// 這正是virtual所要表達的"名不副實"的本意,而非一個"虛"字所能傳達
myActor1.DoShow();
myActor2.DoShow();
myBandsman.DoShow();
}
}
}
代碼分析:
1. 除了將繼承鏈中最頂層基類的DoShow()方法改成用virtual修飾;把繼承鏈中派生類的DoShow()方法改成override修飾以重寫基類的方法。
2. 主程序代碼沒變,但下半部分產生的效果徹底不一樣!請體會"引用變量自己方法"與"引用變量所引用實例的方法"的不一樣--這是關鍵。
多態成因的分析:
爲何會產生這樣的效果呢?這裏要提到一個"virtual表"的問題。咱們看看程序中繼承鏈的構成:Actor à Bandsman à Guitarist。由於派生類不但繼承了基類的代碼(確切地說是public代碼)並且還有本身的特有代碼(不管是否是與基類同名,都是本身特有的)。 從程序的邏輯視角來看,你能夠這樣想象:在內存中,子類的實例所佔的內存塊是在父類所佔的內存塊的基礎上"追加"了一小塊--拜託你們本身畫畫圖。這多出 來的一小塊裏,裝的就是子類特有的數據和代碼。
咱們仔細分析這幾句代碼:
1. Actor actor = new Actor(); //常規的聲明及分配內存方法
由於類是引用類型,因此actor這個引用變量是放在棧裏的、類型是Actor類型,而它所引用的實例--一樣也是Actor類型的--內存由new操做 符來分配而且放在堆裏。這樣,引用變量與實例的類型如出一轍、徹底匹配。換句話說:棧裏的引用變量所能"管理"的堆中的內存塊大小正好、很少也很多。
2. Actor myActor1 = new Bandsman(); //正確:樂手是演員
一樣是這句代碼,在兩個例子中產生的效果徹底不一樣。爲何呢?且看!在例1中,在Bandsman類中只是使用new將父類的DoShow()給隱藏了 --所起的做用僅限於本身對父類追加的代碼塊中,絲毫沒有影響到父類。而棧中的引用變量是Actor類型的myActor1,它只能管理Actor類實例 所佔的那麼大一塊內存,而對追加的內存毫無控制能力(或者說看不見追加的這塊內存)。所以,當你使用myActor1.DoShow();調用成員方法 時,myActor1只能使喚本身能管到的那塊內存裏的DoShow()方法。那麼例2中呢?難道例2中的myActor1就能管理追加的一塊內存了嗎?否也!它仍然管理不了,但不要忘了--這時候Actor類中的DoShow()方法已經被virtual所修飾,同時Bandsman類中的DoShow()方法已經被override修飾。這時候,當執行myActor1.DoShow();一句時,myActor1調用本身所管轄的內存塊時,發現DoShow()這個 函數已經標記爲"可被重寫"了(其實,在VB.NET中,與C#的virtual關鍵字對應的關鍵字就是Overridable,更直白),那麼它就會嘗 試去發現有沒有override鏈(也就是virtual表,即"虛表")的存在,若是存在,那麼就調用override鏈上的最新可用版本--這就有了 咱們在例2中看到的效果。
3. Actor myActor2 = new Guitarist(); //正確:吉他手是演員
經過這句代碼,你也能夠想象一下2級重寫是怎麼造成的,同時也能夠感悟一下所謂"重寫鏈上最新的可用版本"是什麼意思。
4. Guitarist myActor2 = new Actor(); //錯誤:想想爲何?
呵呵,這是錯誤的,緣由是引用變量所管理的內存大小超出了實例實際的內存大小。
亂彈:
多態,臺灣的兄弟們喜歡稱"多型",同樣的。"多"表示在實例化引用變量的時候,根據用戶當時的使用狀況(這時候程序已經Release了,不能再修改了,程序員已經不能控制程序了)智能地給出個性化的響應。
多,謂之變。莫非"多態"亦可稱爲"變態"耶?咦.."變型"..讓我想起Transformer來了。
TO BE CONTINUE