1、多型性和虛函式
我們先看乙個例子:
考慮一下這段程式的輸出結果是什麼?答案是輸出:animal breath
我們在main函式中首先定義乙個fish類的物件fh,接著定義了乙個指向animal類的指標pan,將fn的位址賦給了指標變數pan,然後利用該變數呼叫pan->breath()。許多人往往將這種情況和c++的多型性搞混淆,認為fh實際上是fish類的物件,應該呼叫fish類的breath函式,輸出「fish bubble」。然而結果卻不是這樣,下面我們從2個角度來講述原因。
1)編譯的角度
我們構造fish類的物件時,首先要呼叫animal類的建構函式去構造animal類的物件,然後才呼叫fish類的建構函式完成自身部分的構造,從而拼接出乙個完整的fish物件。當我們將fish類的物件轉換為animal型別時,fish物件就被認為是原物件整個記憶體模型的上半部分,也就是animal物件所佔記憶體。那麼當我們利用型別轉換後的物件指標去呼叫它的方法時,當然也就是呼叫它所在的記憶體中的方法。因此,輸出animal breath,也就順理成章了。
正如很多人所想,我們知道pan實際指向的是fish類的物件。我們希望的輸出結果是fish的breath方法,這個時候就該輪到虛函式登場了。前面的輸出結果是因為編譯器在編譯的時候,就已經確定了物件呼叫 的函式 的位址。要解決這個問題要使用遲繫結(late binding)技術。當編譯器使用遲繫結時,就會在執行時再去確定物件的型別以及正確的呼叫函式。而要讓編譯器使用遲繫結,就要在基類中宣告函式時使用virtual關鍵字(注意:這是必須的,很多人就是因為沒有使用虛函式而寫出很多錯誤的例子),這樣的函式我們成為虛函式。一旦某個函式在基類中宣告為virtual,那麼在所有的派生類中該函式都是virtual,而不需要再顯式的宣告為virtual。
那麼當我們將breath宣告為virtual時,在背後發生了什麼呢?
編譯器在編譯的時候,發現animal類中有虛函式,此時編譯器會為每個包含虛函式的類建立乙個虛表(即vtable),該表是乙個一維陣列,在這個陣列中存放每個虛函式的位址,對於上面的程式,animal和fish類都包含乙個虛函式breath(),因此編譯器會為這兩個類都建立乙個虛表。
那麼如何定位虛表呢?編譯器另外還為每個類的 物件 提供了乙個虛表指標(即vptr),這個指標指向了物件所屬類的虛表。在程式執行時,根據物件的型別去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在呼叫虛函式時,就能夠找到正確的函式。對於上面的程式,由於pan實際指向的物件型別是fish,因此vptr指向的是fish類的vtable,當呼叫pan->breath()時,根據虛表中的函式位址找到的就是fish類的breath()函式。
正是由於每個物件呼叫的虛函式都是通過虛表指標來索引的,也就決定了虛表指標的正確初始化是非常重要的。換句話說,在虛表指標沒有正確初始化之前,我們不能夠去呼叫虛函式,那麼虛表指標在什麼時候,或者說在什麼地方初始化呢?
答案是在建構函式中進行 虛表的建立 和 虛表指標的初始化 。還記得建構函式的呼叫順序嗎?在構造子類物件時,要先呼叫父類的建構函式,此時編譯器只「看到了」父類。並不知道後面還有沒有繼承者,它初始化父類物件的虛表指標,該指標指向父類的虛表。當執行子類的建構函式時,子類物件的虛表指標被初始化,指向自身的虛表。對於上例的程式來說,當fish類的fh物件構造完畢後,其內部的虛表指標也就被初始化為指向fish的虛表。在型別轉換後,呼叫pan->breath(),由於pan實際指向的是fish類的物件,該物件內部的虛表指標指向的是fish類虛表,因此最終呼叫的是fish類的breath()函式。
要注意:對於虛函式來說,每乙個物件內部都有乙個虛表指標,該虛表指標被初始化為本類的虛表,所有在程式中,不管你的物件型別如何轉換,但該物件內部的虛表指標是固定的,所以呢,才能實現動態的物件函式呼叫,這就是c++多型性實現的原理。
總結:1、每乙個類都有虛表
2、虛表可以繼承,如果子類沒有重新虛函式,那麼子類虛表中仍然會有該函式的位址,只不過這個位址指向的是基類的虛函式實現。如果基類3個虛函式,那麼基類的虛表中就有3項(虛函式的位址),派生類也會有虛表,至少有3項,如果重寫了相應的虛函式,那麼虛表的中的位址就會改變,指向自身的虛函式實現。如果派生類有自己的虛函式,那麼虛函式表中就會新增該項。
3、派生類的虛表中虛函式的位址排列順序和基類的虛表中虛函式位址排列順序相同。
C 的多型性實現機制剖析
c 的多型性實現機制剖析 即 vc this 指標詳細說明 2006年1 月12日星期四 我們先看乙個例子 例 1 1 include class animal void breathe class fish public animal void main 注意,在例 1 1的程式中沒有定義虛函式。...
C 的多型性實現機制剖析
我們先看乙個例子 例 1 1 include class animal voidbreathe class fish public animal void main 注意,在例 1 1的程式中沒有定義虛函式。考慮一下例 1 1的程式執行的結果是什麼?答案是輸出 animal breathe 我們在m...
C 的多型性實現機制剖析
1 多型性和虛函式 我們先看乙個例子 cpp view plain copy include class animal void breath class fish public animal void main 考慮一下這段程式的輸出結果是什麼?答案是輸出 animal breath 我們在mai...