C 虚函数表及多态内部原理详解
C 中的虛函數(shù)的作用主要是實現(xiàn)了多態(tài)的機制。關(guān)于多態(tài),簡而言之就是用父類型別的指針指向其子類的實例,然后通過父類的指針調(diào)用實際子類的成員函數(shù)。這種技術(shù)可以讓父類的指針有“多種形態(tài)”,這是一種泛型技術(shù)。
虛函數(shù)表
每個含有虛函數(shù)的類都有一個虛函數(shù)表(Virtual Table)來實現(xiàn)的。簡稱為V-Table。C 的編譯器應(yīng)該是保證虛函數(shù)表的指針存在于對象實例中最前面的位置(這是為了保證取到虛函數(shù)表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。這意味著我們通過對象實例的地址得到這張?zhí)摵瘮?shù)表,然后就可以遍歷其中函數(shù)指針,并調(diào)用相應(yīng)的函數(shù)。
1、 每一個類都有虛函數(shù)列表。
2、 虛表可以繼承,如果子類沒有重寫虛函數(shù),那么子類虛表中仍然會有該函數(shù)的地址,只不過這個地址指向的是基類的虛函數(shù)實現(xiàn)。如果基類3個虛函數(shù),那么基類的虛表中就有三項(虛函數(shù)地址),派生類也會有虛表,至少有三項,如果重寫了相應(yīng)的虛函數(shù),那么虛表中的地址就會改變,指向自身的虛函數(shù)實現(xiàn)。如果派生類有自己的虛函數(shù),那么虛表中就會添加該項。
3、 派生類的虛表中虛函數(shù)地址的排列順序和基類的虛表中虛函數(shù)地址排列順序相同,子類獨有的虛函數(shù)放在后面。
當(dāng)定義一個有虛函數(shù)類的對象時,對象的第一塊的內(nèi)存空間就是一個指向虛函數(shù)列表的指針。
在這舉個例子
假設(shè)我們有這樣的一個類:
由于例程的操作環(huán)境是64位系統(tǒng),所以用long*強轉(zhuǎn)。其中(long*)(&b)就是虛函數(shù)表地址,(long*)*(long*)(&b)就是第一個函數(shù)地址,代碼運行結(jié)果如下:
在程序中取出對象b的地址,根據(jù)對象的布局可以得出就是虛表的地址,根據(jù)這個地址可以把虛表的第一個內(nèi)存單元的內(nèi)容取出,然后強制轉(zhuǎn)換成一個函數(shù)指針,利用這個函數(shù)指針來訪問虛函數(shù)。又因為虛表是連續(xù)的,利用每次 1可以來訪問下一個內(nèi)存單元。
如果對代碼不理解的話,可以看這幅圖就會懂了
注意:虛函數(shù)表在最后會有一個結(jié)束標(biāo)志,為1說明還有虛表,為0表示沒有虛表了 。(編譯器不同,結(jié)束標(biāo)志可能存在差異)
下面,將分別具體說明“無虛函數(shù)覆蓋”和“有虛函數(shù)覆蓋”時的虛函數(shù)表的情況。
(一)無虛函數(shù)覆蓋
沒有任何的繼承,虛函數(shù)表如下圖
根據(jù)示意圖,編寫的代碼如下圖所示:
和上一個程序一樣,根據(jù)取出虛表里面的地址強制轉(zhuǎn)換成函數(shù)指針,同樣,利用虛表的連續(xù)性,每次指針 1調(diào)用對應(yīng)的虛函數(shù)。可以得出虛函數(shù)按照其聲明順序存放于虛函數(shù)表中的,子類自己的虛函數(shù)是排在父類虛函數(shù)之后的。運行結(jié)果如下圖
(二)一般繼承(有虛函數(shù)覆蓋)
如果子類中有虛函數(shù)重載了父類的虛函數(shù),會是一個什么樣子?假設(shè),我們有下面這樣的一個繼承關(guān)系。如圖所示:
在這個類的設(shè)計中,只覆蓋了父類的一個函數(shù):f()。那么,對于派生類的實例,其虛函數(shù)表會是下面的一個樣子:
從表中可以看到下面幾點,
1)覆蓋的f()函數(shù)被放到了虛表中原來父類虛函數(shù)的位置。
2)沒有被覆蓋的函數(shù)依舊。
這樣就會出現(xiàn)虛調(diào)用
base *b = new Derive();
b->f();
由b所指的內(nèi)存中的虛函數(shù)表的f()的位置已經(jīng)被Derive::f()函數(shù)地址所取代,于是在實際調(diào)用發(fā)生時,是Derive::f()被調(diào)用了。這就實現(xiàn)了多態(tài)。下面我們用一個示例代碼來看一下
運行結(jié)果如下,確實如我們以上分析的那樣,由b所指的內(nèi)存中的虛函數(shù)表的f()的位置已經(jīng)被Derive::f()函數(shù)地址所取代:
(三)多重繼承(無虛函數(shù)覆蓋)
下面我們再看看多重繼承的情況
對于子類實例中的虛函數(shù)表,是下面這個樣子:
從圖上我們可以看到
1)每個父類都有自己的虛表。
2) 子類的成員函數(shù)被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)
這樣做就是為了解決不同的父類類型的指針指向同一個子類實例,而能夠調(diào)用到實際的函數(shù)。
下面我們根據(jù)上圖來實現(xiàn)一下
運行結(jié)果如下:
在這個程序中,子類有多個父類,因此從每個父類都繼承了一個虛表,因此會有3個虛表,根據(jù)代碼和運行結(jié)果會發(fā)現(xiàn),排列的順序和繼承的順序一樣,子類自己的虛函數(shù)排在第一個虛表的后面。程序中沒有改寫虛函數(shù) ,因此沒有覆蓋。同時主函數(shù)中應(yīng)用的是一個二重指針,利用二維數(shù)組取每個虛函數(shù)地址。
(四)多重繼承(有虛函數(shù)覆蓋)
下面我們再來看看,如果發(fā)生虛函數(shù)覆蓋的情況。
下圖中,我們在子類中覆蓋了父類的f()函數(shù)。
子類虛函數(shù)列表如圖所示
三個父類虛函數(shù)表中的f()的位置被替換成了子類的函數(shù)指針。這樣,我們就可以任一靜態(tài)類型的父類來指向子類,并調(diào)用子類的f()了。
子類虛函數(shù)列表訪問代碼如下:
程序運行結(jié)果如下所示:
本程序中,子類重寫了f函數(shù),把所有父類里面的f函數(shù)都屏蔽了。在虛表中父類f函數(shù)的位置全部換成了子類f函數(shù)的地址。因此在輸出時父類的f函數(shù)沒有了,全部是子類f函數(shù)的輸出。
總結(jié)
以上是生活随笔為你收集整理的C 虚函数表及多态内部原理详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux查看文件内容命令vim(lin
- 下一篇: 10 张程序员喜爱的壁纸,需要自取~