class C{
public:void x1(){};void x2(){};};C c;
cout << sizeof(c) <<"\n";1字節
class D{
public:void x1(){};void x2(){};virtual void x3(){};//void *vptr看不見的虛函數表指針
};
D d;
cout << sizeof(d) <<"\n";8字節
類A中,定義了一個虛函數,編譯器就會生成一個看不見的成員變量(虛函數表指針)占用一定字節。類中至少存在一個虛函數時,編譯時,就會為這個類生成一個虛函數表vtbl,經過編譯、鏈接,類和虛函數表都會保存到可執行文件中,執行時會被裝載到內存中。
在編譯期間,帶有虛函數的類會在編譯期間安插一個賦值語句(看不到)
vptr=&A::vftable;
這樣就使vptr指向vtbl。
class A{
public://void *vptr看不見的虛函數表指針void func1(){};void func2(){};virtual void vfunc1(){};virtual void vfunc2(){};virtual ~A(){};
private://vptr=&A::vftable;int m_a;int m_b;
};A a;
cout << sizeof(a) <<"\n";16字節 //虛函數一共8字節,m_a和m_b一共8字節
多態必須存在虛函數,沒有虛函數絕不可能存在多態
判斷有沒有多態:從代碼實現上來看,查看調用調用路線是不是利用從vptr找到vtbl,然后通過查詢vtbl來找到虛函數表的入口地址并去執行虛函數,如果是這個流程,則就是多態,否則就不是多態。
多態發生的核心條件(必須同時滿足):
- 通過基類指針或基類引用調用函數
- 調用的是虛函數
- 調用形式:
指針->虛函數
或引用.虛函數
class Animal {
public:virtual void speak() { cout << "Animal sound" << endl; }
};class Cat : public Animal {
public:void speak() override { cout << "Meow" << endl; }
};// 場景1:通過基類指針調用虛函數
Animal* animal = new Cat();
animal->speak(); // ?? 多態:輸出"Meow"// 場景2:通過基類引用調用虛函數
Cat kitty;
Animal& ref = kitty;
ref.speak(); // ?? 多態:輸出"Meow"
案例:
class Base
{
public:virtual void myvirfunc() {}
};
Base* pa = new Base();
pa->myvirfunc();//多態Base base;
base.myvirfunc();//不是多態Base* ybase = &base;
ybase->myvirfunc();//多態
1.程序中存在繼承關系,且父類至少有一個虛函數,但子類不強制重寫虛函數(除非是純虛函數 = 0
),要觸發運行時多態(動態多態),派生類必須重寫基類的虛函數。
2.必須通過父類指針或父類引用指向子類對象,才能觸發多態。
3.當通過父類指針/引用調用被重寫的虛函數時,才會表現出多態行為(動態綁定)。
// 基類(父類)必須包含虛函數
class Base {
public:virtual void myvirfunc() {} // 虛函數聲明(virtual 關鍵字)
};
// 派生類非必須重寫普通虛函數(基類已有默認實現)。僅當基類聲明為純虛函數(virtual void func() = 0;)時,派生類必須重寫
class Derive : public Base {
public:virtual void myvirfunc() {} // 重寫虛函數(virtual 可省略,但建議保留)//C++11 后可用 override 關鍵字顯式標記重寫(如 void myvirfunc() override {})
};
//父類指針指向子類對象
Derive derive;
Base* pbase = &derive;
pbase->myvirfunc(); //Derive::myvirfunc()
//或者
Base* pbase2 = new Derive(); //釋放內存請自行釋放,在這里沒演示
pbase2->myvirfunc(); //Derive::myvirfunc()
//父類引用綁定(指向)子類對象
Derive derive2;
Base& yinbase = derive2;
yinbase.myvirfunc(); //Derive::myvirfunc()
虛析構函數
class Base {
public:virtual ~Base() {} // 必須聲明為虛析構函數!
};class Derive : public Base {
public://~Derived() {} // 自動成為虛函數,即使不寫virtual~Derive() override {} // 但建議顯式使用 override(C++11)
};Base* pb = new Derive();
delete pb; // 正確調用 Derive::~Derive() → Base::~Base()
- 若不聲明虛析構函數:
delete pb
僅調用Base::~Base()
,導致派生類資源泄漏!
-
虛函數表的共享機制
- 同一類的所有對象共享同一個 vtbl(節省內存)
vptr
在對象構造時被初始化指向該類的 vtbl
-
多繼承下的 vptr
class Derived : public Base1, public Base2 {virtual void new_func() {} };
- 派生類會包含多個 vptr(每個基類一個)
- vtbl 可能包含多個子表(Thunk技術處理指針偏移)
class Base1 { public:virtual void func1() { cout << "Base1::func1\n"; }virtual ~Base1() {} };class Base2 { public:virtual void func2() { cout << "Base2::func2\n"; }virtual ~Base2() {} };class Derived : public Base1, public Base2 { public:virtual void new_func() { cout << "Derived::new_func\n"; }virtual void func1() override { cout << "Derived::func1\n"; }virtual ~Derived() {} };
1. Derived類的Base1虛函數表
索引 | 函數類型 | 實際函數地址 -----|--------------------|----------------- 0 | 析構函數 | Derived::~Derived 1 | func1() | Derived::func1 2 | new_func() | Derived::new_func
2. Derived類的Base2虛函數表
索引 | 函數類型 | 實際函數地址 -----|--------------------|----------------- 0 | 析構函數 | Thunk to Derived::~Derived 1 | func2() | Base2::func2
- 每個有虛函數的基類都會有自己的vptr
- Derived類包含兩個vptr:一個來自Base1,一個來自Base2
- 派生類新增的虛函數會添加到第一個基類的虛函數表中
總結:多態性的核心邏輯
步驟 | 關鍵操作 | 作用 |
---|---|---|
必要條件 | 基類聲明虛函數,派生類重寫 | 建立動態綁定基礎 |
橋梁搭建 | 基類指針/引用指向派生類對象 | 提供統一接口 |
多態觸發 | 通過基類接口調用虛函數 | 運行時動態解析實際函數地址 |
底層支持 | 虛函數表(vtable)+ 虛表指針(vptr) | C++ 實現動態綁定的核心機制 |
📌 核心結論:多態性 = 虛函數重寫 + 基類訪問派生類對象 + 通過基類接口調用函數