將函式宣告為virtual時,在背後發生了什麼呢?
編譯器在編譯的時候,發現animal類中有虛函式,此時編譯器會為每個包含虛函式的類建立乙個虛表(即vtable),該表是乙個一維陣列,在這個陣列中存放每個虛函式的位址。對於例1-2的程式,animal和fish類都包含了乙個虛函式breathe(),因此編譯器會為這兩個類都建立乙個虛表,如下圖所示:
那麼如何定位虛表呢?編譯器另外還為每個類的物件提供了乙個虛表指標(即vptr),這個指標指向了物件所屬類的虛表。在程式執行時,根據物件的型別去初始化vptr,從而讓vptr正確的指向所屬類的虛表,從而在呼叫虛函式時,就能夠找到正確的函式。對於例1-2的程式,由於pan實際指向的物件型別是fish,因此vptr指向的fish類的vtable,當呼叫pan->breathe()時,根據虛表中的函式位址找到的就是fish類的breathe()函式。
正是由於每個物件呼叫的虛函式都是通過虛表指標來索引的,也就決定了虛表指標的正確初始化是非常重要的。換句話說,在虛表指標沒有正確初始化之前,我們不能夠去呼叫虛函式。那麼虛表指標在什麼時候,或者說在什麼地方初始化呢?
答案是在建構函式中進行虛表的建立和虛表指標的初始化。還記得建構函式的呼叫順序嗎,在構造子類物件時,要先呼叫父類的建構函式,此時編譯器只「看到了」父類,並不知道後面是否後還有繼承者,它初始化父類物件的虛表指標,該虛表指標指向父類的虛表。當執行子類的建構函式時,子類物件的虛表指標被初始化,指向自身的虛表。對於例2-2的程式來說,當fish類的fh物件構造完畢後,其內部的虛表指標也就被初始化為指向fish類的虛表。在型別轉換後,呼叫pan->breathe(),由於pan實際指向的是fish類的物件,該物件內部的虛表指標指向的是fish類的虛表,因此最終呼叫的是fish類的breathe()函式。
要注意:對於虛函式呼叫來說,每乙個物件內部都有乙個虛表指標,該虛表指標被初始化為本類的虛表。所以在程式中,不管你的物件型別如何轉換,但該物件內部的虛表指標是固定的,所以呢,才能實現動態的物件函式呼叫,這就是c++多型性實現的原理。
總結(基類有虛函式):
1、 每乙個類都有虛表。
2、 虛表可以繼承,如果子類沒有重寫虛函式,那麼子類虛表中仍然會有該函式的位址,只不過這個位址指向的是基類的虛函式實現。如果基類3個虛函式,那麼基類的虛表中就有三項(虛函式位址),派生類也會有虛表,至少有三項,如果重寫了相應的虛函式,那麼虛表中的位址就會改變,指向自身的虛函式實現。如果派生類有自己的虛函式,那麼虛表中就會新增該項。
3、 派生類的虛表中虛函式位址的排列順序和基類的虛表中虛函式位址排列順序相同。
虛函式實現機制
c 中的虛函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父型別別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術可以讓父類的指標有 多種形態 這是一種泛型技術。虛函式的作用是實現動態聯編,也就是在程式的執行階段動態地選擇合適的成員函式。當程式發現虛函式名前的關鍵...
虛函式實現機制
說到虛函式的實現方法,我們就不得不說到動態聯編 dynamic binding 和靜態聯編 static binding 靜態聯編意味著編譯器能夠直接將識別符號和儲存的實體地址聯絡在一起。每乙個函式都有乙個唯一的實體地址,當編譯器遇到乙個函式呼叫時,它將用乙個機械語言說明來替代函式呼叫,用來告訴cp...
虛函式的實現機制
一 虛函式表 對c 了解的人都應該知道虛函式 virtual function 是通過一張虛函式表 virtual table 來實現的。簡稱為v table。在這個表中,主要是乙個類的虛函式的位址表,這張表解決了繼承 覆蓋的問題,保證真實反應實際的函式。這樣,在有虛函式的類的例項中這個表被分配在了...