在c++中為了實現執行中的多型,需要滿足三個條件:類之間是派生關係;宣告基類、派生類的成員函式為虛函式;在滿足賦值相容性規則的前提下,通過指標或引用訪問虛函式。虛函式的為了實現動態聯編(執行多型)而引入的概念。
動態聯編(dynamic binding)和靜態聯編(static binding)。靜態聯編意味著編譯器能夠直接將識別符號和儲存的實體地址聯絡在一起。每乙個函式都有乙個唯一的實體地址,當編譯器遇到乙個函式呼叫時,它將用乙個機器語言說明來替代函式呼叫,用來告訴cpu跳至這個函式的位址,然後對此函式進行操作。這個過程是在編譯過程中完成的(注:呼叫的函式在編譯時必須能夠確定),所以靜態聯編也叫前期聯編(early binding)。
但是,如果使用哪個函式不能在編譯時確定,則需要採用動態聯編的方式,在程式執行時在呼叫函式,所以動態聯編也叫後期聯編(late binding)。虛函式簡單例項如下:
/*它具體的實現原理是什麼呢?c++採用虛函式表來實現動態束定。虛函式表是一張函式查詢表,用以解決以動態聯編方式呼叫函式。它為每個可以被類物件呼叫的虛函式提供乙個入口,這樣當我們用基類的指標或者引用來操作子類的物件時,這張虛函式表就提供了編譯器實際呼叫的函式。虛函式表其實是儲存了為類物件進行宣告的虛函式位址。當我們建立乙個類物件時,編譯器會自動的生成乙個指標*__vptr(乙個隱藏指標),該指標指向這個類中所有虛函式的位址表。*說明:虛函式簡單例項
*日期:2014-1-4
*/#include
using
namespace
std;
class
a //
宣告虛函式
};class b : publica};
void
main()
虛函式的動態繫結機制和3個東西相關:
虛函式表 vtbl:它是多型類自身的資訊,和具體的物件無關。在多繼承情況下,虛表中羅列繼承自每個父類的虛函式位址。如果子類直接使用父類中的虛函式定義,不進行覆蓋,則子類、父類的虛函式位址就會相同;如果子類進行覆蓋虛函式,vtbl 中對應的虛函式位址就會替換成子類自己的函式位址。
虛表指標 vptr:它儲存 vtbl 的位址,每個多型類的物件儲存中都會有 vptr。並且在多繼承情況下,會存在多個 vptr。多繼承子類物件,是按續構造父類物件後的拼接物件,再加上子類特有的儲存。與單繼承相同的是所有的虛函式都包含在虛函式表中,所不同的多重繼承有多個虛函式表,當子類對父類的虛函式有重寫時,子類的函式覆蓋父類的函式在對應的虛函式位置,當子類有新的虛函式時,這些虛函式被加在第乙個虛函式表的後面。
虛函式序號:它是配合 vtbl 實現動態繫結的重要元素,它在編譯時就確定了。在執行時,會根據 rtti(執行時型別資訊) 來選擇合適的 vptr,配合虛函式序號,計算 vtbl 中的儲存虛函式位址的位置,最後找到虛函式的實際呼叫位址,進行 this call 呼叫。
簡單的示例程式如下:
class輸出結果為:c1};
class
c2};
class
c_virtual
};class d_virtual:public
c_virtual
};void
main()
一般來說,成員函式存在類中,而變數存放在物件中,所以c1是空類。空類(不含資料成員)的大小不為0,而是1,試想乙個「不佔空間」的變數如何被儲存、又如何被取位址呢?顯然不行,編譯器也就只得為其分配乙個位元組的空間用於佔位,而一旦類有了非靜態資料成員,那麼就不存在上面的問題,編譯器也就沒有必要多分配這1個位元組了。
而c2就不是空類了嗎,它還有乙個非靜態的成員,整型變數s儲存在物件中,所以佔4個位元組。
c_virtual 類中含有虛函式c_test,所以類中要存放虛函式表,相對應的物件中就要儲存虛表指標vptr,32位作業系統中,指標佔4個位元組,再加上變數s共8個位元組。
d_virtual 繼承自c_virtual 類,並含有自己宣告的虛函式,繼承機制會把d_test新增到繼承的虛函式表中,形成新的虛函式表儲存在類空間中;對應物件中有虛表指標指向它的虛函式表。
在vs下命令提示中,檢視物件的記憶體布局:
可見,物件大小為12,vfptr佔4個位元組,基類的變數s和自己的變數s各佔4個位元組;虛函式表中包含c_test和d_test兩個虛函式。
最後簡單談談:使用基類指標操作子類陣列
陣列的定址方式:元素在記憶體中的位置 = 陣列開始位置 + 索引 x 單個元素的大小。
子類陣列的結構:基類資料放在前面,子類資料放在後面,如下
使用父類指標時的操作分析:
把乙個子類陣列變數賦值給乙個資料元素父類的指標時,指標首先指向陣列頭的位置(如圖)。
操作1:訪問元素
訪問第乙個元素(元素0)時,沒有任何問題。訪問下乙個元素時,指標後移。因為該指標是父類指標,所以偏移量為父類的大小sizeof(父類)。這時指標將指向圖中「子類資料」的位置,而不是元素1的位置。這將導致出錯。
操作2:使用delete刪除資料
delete同樣是按訪問元素的方法逐個析構這些元素。因為它無法找到物件真正的起始位置。比較有意思的一段程式如下:
#include using輸出:namespace
std;
class
base
void print()
};class child:public
base
};int main(int argc, char*ar**)
虛函式的實現機制
將函式宣告為virtual時,在背後發生了什麼呢?編譯器在編譯的時候,發現animal類中有虛函式,此時編譯器會為每個包含虛函式的類建立乙個虛表 即vtable 該表是乙個一維陣列,在這個陣列中存放每個虛函式的位址。對於例1 2的程式,animal和fish類都包含了乙個虛函式breathe 因此編...
虛函式的實現機制
一 虛函式表 對c 了解的人都應該知道虛函式 virtual function 是通過一張虛函式表 virtual table 來實現的。簡稱為v table。在這個表中,主要是乙個類的虛函式的位址表,這張表解決了繼承 覆蓋的問題,保證真實反應實際的函式。這樣,在有虛函式的類的例項中這個表被分配在了...
虛函式的實現機制
1 c 實現多型的方法 其實很多人都知道,虛函式在c 中的實現機制就是用虛表和虛指標,但是具體是怎樣的呢?從more effecive c 其中一篇文章裡面可以知道 是每個類用了乙個虛表,每個類的物件用了乙個虛指標。具體的用法如下 class a class b public a a,b的實現省略 ...