三種函數類型對比
| 類型 | 關鍵字 | 實現 | 繼承語義 | 多態性 | 實例化限制 |
| 虛函數 | virtual | 基類提供默認實現 | 接口繼承 + 實現繼承(可選覆蓋) | 運行時動態綁定 | 基類可實例化 |
| 純虛函數 | virtual ... = 0 | 基類不提供實現 | 接口繼承(強制派生類實現) | 運行時動態綁定 | 基類為抽象類,不可實例化 |
| 普通函數 | 無 | 基類提供強制實現 | 實現繼承(不建議覆蓋) | 編譯時靜態綁定 | 基類可實例化 |
代碼示例
class Base {
public:
virtual void pureVirtual() = 0; // 純虛函數:接口契約
virtual void virtualFunc() { cout << "Base::virtualFunc\n"; } // 虛函數:默認實現
void normalFunc() { cout << "Base::normalFunc\n"; } // 普通函數:強制實現
virtual ~Base() {} // 基類析構必須虛!
};
class Derived : public Base {
public:
void pureVirtual() override { cout << "Derived::pureVirtual\n"; } // 必須實現
void virtualFunc() override { cout << "Derived::virtualFunc\n"; } // 可選覆蓋
void normalFunc() { cout << "Derived::normalFunc\n"; } // 隱藏而非覆蓋!
};
關鍵行為差異演示
int main() {
Base* ptr = new Derived();
Derived* dptr = new Derived();
ptr->pureVirtual(); // Derived::pureVirtual ✅ 動態綁定
ptr->virtualFunc(); // Derived::virtualFunc ✅ 動態綁定
ptr->normalFunc(); // Base::normalFunc ❌ 靜態綁定!
dptr->pureVirtual(); // Derived::pureVirtual
dptr->virtualFunc(); // Derived::virtualFunc
dptr->normalFunc(); // Derived::normalFunc ✅ 編譯時綁定到 Derived
delete ptr; delete dptr;
return 0;
}
核心原理解析
虛函數表(vtable)機制
- 含虛函數的類,編譯器生成 vtable(虛函數地址表)
- 每個對象包含 vptr 指向類的 vtable
- 調用虛函數時:
obj->vptr[vtable_index]() 間接跳轉
- 純虛函數在 vtable 中佔位,地址為 0 或純虛調用處理器
普通函數無多態
- 編譯時根據指針/引用的靜態類型確定調用哪個函數
Base* ptr = new Derived; ptr->normalFunc() → 調用 Base::normalFunc
- 派生類同名函數是隱藏,而非覆蓋
設計原則總結
- 接口繼承用純虛:定義契約,強制實現(如
Drawable::draw())
- 默認行為用虛函數:提供缺省實現,允許選擇性覆蓋(如
Animal::makeSound())
- 不變行為用普通函數:工具方法、輔助邏輯,禁止覆蓋(用
final 或私有化)
- 基類析構必須虛:確保通過基類指針刪除派生對象時正確調用派生析構
常見錯誤避坑
- ❌ 普通函數加
virtual 以為能多態 → 那是虛函數
- ❌ 派生類重寫普通函數期望多態 → 只會隱藏,基類指針仍調用基類版本
- ❌ 忘記基類析構函數加
virtual → 記憶體洩漏、未定義行為
- ✅ 用
override (C++11) 明確標記覆蓋意圖,編譯器檢查錯誤