日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

C 虚函数表及多态内部原理详解

發(fā)布時間:2023/12/2 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C 虚函数表及多态内部原理详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。