**:
,原創作品,轉貼請註明作者和出處。
關於繼承,你是否駕熟就輕,關於繼承,你是否瞭如指掌。
本文不討論繼承的基本概念,我們回歸本質,從編譯器執行的角度來揭示.net繼承中的執行本源,來發現子類物件是如何實現了對父類成員與方法的繼承,以最為簡陋的示例來揭示繼承的實質,闡述繼承機制是如何被執行的,這對於更好的理解繼承,是必要且必然的。
下面首先以乙個簡單的動物繼承體系為例,來進行說明:
public abstract class animal
}public class bird: animal
", type);
}private string color;
public string color
set }}
public class chicken : bird
", type);
}public void showcolor()
", color);}}
然後,在測試類中建立各個類物件,由於animal為抽象類,我們只建立bird物件和chicken物件。
public class testinheritance
}下面我們從編譯角度對這一簡單的繼承示例進行深入分析,從而了解.net內部是如何實現我們強調的繼承機制。
(1)我們簡要的分析一下物件的建立過程:
bird animal = new bird();
bird bird建立的是乙個bird型別的引用,而new bird()完成的是建立bird物件,分配記憶體空間和初始化操作,然後將這個物件賦給bird引用,也就是建立bird引用與bird物件的關聯。
(2)我們從繼承的角度來分析在編譯器編譯期是如何執行物件的建立過程,因為繼承的本質就體現於物件的建立過程。
在此我們以chicken物件的建立為例,首先是字段,物件一經建立,會首先找到其父類bird,並為其欄位分配儲存空間,而bird也會繼續找到其父類animal,為其分配儲存空間,依次類推直到遞迴結束,也就是完成system.object記憶體分配為止。我們可以在編譯器中單步執行的方法來大致了解其分配的過程和順序,因此,物件的建立過程是按照順序完成了對整個父類及其本身欄位的記憶體建立,並且欄位的儲存順序是由上到下排列,object類的字段排在最前面,其原因是如果父類和子類出現了同名字段,則在子類物件建立時,編譯器會自動認為這是兩個不同的字段而加以區別。
然後,是方法表的建立,必須明確的一點是方法表的建立是類第一次載入到clr時完成的,在物件建立時只是將其附加成員typehandle指向方法列表在loader heap上的位址,將物件與其動態方法列表相關聯起來,因此方法表是先於物件而存在的。類似於字段的建立過程,方法表的建立也是父類在先子類在後,原因是顯而易見的,類chicken生成方法列表時,首先將bird的所有方法拷貝乙份,然後和chicken本身的方法列表做以對比,如果有覆寫的虛方法則以子類方法覆蓋同名的父類方法,同時新增子類的新方法,從而建立完成chicken的方法列表。這種建立過程也是逐層遞迴到object類,並且方法列表中也是按照順序排列的,父類在前子類在後,其原因和字段大同小異,留待讀者自己體味。
結合我們的分析過程,現在將物件建立的過程以簡單的圖例來揭示其在記憶體中的分配情形,如下:
從我們的分析,和上面的物件建立過程可見,對繼承的本質我們有了更明確的認識,對於以下的問題就有了清晰明白的答案:
你是否已經找到了理解繼承、理解動態編譯的不二法門。
通過上面的講述與分析,我們基本上對.net在編譯期的實現原理有了大致的了解,但是還有以下的問題,一定會引起一定的疑惑,那就是:
bird bird2 = new chicken();
這種情況下,bird2.showtype應該返回什麼值呢?而bird2.type有該是什麼值呢?有兩個原則,是.net專門用於解決這一問題的:
注意 根據關注物件原則,那麼下面的兩種情況又該如何區別呢?
bird bird2 = new chicken();
chicken chicken = new chicken();
根據我們上文的分析,bird2物件和chicken物件在記憶體布局上是一樣的,差別就在於其引用指標的型別不同:bird2為bird型別指標,而chicken為chicken型別指標。以方法呼叫為例,不同的型別指標在虛擬方法表中有不同的附加資訊作為標誌來區別其訪問的位址區域,稱為offset。不同型別的指標只能在其特定位址區域內進行執行,子類覆蓋父類時會保證其訪問位址區域的一致性,從而解決了不同的型別訪問具有不同的訪問許可權問題。
思考 1. 上面我們分析到bird2.type的值是「bird」,那麼bird2.showtype()會顯示什麼值呢?答案是「type is chicken」,根據本文上面的分析,想想到底為什麼?
2. 關於new關鍵字在虛方法動態呼叫中的阻斷作用,也有了更明確的理論基礎。在子類方法中,如果標記new關鍵字,則意味著隱藏基類實現,其實就是建立了與父類同名的另乙個方法,在編譯中這兩個方法處於動態方法表的不同位址位置,父類方法排在前面,子類方法排在後面。
在.net中,如果建立乙個類,則該類總是在繼承。這緣於.net的物件導向特性,所有的型別都最終繼承自共同的根system.object類。可見,繼承是.net執行機制的基礎技術之一,一切皆為物件,一切皆於繼承。本文從基礎出發,深入本質探索本源,分析疑難比較鑑別。對於什麼是繼承這個話題,希望每個人能從中尋求自己的答案,理解繼承、關注封裝、玩轉多型是理解物件導向的起點,希望本文是這一旅程的起點。
[祝福]
僅以此篇獻給我的老師們:湯文海老師,陳樺老師。
繼承本質論
1.引言 關於繼承,你是否駕熟就輕,關於繼承,你是否瞭如指掌。本文不討論繼承的基本概念,我們回歸本質,從編譯器執行的角度來揭示.net繼承中的執行本源,來發現子類物件是如何實現了對父類成員與方法的繼承,以最為簡陋的示例來揭示繼承的實質,闡述繼承機制是如何被執行的,這對於更好的理解繼承,是必要且必然的...
指標本質論
指標本質論 1.指標是什麼?和一般變數有什麼區別?指標就是位址,和一般變數沒有本質區別,僅僅是它有自己的規則。int a 100 int p a printf d n a 100 printf p n p 0xbfa47858 a是乙個變數名,型別是int,值是100,a有自己的位址 a p是乙個變...
type traits 之 本質論
侯捷老師在 stl 原始碼剖析 說 traits程式設計方法是一把開啟stl源 大門的鑰匙,其重要性也就不必再說了。既然traits程式設計方法如此重要,那麼掌握並領悟其精髓是相當必要了。trait的意思是什麼?英文意思是attribute,feature等等,中文意思可以解釋為特點,特性。那麼ty...