使用函数指针和结构体来模拟“类”和“虚函数表”(vtable)。

(图片来源网络,侵删)
下面我将分步详细解释,并提供一个完整的、可运行的示例。
核心概念:C 语言如何模拟面向对象
在 C++ 中,多态通常是这样工作的:
- 基类:定义一个或多个虚函数。
- 派生类:继承基类,并重写这些虚函数。
- 虚函数表:编译器为每个有虚函数的类生成一个静态的函数指针表,存储了该类实际应该调用的函数地址。
- 虚指针:每个类的对象(实例)内部都有一个隐藏的指针(
vptr),指向其所属类的虚函数表。
在 C 语言中,我们需要自己实现这几点:
- 类:用
struct来表示。struct可以包含数据成员和函数指针成员。 - 虚函数表:用一组
函数指针来表示,通常是定义在struct内部的一组静态函数指针。 - 对象:
struct的一个实例。 - 继承:通过将基类的
struct作为派生类struct的第一个成员来实现,这确保了内存布局的兼容性,使得基类指针可以安全地指向派生类对象(类似 C++ 的upcast)。
实现步骤:一个“动物”叫的例子
我们将创建一个基类 Animal 和两个派生类 Dog 和 Cat,它们都有一个 make_sound 函数,但行为不同,我们想通过一个 Animal 类型的指针来调用正确的 make_sound 函数。

(图片来源网络,侵删)
步骤 1: 定义函数指针类型
为我们的基类定义一个函数指针类型,这会让代码更清晰。
// 定义一个函数指针类型,它指向一个接受 void* 指针并返回 void 的函数。 // void* 用于模拟 C++ 中的 this 指针,指向对象实例。 typedef void (*SoundFunc)(void*);
步骤 2: 定义基类(Animal)
Animal 结构体将包含:
- 一个虚函数表指针
vtable,它将指向一个包含所有虚函数(这里是make_sound)地址的结构体。 - 数据成员,
name。
// 基类: Animal
// 1. 定义基类的虚函数表结构
typedef struct {
SoundFunc make_sound; // 虚函数表中的第一个函数
} AnimalVTable;
// 2. 定义基类结构体
typedef struct {
AnimalVTable* vtable; // 指向虚函数表的指针
const char* name; // 数据成员
} Animal;
步骤 3: 实现基类的虚函数
这些函数的第一个参数是 void* this,它们会通过这个指针来访问对象的数据,并调用虚函数表中正确的函数。
// 基类的 make_sound 实现
// 注意:第一个参数是 void*,代表当前对象实例
void animal_make_sound(void* this) {
Animal* animal = (Animal*)this;
printf("%s (generic animal) makes a sound.\n", animal->name);
}
步骤 4: 定义派生类(Dog 和 Cat)
派生类将包含:

(图片来源网络,侵删)
- 基类
Animal作为其第一个成员(这是实现“继承”的关键)。 - 自己特有的数据成员(如果有的话)。
// 派生类: Dog
typedef struct {
Animal base; // 必须将基类作为第一个成员
int breed; // Dog 特有的属性
} Dog;
// 派生类: Cat
typedef struct {
Animal base; // 必须将基类作为第一个成员
bool is_indoor; // Cat 特有的属性
} Cat;
步骤 5: 实现派生类的虚函数
这些函数与基类函数签名相同,但实现不同。
// Dog 的 make_sound 实现
void dog_make_sound(void* this) {
Dog* dog = (Dog*)this;
printf("%s (Dog) says: Woof! Woof!\n", dog->base.name);
}
// Cat 的 make_sound 实现
void cat_make_sound(void* this) {
Cat* cat = (Cat*)this;
printf("%s (Cat) says: Meow!\n", cat->base.name);
}
步骤 6: 创建和初始化对象(最关键的一步)
这是手动实现 vtable 的地方,我们需要为每个派生类创建一个静态的 vtable,并在创建对象时,将对象的 vtable 指针指向正确的 vtable。
// 1. 为 Dog 创建一个静态的虚函数表
static AnimalVTable dog_vtable = {
.make_sound = dog_make_sound // 将 dog_make_sound 函数地址存入 vtable
};
// 2. 为 Cat 创建一个静态的虚函数表
static AnimalVTable cat_vtable = {
.make_sound = cat_make_sound // 将 cat_make_sound 函数地址存入 vtable
};
// 3. 创建 Dog 对象并初始化
void dog_ctor(Dog* this, const char* name, int breed) {
this->base.vtable = &dog_vtable; // 关键:将对象的 vtable 指针指向 Dog 的 vtable
this->base.name = name;
this->breed = breed;
}
// 4. 创建 Cat 对象并初始化
void cat_ctor(Cat* this, const char* name, bool is_indoor) {
this->base.vtable = &cat_vtable; // 关键:将对象的 vtable 指针指向 Cat 的 vtable
this->base.name = name;
this->is_indoor = is_indoor;
}
步骤 7: 使用多态
我们可以通过一个 Animal* 指针来操作不同类型的对象,而调用正确的函数。
// 一个通用的函数,它接受一个 Animal 指针
// 这个函数不需要知道具体是 Dog 还是 Cat
void let_animal_speak(Animal* animal) {
// 调用虚函数
// animal->vtable->make_sound(animal);
// 这行代码就是多态的核心!
// 1. animal->vtable 获取到对象对应的 vtable。
// 2. .make_sound 从 vtable 中获取到正确的函数地址。
// 3. (animal) 作为 this 指针传递给该函数。
animal->vtable->make_sound(animal);
}
完整代码示例
将以上所有部分组合起来,形成一个完整的 C 程序。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// =================================================================
// 1. 定义函数指针类型
// =================================================================
typedef void (*SoundFunc)(void*);
// =================================================================
// 2. 定义基类 (Animal)
// =================================================================
// 基类的虚函数表结构
typedef struct {
SoundFunc make_sound;
} AnimalVTable;
// 基类结构体
typedef struct {
AnimalVTable* vtable;
const char* name;
} Animal;
// 基类的虚函数实现
void animal_make_sound(void* this) {
Animal* animal = (Animal*)this;
printf("%s (generic animal) makes a sound.\n", animal->name);
}
// =================================================================
// 3. 定义派生类 (Dog, Cat)
// =================================================================
typedef struct {
Animal base; // 基类作为第一个成员
int breed;
} Dog;
typedef struct {
Animal base; // 基类作为第一个成员
bool is_indoor;
} Cat;
// 派生类的虚函数实现
void dog_make_sound(void* this) {
Dog* dog = (Dog*)this;
printf("%s (Dog) says: Woof! Woof! (Breed: %d)\n", dog->base.name, dog->breed);
}
void cat_make_sound(void* this) {
Cat* cat = (Cat*)this;
printf("%s (Cat) says: Meow! (Indoor: %s)\n", cat->base.name, cat->is_indoor ? "Yes" : "No");
}
// =================================================================
// 4. 创建和初始化对象 (构造函数)
// =================================================================
// 为 Dog 创建静态的 vtable
static AnimalVTable dog_vtable = {
.make_sound = dog_make_sound
};
// 为 Cat 创建静态的 vtable
static AnimalVTable cat_vtable = {
.make_sound = cat_make_sound
};
// Dog 的构造函数
void dog_ctor(Dog* this, const char* name, int breed) {
this->base.vtable = &dog_vtable;
this->base.name = strdup(name); // 使用 strdup 复制字符串,避免外部修改
this->breed = breed;
}
// Cat 的构造函数
void cat_ctor(Cat* this, const char* name, bool is_indoor) {
this->base.vtable = &cat_vtable;
this->base.name = strdup(name);
this->is_indoor = is_indoor;
}
// 析构函数 (为了内存安全)
void animal_dtor(Animal* animal) {
if (animal && animal->name) {
free((void*)animal->name);
animal->name = NULL;
}
}
// =================================================================
// 5. 多态接口
// =================================================================
void let_animal_speak(Animal* animal) {
if (animal && animal->vtable) {
animal->vtable->make_sound(animal);
} else {
printf("Error: Invalid animal object or vtable.\n");
}
}
// =================================================================
// 6. 主函数 - 演示
// =================================================================
int main() {
Dog my_dog;
Cat my_cat;
// 创建对象
dog_ctor(&my_dog, "Buddy", 1); // 1 for Golden Retriever
cat_ctor(&my_cat, "Whiskers", true);
printf("--- Polymorphism in Action ---\n");
// 使用基类指针数组来存储不同类型的对象
Animal* animals[2];
animals[0] = (Animal*)&my_dog;
animals[1] = (Animal*)&my_cat;
// 循环调用,体现多态
for (int i = 0; i < 2; i++) {
let_animal_speak(animals[i]);
}
printf("\n--- Direct Call (Non-Polymorphic) ---\n");
// 也可以直接调用,但这不是多态
dog_make_sound(&my_dog);
cat_make_sound(&my_cat);
// 清理资源
animal_dtor((Animal*)&my_dog);
animal_dtor((Animal*)&my_cat);
return 0;
}
编译和运行
将上述代码保存为 polymorphism.c,然后使用 GCC 编译:
gcc polymorphism.c -o polymorphism
运行程序:
./polymorphism
预期输出:
--- Polymorphism in Action ---
Buddy (Dog) says: Woof! Woof! (Breed: 1)
Whiskers (Cat) says: Meow! (Indoor: Yes)
--- Direct Call (Non-Polymorphic) ---
Buddy (Dog) says: Woof! Woof! (Breed: 1)
Whiskers (Cat) says: Meow! (Indoor: Yes)
总结与优缺点
优点:
- 实现了多态:可以在 C 语言中获得类似 C++ 的多态行为,这对于编写可扩展的库和框架非常有用。
- 性能高效:函数指针的调用开销在现代 CPU 上非常小,几乎和直接调用一样快,这比使用
switch-case或字符串比较来分发函数要高效得多。 - 灵活性:提供了强大的抽象能力,可以隐藏实现细节,让代码更模块化。
缺点:
- 代码冗长且繁琐:需要手动定义 vtable、结构体、构造/析构函数,代码量比 C++ 多很多,容易出错。
- 类型安全降低:
void*指针的使用削弱了 C 语言的类型检查,如果将错误的类型void*传递给函数,会导致未定义行为。 - 没有语言级支持:编译器不会自动检查 vtable 是否正确初始化,所有错误都需要在运行时由程序员自己处理。
- 维护困难:对于不熟悉这种模式的开发者来说,代码的意图可能不清晰,增加了维护成本。
尽管如此,在 C 语言项目中,尤其是在嵌入式系统、高性能库(如 OpenGL, SDL)等领域,这种手动实现多态的技术仍然非常普遍和重要。
