C++ 物件導向程式設計中,函數調用的綁定時機決定了程式的行為模式。靜態綁定(早期綁定)在編譯期確定,動態綁定(晚期綁定)在執行期根據物件實際類型確定,是實現多態的基石。
| 特性 | 靜態綁定 | 動態綁定 |
|---|---|---|
| 綁定時機 | 編譯期 | 執行期 |
| 決策依據 | 指標/引用的聲明類型 | 物件的實際類型 |
| 實現機制 | 直接函數調用 | 虛函數表查找 |
| 適用函數 | 非虛函數、多載函數、樣板函數 | 虛函數 |
| 效能 | 極高(無額外開銷) | 略低(一次間接查找) |
| 多態支援 | 不支援 | 支援 |
當類別宣告虛函數時,編譯器會:
class Base {
public:
virtual void func() { cout << "Base::func\n"; }
void nonVirtual() { cout << "Base::nonVirtual\n"; }
};
class Derived : public Base {
public:
void func() override { cout << "Derived::func\n"; } // 覆寫
void nonVirtual() { cout << "Derived::nonVirtual\n"; } // 隱藏
};
Base* ptr = new Derived(); ptr->func(); // 動態綁定:輸出 "Derived::func" ptr->nonVirtual(); // 靜態綁定:輸出 "Base::nonVirtual" Derived obj; Base& ref = obj; ref.func(); // 動態綁定:輸出 "Derived::func" ref.nonVirtual(); // 靜態綁定:輸出 "Base::nonVirtual"
行為:虛函數機制失效,調用當前建構中類別的版本(基類建構子調用基類版本,派生類建構子調用派生版本)。
原因:派生類部分尚未建構完成,虛指標尚未指向派生類虛函數表。
class Base {
public:
Base() { init(); } // 危險!
virtual void init() { cout << "Base init\n"; }
};
class Derived : public Base {
int data_;
public:
Derived() : data_(42) {}
void init() override { cout << "Derived init, data=" << data_ << '\n'; }
};
// 輸出: "Base init" -> data_ 尚未初始化!
Base* p = nullptr; p->nonVirtual(); // 未定義行為,但許多編譯器「碰巧」能執行(靜態綁定無需 this) p->func(); // 崩潰:需透過虛指標查找虛函數表
預設參數在靜態綁定時(編譯期)根據指標類型決定,而虛函數在動態綁定時(執行期)決定調用哪個函數體,導致不一致:
class Base {
public:
virtual void func(int x = 10) { cout << "Base: " << x << '\n'; }
};
class Derived : public Base {
public:
void func(int x = 20) override { cout << "Derived: " << x << '\n'; }
};
Base* p = new Derived();
p->func(); // 輸出 "Derived: 10" — 函數體是 Derived,但預設參數是 Base 的 10!
解決方案:避免虛函數使用預設參數,或使用 NVI(非虛擬介面)模式。
final 關鍵字(C++11)允許編譯器去虛擬化:void func() final;virtual(或 = default)。override(C++11),編譯器會檢查簽名一致性。virtual,減少虛函數表大小與開銷。深入理解靜態與動態綁定,不僅是面試必考點,更是寫出正確、高效、可維護 C++ 代碼的核心功力。