首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

C++ 多態與虛函數核心機制:從靜態綁定到動態分發的完整圖解

     編輯:VC 教學

**多態** 是面向對象語言的靈魂:同一接口調用,根據對象實際類型執行不同實現。C++ 通過 **虛函數** + **虛函數表 (vtable)** 實現運行時多態。本文對比靜態綁定 (重載/隱藏) 與動態綁定 (虛函數),深入剖析 vtable 佈局、指針/引用多態條件、純虛函數與抽象類設計模式。

靜態綁定 vs 動態綁定

class Vehicle {
protected: float speed; int total;
public:
    Vehicle(float s, int t) : speed(s), total(t) {}
    void ShowMember() {  // 非虛函數:編譯期綁定
        cout << speed << "|" << total << endl;
    }
};

class Car : public Vehicle {
    int aird;
public:
    Car(int a, float s, int t) : Vehicle(s, t), aird(a) {}
    void ShowMember() {  // 隱藏基類同名函數 (非覆寫)
        cout << speed << "|" << total << "|" << aird << endl;
    }
};

int main() {
    Vehicle v(120, 4);
    Car c(180, 110, 4);
    v.ShowMember();  // Vehicle::ShowMember()
    c.ShowMember();  // Car::ShowMember() — 靜態綁定,看變量類型
}

⚠️ 若用 Vehicle* pv = &c; pv->ShowMember(); 仍調用 Vehicle::ShowMember() — **無多態**。

引入虛函數:開啟動態分發


class Vehicle {
    // ...
    virtual void ShowMember() {  // 加上 virtual
        cout << speed << "|" << total << endl;
    }
};

class Car : public Vehicle {
    // ...
    void ShowMember() override {  // C++11 override 關鍵字檢查
        cout << speed << "|" << total << "|" << aird << endl;
    }
};

int main() {
    Vehicle* pv = new Car(180, 110, 4);
    pv->ShowMember();  // Car::ShowMember() — 動態綁定,看對象實際類型
    delete pv;
}

虛函數表 (vtable) 內存模型

Vehicle 對象佈局 (單繼承):
+------------------+
| vptr (指向 Vehicle::vtable) |
+------------------+
| speed (float)    |
| total (int)      |
+------------------+

Vehicle::vtable (只讀數據段):
+------------------+
| &Vehicle::ShowMember |
+------------------+

Car 對象佈局:
+------------------+
| vptr (指向 Car::vtable)     |
+------------------+
| speed (float)    |  ← 繼承自 Vehicle
| total (int)      |
+------------------+
| aird (int)       |  ← Car 自有
+------------------+

Car::vtable:
+------------------+
| &Car::ShowMember |  ← 覆寫條目替換基類
+------------------+

調用 pv->ShowMember() 步驟:

  1. 讀取對象首地址的 vptr;
  2. 查表找到 ShowMember 在 vtable 中的槽位偏移;
  3. 跳轉到該地址執行 (間接跳轉,極快)。

純虛函數與抽象類


class Shape {
public:
    virtual void Draw() = 0;  // 純虛函數:無實現,強制派生類覆寫
    virtual ~Shape() {}       // 虛析構:刪除基類指針時正確調用派生析構
};

class Circle : public Shape {
    int r;
public:
    Circle(int r_) : r(r_) {}
    void Draw() override { /* 畫圓 */ }
};

// Shape s; // 錯誤:抽象類不可實例化
Shape* s = new Circle(10); // OK
s->Draw();
delete s;

多態使用鐵律 (Effective C++ 條款)

規則說明
基類析構函數**必須聲明為 virtual**否則 delete basePtr 僅調用基類析構,派生資源洩漏
覆寫函數**加 override** (C++11)編譯期捕獲簽名不匹配 (如 const/參數差異)
**不要**在構造/析構函數中調用虛函數此時 vptr 尚未/已經切換,調用的是當前類版本
虛函數參數**傳值/引用/指針**一致协变返回类型允許返回派生類指針/引用
避免「預設參數值在虛函數中」默認參數靜態綁定,動態類型可能導致驚訝行為

進階:虛繼承與菱形問題


class Base { int x; };
class A : virtual public Base { };
class B : virtual public Base { };
class C : public A, public B { };  // 只有一份 Base::x
  • virtual 繼承引入 **vbptr** (虛基類表指針),增加對象大小與訪問間接層;
  • 僅在**菱形繼承**確實需要共享基類子對象時使用;
  • 構造順序:虛基類 → 非虛基類 → 派生類,由**最派生類**負責調用虛基類構造函數。

掌握虛函數表機制、抽象類設計、虛析構必要性、構造析構期行為差異,是寫出正確、高效、可擴展 C++ 多態代碼的核心內功。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved