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 指標,節省記憶體、支援動態派發。
// === 核心架構 ===
// 虛函數表結構
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)
}
shape_area(s) 隱藏 s->vptr->area(s) 細節| 特性 | C 手工實現 | C++ 編譯器支援 |
|---|---|---|
| 語法糖 | 無 | virtual, override |
| 建構/解構順序 | 手動控制 | 自動管理 |
| 多重繼承指標調整 | 手動計算位移 | 編譯器自動插入 thunk |
| RTTI / dynamic_cast | 需自行實現 | 內建支援 |
| 效能 | 相同 (間接跳躍) | 相同 |
掌握 C 語言手工實現多態機制,不僅能在嵌入式、核心開發等無 C++ 環境下應用,更能深度理解 C++ 物件模型底層運作,成為更優秀的系統程式設計師。