虛函數表,以及虛函數指針是實現多態性(Polymorphism)的關鍵機制。多態性允許我們通過基類的指針或引用來調用派生類的函數 定義 虛函數(Virtual Function) 定義:類中使用virtual 關鍵字修飾的函數 叫做虛函數 語法: class Base { public: virtu ...
虛函數表,以及虛函數指針是實現多態性(Polymorphism)的關鍵機制。多態性允許我們通過基類的指針或引用來調用派生類的函數
定義
虛函數(Virtual Function)
-
定義:類中使用virtual 關鍵字修飾的函數 叫做虛函數
-
語法:
class Base {
public:
virtual void show() { cout << "Base show" << endl; }
};
虛函數表(Virtual Function Table)
- 定義:當類含有至少一個虛函數時,編譯器會為該類創建一個虛函數表。這個表是一個編譯時構建的靜態數組,存儲了指向類中所有虛函數的指針。如果一個派生類重寫了這些函數,那麼在派生類的虛表中,相應函數的指針會被更新為指向派生類中的版本。
- 作用:v-table使得在運行時可以實現函數的動態綁定,允許通過基類的指針或引用調用正確的函數版本。
虛函數指針(Virtual Pointer)
- 定義:每個含有虛函數的類的對象(實例化出的)會持有一個指向相應虛表的指針,這個指針通常被稱為虛指針(vptr)。vptr是對象運行時的一部分,確保了當通過基類指針調用虛函數時,能夠查找到正確的函數實現。
- 作用:在對象的生命周期開始時,構造函數會設置vptr以指向相應的虛函數表。如果有派生類對象,它的構造函數會更新vptr,以指向派生類的虛函數表。這保證了通過基類的引用或指針調用虛函數也會執行最派生類的重寫版本。
示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
};
class Derived : public Base {
public:
void func1() override { cout << "Derived::func1" << endl; }
// func2() 繼承自 Base
};
void printVTable(void* obj) {
cout << "vptr Address: " << obj << endl;
void** vTable = *(void***)obj;
cout << "VTable[0] (func1): " << vTable[0] << endl;
cout << "VTable[1] (func2): " << vTable[1] << endl;
}
int main() {
Base* base = new Base();
Derived* derived = new Derived();
cout << "Base object:" << endl;
printVTable(base);
cout << "\nDerived object:" << endl;
printVTable(derived);
delete base;
delete derived;
return 0;
}
程式輸出如下,可以看到沒用重寫的func2函數地址是一樣的。
Base object:
vptr Address: 0x8c1510
VTable[0] (func1): 0x422270
VTable[1] (func2): 0x4222b0
Derived object:
vptr Address: 0x8c1530
VTable[0] (func1): 0x422330
VTable[1] (func2): 0x4222b0
如下圖所示:
面試題
(來自2024騰訊實習面試)場景題:一個類 A,裡面有一個列印 helloworld 的虛函數,然後類 A 會在構造函數里調用這個虛函數,此時有個類 B,繼承A,重寫了這個 helloworld虛函數,問你在創建類 B 時,會列印 A 里的 helloworld 還是 B 里的。
代碼如下:
class A {
public:
A() {
print();
}
virtual void print() {
cout << "A print" << endl;
}
};
class B : public A {
public:
void print() override {
cout << "B print" << endl;
}
};
int main() {
A* aTemp = new B();
delete aTemp;
}
解答:基類構造函數執行的時候,派生類的部分尚未初始化,因此調用的虛函數不會下發到派生類中。
最終會列印 A print,而不是類 B 里重寫的版本
本文來自博客園,作者:江水為竭,轉載請註明原文鏈接:https://www.cnblogs.com/Az1r/p/18081756