在繼承體系中,基類指針指向派生類對象時,若直接呼叫成員函數,預設會調用基類版本——這顯然違背直覺。請看經典案例:
class People {
public:
virtual void display() { /* 基類實作 */ }
};
class Teacher : public People {
public:
void display() override { /* 派生類實作 */ }
};
People *p = new Teacher("趙老師", 45, 8200);
p->display(); // 若無 virtual,輸出基類版本(錯誤);加上 virtual,輸出派生類版本(正確)
為消除這種「指向派生類卻調用基類函數」的尷尬,C++ 引入 虛函數:只需在基類函數宣告前加 virtual,即可實現動態綁定——根據對象實際類型決定調用哪個版本。
p->display()),因指針指向不同對象而呈現不同行為。引用本質是指針別名,同樣能觸發動態綁定:
People p("王志剛", 23);
Teacher t("趙宏佳", 45, 8200);
People &rp = p, &rt = t;
rp.display(); // 基類版本
rt.display(); // 派生類版本
但引用不如指針靈活(不可重新綁定),實務上多態多以指針為主。
假設開發軍事遊戲,擁有陸軍、空軍及其裝備的多層繼承樹:
class Troops { virtual void fight() { cout << "Strike back!"; } };
class Army : public Troops { void fight() override { cout << "--Army is fighting!"; } };
class _99A : public Army { void fight() override { cout << "----99A(Tank) fighting!"; } };
// ... 更多派生類
Troops *p = new Troops; p->fight();
p = new Army; p->fight();
p = new _99A; p->fight();
// 僅用一個指針 p 即可調用所有派生類的 fight()
優勢:無需為每個派生類定義專屬指針,單一基類指針統籌全局,極大簡化代碼並提升擴展性。對於大型專案,多態是實現「開閉原則」的關鍵工具。
| 綁定類型 | 決策依據 | 典型場景 |
|---|---|---|
| 靜態(早期) | 指針/引用的聲明類型 | 普通成員函數、重載函數 |
| 動態(晚期) | 對象的實際類型 | 虛函數 |
虛函數透過 vtable 實現動態綁定,編譯器在構造/析構時維護 vptr,確保正確分發。詳細機制請參閱《虛函數表與動態綁定深度解析》。
virtual 關鍵字開啟動態綁定。