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

C 語言實現虛函數與多態:手動構建虛函數表機制

     編輯:關於C

前言:從 C++ 虛函數機制到 C 語言實現

在《C++ 程式設計語言》等經典著作中,虛函數的實現機制有詳細闡述:編譯器透過虛函數表(VTABLE)在編譯期插入隱藏代碼,保存類型資訊與虛函數位址,執行期根據實際物件類型查找對應的函數實作。本文將示範如何在 C 語言中手動模擬這一機制,實現類似 C++ 的多態行為。

核心設計思路

網路上常見的做法是在基類中保存派生類指標,但這違背了多態的本質——多態應透過基類介面使用派生類,基類不應依賴派生類資訊。本文採用的方案是:

  • 在「基類」結構體中顯式聲明 void** vtable 成員作為函數指標陣列,並以 int vt_size 記錄陣列長度
  • 基類定義的每個虛函數在陣列中的位置、順序固定,作為約定
  • 所有「派生類」必須填充基類的虛函數指標陣列(可動態擴展),未覆寫的虛函數對應位置置 0
  • 基類的函數實作中遍歷虛函數表,尋找繼承鏈中最後一個非 0 的函數指標,即為實際應調用的實作

結構體定義與繼承關係

struct base {
    void **vtable;
    int vt_size;
    void (*func_1)(struct base *b);
    int (*func_2)(struct base *b, int x);
};

struct derived {
    struct base b;
    int i;
};

struct derived_2 {
    struct derived d;
    char *name;
};

注意:derived 和 derived_2 並未額外聲明 func_1、func_2。在 C 語言虛函數實現中,派生類覆寫虛函數時無需在結構體中重複聲明,只需在實作檔案中定義覆寫函數,並在建構函數中將其填入虛函數表。

建構與初始化分離模式

面臨的問題:派生類不知道基類函數實作位置(高內聚、低耦合原則),建構派生類實例時如何初始化虛函數表?C++ 編譯器會自動調用父類建構函數,C 語言需手動協調。

解決方案:每個類提供兩個函數——

  • 建構函數(如 new_derived()):供使用者建立實例
  • 初始化函數(如 initialize_derived()):供直接繼承的子類調用,確保虛函數表正確初始化

派生類只需調用直接基類的初始化函數,層層遞進,保證虛函數表正確構建。

struct derived *new_derived() {
    struct derived *d = malloc(sizeof(struct derived));
    initialize_base((struct base*)d);
    initialize_derived(d);  // 設置或修改 VTABLE
    return d;
}

虛析構函數實現

刪除物件時需從派生類析構函數依次調用至基類。在基類中顯式聲明析構函數指標 deletor,基類實作中反向遍歷虛函數表調用:

struct base {
    void **vtable;
    int vt_size;
    void (*func_1)(struct base *b);
    int (*func_2)(struct base *b, int x);
    void (*deletor)(struct base *b);
};

虛函數表佈局圖解

虛函數表結構示意圖

假設 derived 實作三個虛函數,derived_2 實作兩個(func_2 未覆寫),derived_2 實例的最終虛函數表長度(vt_size)為 9;derived 實例則為 6。關鍵限制:基類必須實作所有虛函數,否則機制無法運轉——所有調用從基類實作進入,透過遍歷虛函數表尋找派生類實作。

總結

此機制下,透過基類指標存取派生類實例時,行為基本與 C++ 一致。初始化函數如何裝配虛函數表、基類虛函數實作細節,可依上述思路自行編寫。這種手動實現雖較繁瑣,但深入理解後有助於掌握物件導向核心機制的本質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved