**多態** 是面向對象語言的靈魂:同一接口調用,根據對象實際類型執行不同實現。C++ 通過 **虛函數** + **虛函數表 (vtable)** 實現運行時多態。本文對比靜態綁定 (重載/隱藏) 與動態綁定 (虛函數),深入剖析 vtable 佈局、指針/引用多態條件、純虛函數與抽象類設計模式。
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;
}
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() 步驟:
vptr;ShowMember 在 vtable 中的槽位偏移;
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;
| 規則 | 說明 |
|---|---|
| 基類析構函數**必須聲明為 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++ 多態代碼的核心內功。