多態與面向接口編程並非C++專有,純C語言同樣可以通過函數指針與結構體嵌套模擬虛函數表機制,實現運行時多態。本文通過一個「動物-狗/貓」的經典繼承體系,詳細演示如何在C中手工構建虛表、綁定虛表指針、實現多態調用。
_vptr),構造函數中綁定。_vptr,再調用虛表中的函數指針,實現動態綁定。#ifndef ANIMAL_H
#define ANIMAL_H
/* 虛函數表結構:定義所有虛函數指針 */
typedef struct vtable {
void (*eat)();
void (*bite)();
} vtable;
/* 基類對象結構:包含虛表指針 */
typedef struct Animal {
const vtable *_vptr; // 指向具體子類的虛表
} Animal;
#endif
/* dog.h */
#ifndef DOG_H
#define DOG_H
#include "animal.h"
typedef struct Dog {
Animal base_obj; // 基類子對象(必須放在第一位)
int x; // 子類自有數據
} Dog;
Animal* create_dog(); // 構造函數
#endif
/* dog.c */ #include#include #include "dog.h" static void dog_eat() { printf("dog_eat()\n"); } static void dog_bite() { printf("dog_bite()\n"); } /* 編譯期確定的 Dog 虛表 */ static const vtable dog_vtable = { dog_eat, dog_bite }; Animal* create_dog() { Dog *pDog = (Dog*)malloc(sizeof(Dog)); if (pDog) { pDog->base_obj._vptr = &dog_vtable; // 運行時綁定虛表 pDog->x = 0; } return (Animal*)pDog; // 向上轉型返回基類指針 }
/* cat.h */
#ifndef CAT_H
#define CAT_H
#include "animal.h"
typedef struct Cat {
Animal base_obj;
float y;
} Cat;
Animal* create_cat();
#endif
/* cat.c */ #include#include #include "animal.h" #include "cat.h" static void cat_eat() { printf("cat_eat()\n"); } static void cat_bite() { printf("cat_bite()\n"); } static const vtable cat_vtable = { cat_eat, cat_bite }; Animal* create_cat() { Cat *pCat = (Cat*)malloc(sizeof(Cat)); if (pCat) { pCat->base_obj._vptr = &cat_vtable; pCat->y = 0.0f; } return (Animal*)pCat; }
#include#include "animal.h" #include "dog.h" #include "cat.h" /* 面向基類接口編程 */ void ShowBite(Animal *pAnimal) { pAnimal->_vptr->bite(); // 動態綁定調用 } int main() { ShowBite(create_dog()); // 輸出: dog_bite() ShowBite(create_cat()); // 輸出: cat_bite() return 0; }
| 技術點 | C++ 實現 | C 模擬實現 |
|---|---|---|
| 虛表存儲 | 編譯器自動生成,存放在只讀數據段 | 手工定義 static const vtable |
| 虛表指針 | 編譯器隱式添加到類對象首部 | 手工在結構體首部添加 _vptr |
| 構造函數綁定 | 編譯器自動插入綁定代碼 | 在 create_xxx() 中手工賦值 |
| 繼承關係 | 語言級支持 | 結構體嵌套:基類子對象放第一位 |
| 多態調用 | ptr->func() | ptr->_vptr->func() |
Dog 對象內存佈局:
+------------------+
| _vptr -----------|----> dog_vtable { dog_eat, dog_bite }
+------------------+
| x (int) |
+------------------+
Animal 基類子對象佔據 Dog 對象的前 sizeof(Animal) 字節,實現向上轉型時地址不變。
純C模擬C++虛函數表,本質是將隱式的編譯器行為顯式化:手工定義虛表、在對象中存放虛表指針、構造時綁定、調用時通過指針間接跳轉。這不僅加深了對C++多態底層機制的理解,也為C語言項目引入面向對象設計提供了可行路徑。實際項目中,可進一步封裝宏簡化虛表定義與綁定代碼。