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

C語言實現多態:結構體與函數指標模擬物件導向完整實戰

     編輯:關於C語言

C語言實現多態:從結構體到虛函數表的完整演進

C 語言雖無內建類別、繼承、多態機制,但透過結構體 + 函數指標 + 記憶體佈局控制,可完整模擬物件導向核心特性。本文從基礎結構體開始,逐步建構虛函數表機制,實現真正的執行時期多態。

階段一:基礎結構體與函數指標

// 形狀基類結構
struct Shape {
    void (*print)(void);
    float (*area)(struct Shape *this);
};

// 矩形子類
struct Rectangle {
    float width;
    float height;
    void (*print)(void);
    float (*area)(struct Rectangle *this);
};

// 方法實作
void rectangle_print(void) { printf("Rectangle\n"); }
float rectangle_area(struct Rectangle *this) { return this->width * this->height; }

// 建構函數
void rectangle_ctor(struct Rectangle *r, float w, float h) {
    r->width = w; r->height = h;
    r->print = rectangle_print;
    r->area = rectangle_area;
}

階段二:記憶體佈局相容性問題

直接轉型 (Shape*)rect 會導致函數指標位移錯位:

// Shape 佈局: [print][area] (offset 0, 8)
// Rectangle 佈局: [width][height][print][area] (offset 0, 4, 8, 16)
// Shape* 指向 Rectangle 時,讀取 print 會誤讀 width 值 → 崩潰

解法:填充欄位對齊虛函數表位置

struct Shape {
    char padding[8];  // 佔位,對齊 Rectangle 中的 width+height
    void (*print)(void);
    float (*area)(struct Shape *this);
};

// 現在 Shape.print @ offset 8,Rectangle.print @ offset 8 → 完美對齊

階段三:虛函數表 (vtable) 機制 — 工業級實現

將函數指標集中到靜態唯讀表,每個實例僅持有 vtable 指標,節省記憶體、支援動態派發。

// === 核心架構 ===

// 虛函數表結構
typedef struct Shape_vtable {
    void (*print)(void *self);
    float (*area)(void *self);
    void (*dtor)(void *self);  // 解構函數
} Shape_vtable;

// 基類實例結構
typedef struct Shape {
    const Shape_vtable *vptr;  // 唯一成員:虛表指標
    // 基類共同資料...
} Shape;

// 子類實例:繼承基類佈局 (vptr 必須位於 offset 0)
typedef struct Rectangle {
    Shape base;  // 嵌入基類,確保 vptr @ offset 0
    float width;
    float height;
} Rectangle;

// === 虛表定義 (唯讀、共享) ===
static void rectangle_print(void *self) {
    Rectangle *r = (Rectangle*)self;
    printf("Rectangle: %.2f x %.2f\n", r->width, r->height);
}
static float rectangle_area(void *self) {
    Rectangle *r = (Rectangle*)self;
    return r->width * r->height;
}
static void rectangle_dtor(void *self) {
    // 釋放子類特有資源
    free(self);
}

static const Shape_vtable rectangle_vtable = {
    .print = rectangle_print,
    .area = rectangle_area,
    .dtor = rectangle_dtor
};

// === 建構/解構 ===
Shape* rectangle_new(float w, float h) {
    Rectangle *r = malloc(sizeof(Rectangle));
    r->base.vptr = &rectangle_vtable;  // 關鍵:綁定虛表
    r->width = w; r->height = h;
    return (Shape*)r;
}

void shape_delete(Shape *s) {
    s->vptr->dtor(s);  // 動態派發解構函數
}

// === 多態呼叫 ===
void shape_print(Shape *s) { s->vptr->print(s); }
float shape_area(Shape *s) { return s->vptr->area(s); }

階段四:多重繼承與介面模擬

// 介面:可序列化
typedef struct Serializable_vtable {
    char* (*to_json)(void *self);
    void (*from_json)(void *self, const char *json);
} Serializable_vtable;

typedef struct Serializable {
    const Serializable_vtable *vptr;
} Serializable;

// 多重繼承:Rectangle 同時實作 Shape + Serializable
typedef struct Rectangle {
    Shape shape_base;           // 主基類 @ offset 0
    Serializable serial_base;   // 次基類 @ offset sizeof(Shape)
    float width, height;
} Rectangle;

// 建構時初始化兩個 vptr
Rectangle* rectangle_new(float w, float h) {
    Rectangle *r = malloc(sizeof(Rectangle));
    r->shape_base.vptr = &rectangle_shape_vtable;
    r->serial_base.vptr = &rectangle_serial_vtable;
    r->width = w; r->height = h;
    return r;
}

// 介面轉型需位移指標
Serializable* as_serializable(Rectangle *r) {
    return &r->serial_base;  // 指標位移 sizeof(Shape)
}

關鍵設計原則總結

  1. vptr 必須位於 offset 0:確保單一繼承下基類指標與子類指標數值相等
  2. 虛表唯讀共享:所有同類實例共用一個虛表,節省記憶體
  3. 建構函數綁定 vptr:子類建構時覆蓋基類設定的 vptr
  4. 解構函數虛擬化:確保透過基類指標 delete 正確釋放子類資源
  5. 封裝派發函數:shape_area(s) 隱藏 s->vptr->area(s) 細節

與 C++ 虛函數機制對比

特性C 手工實現C++ 編譯器支援
語法糖無virtual, override
建構/解構順序手動控制自動管理
多重繼承指標調整手動計算位移編譯器自動插入 thunk
RTTI / dynamic_cast需自行實現內建支援
效能相同 (間接跳躍)相同

掌握 C 語言手工實現多態機制,不僅能在嵌入式、核心開發等無 C++ 環境下應用,更能深度理解 C++ 物件模型底層運作,成為更優秀的系統程式設計師。

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