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

C++ 多態核心機制深度解析:虛擬函式、動態綁定與抽象類別

     編輯:關於C++

多態本質:介面重用與晚期綁定

多態意為「多種形態」,核心口號是「一個介面,多種實作」。程式在執行期根據物件實際型別決定呼叫哪個函數版本,這是面向對象程式設計的核心概念。多態與非多態的本質區別在於函數位址綁定時機:

  • 早期綁定(靜態綁定):編譯期確定函數位址,生成固定呼叫指令。一般函數、多載函數、樣板函數皆屬此類。
  • 晚期綁定(動態綁定):執行期根據物件實際型別查找虛擬函數表決定呼叫位址。虛擬函數實現多態的基石。

虛擬函數與虛擬函數表機制

以 virtual 宣告的成員函數為虛擬函數。編譯器為每個含虛擬函數的類別建立虛擬函數表,儲存該類別所有虛擬函數的位址。每個物件內含一隱藏指標(vptr)指向其類別的虛擬函數表,使物件大小增加(32 位元系統 4 bytes)。

衍生類別覆寫虛擬函數時,函數名稱、參數列表、回傳型別必須一致(回傳型別允許共變:基底回傳基底指標,衍生可回傳衍生指標)。覆寫的函數自動成為虛擬函數。

經典範例:基底指標指向衍生物件

class A {
public:
    void foo() { printf("1\n"); }
    virtual void fun() { printf("2\n"); }
};
class B : public A {
public:
    void foo() { printf("3\n"); }
    void fun() { printf("4\n"); } // 覆寫虛擬函數
};
int main() {
    A a; B b;
    A* p = &a;
    p->foo();  // 1 (非虛擬,早期綁定)
    p->fun();  // 2 (虛擬,動態綁定到 A::fun)
    p = &b;
    p->foo();  // 1 (非虛擬,指標型別為 A*,固定呼叫 A::foo)
    p->fun();  // 4 (虛擬,動態綁定到 B::fun)
}

關鍵觀察:非虛擬函數 foo() 永遠根據指標靜態型別呼叫;虛擬函數 fun() 根據物件動態型別呼叫。

名稱隱藏規則:多態陷阱

C++ 隱藏規則導致衍生類別同名函數遮蔽基底同名函數,與是否虛擬無關:

  1. 同名、參數不同 → 隱藏基底所有同名函數(非多載)。
  2. 同名、參數相同、基底非虛擬 → 隱藏基底函數(非覆寫)。

class Base {
    virtual void f(float);  // 虛擬
    void g(float);          // 非虛擬
    void h(float);          // 非虛擬
};
class Derived : public Base {
    virtual void f(float);  // 覆寫
    void g(int);            // 隱藏 Base::g(float) — 非多載!
    void h(float);          // 隱藏 Base::h(float) — 非覆寫!
};

對策:衍生類別需明確 using Base::g; 引入被隱藏名稱,或完整實作所有多載版本。

執行期型別識別(RTTI)

  • dynamic_cast<Derived*>(base_ptr):安全向下轉型。成功回傳衍生指標,失敗回傳 nullptr。參考版本失敗拋出 bad_cast。要求類別有虛擬函數。
  • typeid(expr):回傳 const type_info&。type_info::name() 取得型別名稱(編譯器相依)。支援 ==/!= 比較。類別有虛擬函數時利用多態識別動態型別。

純虛擬函數與抽象類別

以 virtual ReturnType func() = 0; 宣告的函數為純虛擬函數(無函數本體)。含純虛擬函數的類別為抽象類別,無法直接實例化物件。抽象類別作為介面規範,強制衍生類別實作純虛擬函數,實現介面契約。

引入原因:

  1. 基底類別本身不應建立物件(如「動物」不可實例化,僅「老虎」、「孔雀」可)。
  2. 統一介面,利用多態實現執行期多型行為。

虛擬解構子:資源管理關鍵

透過基底指標 delete 衍生物件時,若基底解構子非虛擬,僅呼叫基底解構子導致衍生部分資源洩漏。基底類別解構子必須宣告為 virtual。解構子可為純虛擬(但必須提供定義);建構子不可為虛擬(建構時型別已確定,虛擬表未就緒)。

多態分類總結

分類實現機制綁定時機典型例子
編譯期多態函數多載、樣板編譯期max(int, int) vs max(double, double)
執行期多態虛擬函數執行期基底指標呼叫衍生覆寫函數

最佳實踐原則

  • 基底類別解構子預設宣告 virtual。
  • 覆寫虛擬函數保持參數列表一致,使用 override (C++11) 明確語意並獲編譯器檢查。
  • 避免名稱隱藏:衍生類別同名函數參數不同,或使用 using Base::func;。
  • 利用多態時必須透過基底指標/參考操作衍生物件,不可建立基底物件切割衍生部分。
  • 抽象類別定義介面契約,具體類別實作細節,符合依賴倒置原則。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved