C++ 虛函數(shù)及虛函數(shù)表詳解
多態(tài)”的關(guān)鍵在于通過基類指針或引用調(diào)用一個(gè)虛函數(shù)時(shí),編譯時(shí)不確定到底調(diào)用的是基類還是派生類的函數(shù),運(yùn)行時(shí)才確定。
#include <iostream>
using namespace std;
class A
{
public:
int i;
virtual void func() {}
virtual void func2() {}
};
class B : public A
{
int j;
void func() {}
};
int main()
{
cout << sizeof(A) << ", " << sizeof(B); //輸出 8,12
return 0;
}
在 32 位編譯模式下,程序的運(yùn)行結(jié)果是:
8, 12
如果將程序中的 virtual 關(guān)鍵字去掉,輸出結(jié)果變?yōu)椋?br />
4, 8
A * p = new B() 實(shí)現(xiàn)多態(tài)
對(duì)比發(fā)現(xiàn),有了虛函數(shù)以后,對(duì)象所占用的存儲(chǔ)空間比沒有虛函數(shù)時(shí)多了 4 個(gè)字節(jié)。實(shí)際上,任何有虛函數(shù)的類及其派生類的對(duì)象都包含這多出來的 4 個(gè)字節(jié),這 4 個(gè)字節(jié)就是實(shí)現(xiàn)多態(tài)的關(guān)鍵——它位于對(duì)象存儲(chǔ)空間的最前端,其中存放的是虛函數(shù)表的地址。
每一個(gè)有虛函數(shù)的類(或有虛函數(shù)的類的派生類)都有一個(gè)虛函數(shù)表,該類的任何對(duì)象中都放著該虛函數(shù)表的指針(可以認(rèn)為這是由編譯器自動(dòng)添加到構(gòu)造函數(shù)中的指令完成的)。
沒有覆蓋時(shí)的子類,可以看到子類的虛函數(shù)表的前面是基類離得虛函數(shù)

有覆蓋就是

子類對(duì)象地址為什么能賦值給父類對(duì)象指針?
因?yàn)?,子類?duì)象地址賦值給父類對(duì)象指針,父類對(duì)象指針就指向了子類的對(duì)象空間,父類操作子類的范圍是有限制的,只能操作到子類中父類的范圍。
基類和子類各有自己的虛函數(shù)表vtbl;不管是基類還是子類實(shí)例都會(huì)在其內(nèi)存的開頭自動(dòng)創(chuàng)對(duì)象即虛函數(shù)表指針vptr, 用來訪問所在類的虛函數(shù)表
想要實(shí)現(xiàn)多態(tài),需要?jiǎng)討B(tài)綁定,需要父類的指針或父類的引用
父類方法為虛方法,子類覆蓋父類的虛方法,才能達(dá)到多態(tài)
子類中父類沒有的方法,父類指針也無法訪問到,父類指針只能訪問到父類自己有的范圍
子類要覆蓋父類的方法,就是要函數(shù)名參數(shù)都必須一樣才叫覆蓋


再看一個(gè)例子
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1();
void func1();
private:
int m_data3;
};
class C: public B {
public:
virtual void vfunc2();
void func2();
private:
int m_data1, m_data4;
};

子類繼承父類,子類中有父類的同名方法,訪問的是子類的方法,子類會(huì)隱藏父類所有的同名方法,即使父類有一個(gè)同名的參數(shù)不同的方法也是如此。
多重繼承(無虛函數(shù)覆蓋)
下面,再讓我們來看看多重繼承中的情況,假設(shè)有下面這樣一個(gè)類的繼承關(guān)系。注意:子類并沒有覆蓋父類的函數(shù)。

對(duì)于子類實(shí)例中的虛函數(shù)表,是下面這個(gè)樣子:

我們可以看到:
1) 每個(gè)父類都有自己的虛表。
2) 子類的成員函數(shù)被放到了第一個(gè)父類的表中。(所謂的第一個(gè)父類是按照聲明順序來判斷的)
這樣做就是為了解決不同的父類類型的指針指向同一個(gè)子類實(shí)例,而能夠調(diào)用到實(shí)際的函數(shù)。
多重繼承(有虛函數(shù)覆蓋)
下面我們?cè)賮砜纯?,如果發(fā)生虛函數(shù)覆蓋的情況。
下圖中,我們?cè)谧宇愔懈采w了父類的f()函數(shù)。

下面是對(duì)于子類實(shí)例中的虛函數(shù)表的圖:

我們可以看見,三個(gè)父類虛函數(shù)表中的f()的位置被替換成了子類的函數(shù)指針。這樣,我們就可以任一靜態(tài)類型的父類來指向子類,并調(diào)用子類的f()了。
任何妄圖使用父類指針想調(diào)用子類中的未覆蓋父類的成員函數(shù)的行為都會(huì)被編譯器視為非法,
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
如何為Qt視圖中的文字實(shí)現(xiàn)彩虹漸變效果
這篇文章主要給大家介紹了關(guān)于如何為Qt視圖中的文字實(shí)現(xiàn)彩虹漸變效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Qt具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
linux c++ 服務(wù)器端開發(fā)面試必看書籍整理
這篇文章主要介紹了linux c++ 服務(wù)器端開發(fā)面試必看書籍整理,需要的朋友可以參考下2020-02-02
C++實(shí)現(xiàn)一個(gè)封裝的雙鏈表的完整代碼
雙鏈表是鏈表的一種變種,除了每個(gè)節(jié)點(diǎn)指向下一個(gè)節(jié)點(diǎn)外,還多了一個(gè)指向前一個(gè)節(jié)點(diǎn)的指針,由于雙鏈表可以從兩端進(jìn)行遍歷,它的插入和刪除操作更為靈活,本文將詳細(xì)介紹如何使用 C++ 語言實(shí)現(xiàn)一個(gè)封裝的雙鏈表類,需要的朋友可以參考下2025-07-07
C語言關(guān)鍵字const和指針的結(jié)合使用
這篇文章主要介紹了C語言關(guān)鍵字const和指針的結(jié)合,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
關(guān)于C++靜態(tài)成員函數(shù)訪問非靜態(tài)成員變量的問題
靜態(tài)成員函數(shù)不能訪問非靜態(tài)成員,這是因?yàn)殪o態(tài)函數(shù)屬于類而不是屬于整個(gè)對(duì)象,靜態(tài)函數(shù)中的 member可能都沒有分配內(nèi)存。靜態(tài)成員函數(shù)沒有隱含的this自變量。所以,它就無法訪問自己類的非靜態(tài)成員2013-10-10
C++實(shí)現(xiàn)教職工信息管理系統(tǒng)課程設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)教職工信息管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

