C++虛函數(shù)表的使用小結(jié)
前言
使用過(guò)Java的同學(xué)應(yīng)該特別熟悉多態(tài),即耳熟能詳?shù)囊痪湓挘?strong>可以使用父類引用來(lái)指向子類對(duì)象,而實(shí)現(xiàn)起來(lái)非常容易,只需要重寫(xiě)父類的方法或者實(shí)現(xiàn)接口的方法即可:
//Java代碼
class Parent {
void show() {
System.out.println("Inside Parent");
}
}
class Child extends Parent {
void show() {
System.out.println("Inside Child");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child();
obj.show(); // 調(diào)用的是子類的 show() 方法
}
}
在Java中,這里是使用父類Parent的引用obj,指向了子類對(duì)象,然后調(diào)用的是子類的show()方法。
但是在C++中,如果按同樣類似的寫(xiě)法,卻得到的結(jié)果不一樣:
//C++代碼
#include <iostream>
class Parent {
public:
void show() {
std::cout << "Inside Parent" << std::endl;
}
};
class Child : public Parent {
public:
void show() {
std::cout << "Inside Child" << std::endl;
}
};
int main() {
Parent* obj = new Child();
obj->show(); // 調(diào)用的是父類的 show() 方法
delete obj;
return 0;
}
在C++中,這里使用父類的指針,指向子類對(duì)象,但是最終調(diào)用的是父類的show()方法。那如何實(shí)現(xiàn)像Java那種效果呢?就需要使用虛函數(shù),本篇文章深入探究虛函數(shù)以及虛函數(shù)的原理--虛函數(shù)表(后面簡(jiǎn)稱虛表)的相關(guān)知識(shí)。
正文
關(guān)于虛函數(shù)的使用,以及純虛函數(shù)定義接口等,這些基礎(chǔ)知識(shí),暫時(shí)就不詳細(xì)說(shuō)明了,重點(diǎn)關(guān)注虛表。
虛表是屬于類
虛表是函數(shù)指針類型的數(shù)組,指向該類定義的虛函數(shù)。
在C++中,只要一個(gè)類包含了一個(gè)虛函數(shù),那么就有由一個(gè)虛表來(lái)維護(hù)。虛表是屬于類的,而非屬于某個(gè)對(duì)象,一個(gè)類只需要一個(gè)虛表,所以該類對(duì)象共用一個(gè)虛表。
當(dāng)類B繼承至類A,繼承類也可以調(diào)用A的函數(shù),如果A是一個(gè)包含虛表的基類,那么繼承類B也擁有自己的虛表。
理解類的虛表非常重要,比如有代碼:
class A {
public:
A();
virtual void vfunc1() { qDebug() << "A vfunc1()"; }
virtual void vfunc2() { qDebug() << "A vfunc2()"; }
void func1() { qDebug() << "A func1()"; }
void func2() { qDebug() << "A func2()"; }
private:
int m_data1, m_data2;
};
那么類A的虛表如圖:

虛表是一個(gè)指針數(shù)組,其元素是虛函數(shù)的指針,每個(gè)元素對(duì)應(yīng)一個(gè)虛函數(shù)的函數(shù)指針。非虛函數(shù)的函數(shù),調(diào)用不需要虛表,所以虛表中不會(huì)保存。虛表的創(chuàng)建在編譯時(shí)期,即在編譯時(shí)期,屬于一個(gè)類的虛表就構(gòu)造出來(lái)了。
對(duì)象的虛表指針
我們使用IDE調(diào)試模式,來(lái)看一下類A的對(duì)象包含哪些東西:

可以發(fā)現(xiàn)除了其數(shù)據(jù)成員外,還有一個(gè)__vptr指針,這個(gè)指針是虛表指針,指向類的虛表。為了指定對(duì)象的虛表,編譯器在類中添加了__vptr指針,專門(mén)用來(lái)指向虛表。
這里可以知道,同一個(gè)類的多個(gè)對(duì)象,其虛表指針?biāo)赶虻奶摫硎且粯拥模?/p>

效果如圖:

動(dòng)態(tài)綁定
重點(diǎn)來(lái)了,C++是如何通過(guò)虛表實(shí)現(xiàn)多態(tài)的,即動(dòng)態(tài)綁定,有如下3個(gè)類:
class A {
public:
A();
virtual void vfunc1() { qDebug() << "A vfunc1()"; }
virtual void vfunc2() { qDebug() << "A vfunc2()"; }
void func1() { qDebug() << "A func1()"; }
void func2() { qDebug() << "A func2()"; }
private:
int m_data1, m_data2;
};
class B : public A {
public:
B();
virtual void vfunc1() { qDebug() << "B vfunc1()"; }
void func1() { qDebug() << "B func1()"; }
private:
int m_data3;
};
class C : public B {
public:
C();
virtual void vfunc2() { qDebug() << "c vfunc2()"; }
void func2() { qDebug() << "C func2()"; }
private:
int m_data1, m_data4;
};
記住:虛表是屬于類的,上面代碼的虛表通過(guò)IDE調(diào)試模式如下:



虛表關(guān)系如圖:

我們來(lái)仔細(xì)分析:
類A包含2個(gè)虛函數(shù),所以A的虛表元素是2個(gè)。
類B繼承至A,但是重寫(xiě)了虛函數(shù),對(duì)于重寫(xiě)的虛函數(shù),會(huì)增加新的地址來(lái)保存新的函數(shù)指針。所以對(duì)于類B的虛表,有一個(gè)是繼承至A,即
A::vfunc2*(),還有一個(gè)是新的函數(shù),即B::vfunc1()。而對(duì)于C來(lái)說(shuō),它繼承至B,但是重寫(xiě)了虛函數(shù),所以會(huì)多出一個(gè)
C::vfunc2(),那么另一個(gè)虛函數(shù)指針是執(zhí)行類B的vfunc1()還是類A的vfunc1()呢?這里會(huì)指向
B::vfunc1(),規(guī)律非常簡(jiǎn)單:對(duì)象的虛表指針用來(lái)指向類的虛表,虛表中的指針會(huì)指向其繼承的最近的一個(gè)類的虛函數(shù),所以C的虛表先是指向B的虛表,然后由于重寫(xiě)虛函數(shù)的原因,會(huì)修改其中一個(gè)指針的值。
基類指針指向子類對(duì)象
搞明白上面的繼承關(guān)系后,多態(tài)就非常容易理解了,比如下面代碼:
B b; A *p = &b; p->vfunc1();
這里使用p來(lái)調(diào)用vfunc1(),最終會(huì)調(diào)用類B中的虛函數(shù),過(guò)程分析如下:
- 程序執(zhí)行
p->vfunc1()時(shí),會(huì)發(fā)現(xiàn)p是一個(gè)指針,而且調(diào)用的是虛函數(shù)。 - 根據(jù)虛表指針
p->__vptr來(lái)訪問(wèn)對(duì)象b對(duì)應(yīng)的虛表。雖然指針p是基類A的類型,但是__vptr也是基類的一部分,是編譯器加的,所以p->__ptr可以訪問(wèn)對(duì)象的虛表。 - 在虛表中查找調(diào)用的函數(shù),由于虛表是在編譯時(shí)就確定,所以
p->vfunc1()根據(jù)上面的圖可知會(huì)調(diào)用B的虛函數(shù),即重寫(xiě)的虛函數(shù)B::vfunc1()。
這個(gè)過(guò)程就是動(dòng)態(tài)綁定來(lái)實(shí)現(xiàn)的多態(tài),非常容易理解。
總結(jié)
什么情況下會(huì)發(fā)生動(dòng)態(tài)綁定呢?有如下幾個(gè)條件:
- 通過(guò)指針調(diào)用函數(shù)。
- 指針upcast向上轉(zhuǎn)型,比如繼承類向基類的轉(zhuǎn)換。
- 調(diào)用的是虛函數(shù)。
到此這篇關(guān)于C++虛函數(shù)表的使用小結(jié)的文章就介紹到這了,更多相關(guān)C++虛函數(shù)表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C/C++常用函數(shù)易錯(cuò)點(diǎn)分析
這篇文章主要介紹了C/C++常用函數(shù)易錯(cuò)點(diǎn)分析,包含了memset、sizeof、getchar三個(gè)常用函數(shù)的分析,需要的朋友可以參考下2014-08-08
一篇文章帶你了解C語(yǔ)言二分查找的簡(jiǎn)單應(yīng)用
這篇文章主要介紹了二分查找算法在C語(yǔ)言程序中的使用示例,文中最后提到了使用二分查找法一個(gè)需要注意的地方,需要的朋友可以參考下2021-08-08
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易網(wǎng)絡(luò)聊天室
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易網(wǎng)絡(luò)聊天室,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06

