在C++中為了實現運行中的多態,需要滿足三個條件:類之間是派生關系;聲明基類、派生類的成員函數為虛函數;在滿足賦值兼容性規則的前提下,通過指針或引用訪問虛函數。虛函數的為了實現動態聯編(運行多態)而引入的概念。
動態聯編(dynamic binding)和靜態聯編(static binding)。靜態聯編意味著編譯器能夠直接將標識符和存儲的物理地址聯系在一起。每一個函數都有一個唯一的物理地址,當編譯器遇到一個函數調用時,它將用一個機器語言說明來替代函數調用,用來告訴CPU跳至這個函數的地址,然後對此函數進行操作。這個過程是在編譯過程中完成的(注:調用的函數在編譯時必須能夠確定),所以靜態聯編也叫前期聯編(early binding)。
但是,如果使用哪個函數不能在編譯時確定,則需要采用動態聯編的方式,在程序運行時在調用函數,所以動態聯編也叫後期聯編(late binding)。虛函數簡單實例如下:
<iostream> << << endl;} B : << <<* a = pa = &->f(); A &aa = cin.
它具體的實現原理是什麼呢?C++采用虛函數表來實現動態束定。虛函數表是一張函數查找表,用以解決以動態聯編方式調用函數。它為每個可以被類對象調用的虛函數提供一個入口,這樣當我們用基類的指針或者引用來操作子類的對象時,這張虛函數表就提供了編譯器實際調用的函數。虛函數表其實是存儲了為類對象進行聲明的虛函數地址。當我們創建一個類對象時,編譯器會自動的生成一個指針*__vptr(一個隱藏指針),該指針指向這個類中所有虛函數的地址表。
虛函數的動態綁定機制和3個東西相關:
虛函數表 vtbl:它是多態類自身的信息,和具體的對象無關。在多繼承情況下,虛表中羅列繼承自每個父類的虛函數地址。如果子類直接使用父類中的虛函數定義,不進行覆蓋,則子類、父類的虛函數地址就會相同;如果子類進行覆蓋虛函數,vtbl 中對應的虛函數地址就會替換成子類自己的函數地址。
虛表指針 vptr:它保存 vtbl 的地址,每個多態類的對象存儲中都會有 vptr。並且在多繼承情況下,會存在多個 vptr。多繼承子類對象,是按續構造父類對象後的拼接對象,再加上子類特有的存儲。與單繼承相同的是所有的虛函數都包含在虛函數表中,所不同的多重繼承有多個虛函數表,當子類對父類的虛函數有重寫時,子類的函數覆蓋父類的函數在對應的虛函數位置,當子類有新的虛函數時,這些虛函數被加在第一個虛函數表的後面。
虛函數序號:它是配合 vtbl 實現動態綁定的重要元素,它在編譯時就確定了。在運行時,會根據 RTTI(運行時類型信息) 來選擇合適的 vptr,配合虛函數序號,計算 vtbl 中的存儲虛函數地址的位置,最後找到虛函數的實際調用地址,進行 this call 調用。
簡單的示例程序如下:
ttt(){cout<<<< ttt(){cout<<<< C_test(){ cout<<<< D_Virtual: D_test(){cout<<<<<< (c1)<< << (c2)<< << (c_virtual)<< << (d_virtual)<< <<
輸出結果為:
輸出:,大家自己分析下吧。切記,不要用父類指針去操作子類數組。