下面我将分步解释如何实现,从最基础的继承到更高级的多态。

(图片来源网络,侵删)
核心思想
C语言实现继承的核心思想是 “组合” 和 “函数指针”。
- 组合:我们用一个“子结构体”来包含一个“父结构体”作为它的第一个成员,这模拟了“is-a”(是一个)的关系,一个
Dog是一个Animal。 - 函数指针:在结构体中,我们使用函数指针来表示方法,子结构体可以“重写”父结构体中的方法指针,从而实现多态。
第1步:实现基础的继承
假设我们有一个基类 Animal 和一个派生类 Dog。
定义“基类” - Animal
在C中,我们用 struct 来模拟类,它包含数据成员和函数指针成员。
// animal.h
#ifndef ANIMAL_H
#define ANIMAL_H
// 前向声明,因为Dog结构体中会用到Animal
typedef struct Animal Animal;
typedef struct Dog Dog;
// Animal的虚函数表
// 这是一个关键结构,它存储了所有Animal的方法(函数指针)
typedef struct {
void (*say)(Animal*); // say方法的函数指针
} AnimalVTable;
// Animal结构体本身
struct Animal {
const AnimalVTable* vtable; // 指向虚函数表的指针
};
// Animal的构造函数
void Animal_ctor(Animal* this);
#endif // ANIMAL_H
定义“派生类” - Dog
Dog 结构体的第一个成员必须是 Animal,这确保了在内存布局上,Dog 对象可以被视为 Animal 对象(通过类型转换)。

(图片来源网络,侵删)
// dog.h
#ifndef DOG_H
#define DOG_H
#include "animal.h"
// Dog的虚函数表
// 它可以继承并扩展Animal的虚函数表
typedef struct {
void (*say)(Animal*); // 重写了Animal的say方法
} DogVTable;
// Dog结构体
struct Dog {
Animal base; // 第一个成员必须是基类,实现"is-a"关系
// Dog特有的数据成员
char breed[50];
};
// Dog的构造函数
void Dog_ctor(Dog* this, const char* breed);
#endif // DOG_H
实现方法
我们来实现这些方法,注意,Dog 的 say 方法会接收一个 Animal* 指针,但我们会把它转换成 Dog* 来访问 Dog 特有的数据。
// animal.c
#include "animal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Animal的say方法实现
static void Animal_say(Animal* this) {
printf("Animal makes a sound.\n");
}
// Animal的虚函数表实例
static const AnimalVTable animal_vtable = {
.say = Animal_say
};
// Animal的构造函数
void Animal_ctor(Animal* this) {
this->vtable = &animal_vtable;
}
// dog.c
#include "dog.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Dog的say方法实现
static void Dog_say(Animal* this) {
// 将Animal指针转换回Dog指针
Dog* dog = (Dog*)this;
printf("Dog of breed %s says: Woof!\n", dog->breed);
}
// Dog的虚函数表实例
// 它重写了Animal的say方法
static const DogVTable dog_vtable = {
.say = Dog_say
};
// Dog的构造函数
void Dog_ctor(Dog* this, const char* breed) {
// 1. 首先调用基类的构造函数
Animal_ctor(&this->base);
// 2. 设置自己的虚函数表(这里重写了say方法)
// 注意:在更复杂的场景下,我们可能会先复制父类的vtable,然后修改特定方法
// 为了简化,我们直接创建一个新的vtable
this->base.vtable = (const AnimalVTable*)&dog_vtable;
// 3. 初始化自己的特有数据
strncpy(this->breed, breed, sizeof(this->breed) - 1);
this->breed[sizeof(this->breed) - 1] = '\0';
}
使用
我们可以在 main 函数中使用它们了。
// main.c
#include "animal.h"
#include "dog.h"
#include <stdio.h>
void make_sound(Animal* a) {
a->vtable->say(a); // 通过虚函数表调用正确的方法
}
int main() {
// 创建一个Dog实例
Dog my_dog;
Dog_ctor(&my_dog, "Golden Retriever");
// 创建一个Animal实例
Animal my_animal;
Animal_ctor(&my_animal);
printf("--- Calling make_sound function ---\n");
make_sound(&my_animal); // 传入Animal指针,调用Animal::say
make_sound((Animal*)&my_dog); // 传入Dog指针(向上转型),调用Dog::say
printf("\n--- Direct access ---\n");
my_animal.vtable->say(&my_animal);
my_dog.base.vtable->say((Animal*)&my_dog); // 也可以直接通过base访问
return 0;
}
编译和运行:
gcc main.c animal.c dog.c -o inheritance_example ./inheritance_example
输出:

(图片来源网络,侵删)
--- Calling make_sound function ---
Animal makes a sound.
Dog of breed Golden Retriever says: Woof!
--- Direct access ---
Animal makes a sound.
Dog of breed Golden Retriever says: Woof!
从输出可以看到,make_sound 函数接收一个 Animal*,但根据传入对象的真实类型(Animal 还是 Dog),它调用了正确的 say 方法,这就是多态。
第2步:实现多重继承
多重继承在C中要复杂得多,因为需要处理内存布局问题,特别是当多个父类有相同偏移的成员时,核心思想是在子结构体中包含多个父结构体。
假设 Dog 还想继承 Pet 特性。
// pet.h
#ifndef PET_H
#define PET_H
#include <stdio.h>
typedef struct Pet Pet;
typedef struct PetVTable {
void (*play)(Pet*);
} PetVTable;
struct Pet {
const PetVTable* vtable;
};
void Pet_ctor(Pet* this);
void Pet_play(Pet* this);
#endif
// pet.c
#include "pet.h"
static void Pet_play(Pet* this) {
printf("Pet is playing.\n");
}
static const PetVTable pet_vtable = {
.play = Pet_play
};
void Pet_ctor(Pet* this) {
this->vtable = &pet_vtable;
}
现在修改 Dog 和 dog.h:
// dog.h (修改后)
#ifndef DOG_H
#define DOG_H
#include "animal.h"
#include "pet.h"
// Dog的虚函数表,需要同时包含Animal和Pet的方法
typedef struct {
void (*say)(Animal*);
void (*play)(Pet*); // 添加了Pet的方法
} DogVTable;
// Dog结构体,包含两个基类
struct Dog {
Animal base; // 第一个基类
Pet pet_base; // 第二个基类
char breed[50];
};
void Dog_ctor(Dog* this, const char* breed);
#endif
// dog.c (修改后)
#include "dog.h"
#include <stdio.h>
#include <string.h>
// Dog的say方法
static void Dog_say(Animal* this) {
Dog* dog = (Dog*)this;
printf("Dog of breed %s says: Woof!\n", dog->breed);
}
// Dog的play方法
static void Dog_play(Pet* this) {
Dog* dog = (Dog*)this;
printf("Dog of breed %s is playing fetch!\n", dog->breed);
}
// Dog的虚函数表
static const DogVTable dog_vtable = {
.say = Dog_say,
.play = Dog_play
};
void Dog_ctor(Dog* this, const char* breed) {
// 1. 初始化所有基类
Animal_ctor(&this->base);
Pet_ctor(&this->pet_base);
// 2. 设置vtable
// 注意:这里简化了,在真实编译器中,vtable会更复杂,需要处理多个基类的偏移。
this->base.vtable = (const AnimalVTable*)&dog_vtable;
this->pet_base.vtable = (const PetVTable*)&dog_vtable;
// 3. 初始化特有数据
strncpy(this->breed, breed, sizeof(this->breed) - 1);
this->breed[sizeof(this->breed) - 1] = '\0';
}
使用多重继承:
int main() {
Dog my_dog;
Dog_ctor(&my_dog, "Labrador");
printf("--- Calling methods through vtables ---\n");
my_dog.base.vtable->say((Animal*)&my_dog);
my_dog.pet_base.vtable->play((Pet*)&my_dog);
return 0;
}
总结与优缺点
优点
- 灵活性:可以在纯C环境中实现面向对象的设计模式,如继承和多态。
- 性能:这种实现方式通常非常高效,因为它只是结构体和函数指针的调用,没有C++中虚函数表查找的额外开销(现代C++的虚函数调用也很快)。
- 可移植性:不依赖任何特定的C++编译器,在任何标准C环境下都可以工作。
缺点
- 复杂性:代码变得非常繁琐和冗长,你需要手动维护虚函数表、构造函数链等。
- 类型安全:C的类型系统无法提供C++那样的保护,你必须手动进行类型转换(
(Dog*)this),容易出错。 - 可读性差:对于不熟悉这种模式的开发者来说,代码难以理解。
- 缺乏语言支持:没有
public,private,protected访问控制,也没有构造函数/析构函数的自动调用。
虽然C语言没有内置的继承机制,但通过 结构体组合 和 函数指针(虚函数表),我们可以手动模拟出继承和多态的行为,这种方法在C语言的大型项目(如Linux内核、GTK+ GUI库、游戏引擎等)中被广泛使用,以构建模块化、可扩展的代码。
对于新的项目,如果条件允许,直接使用C++会是更简单、更安全、更易维护的选择,但如果你必须使用C,那么理解并掌握这种手动实现继承的技巧是一项非常有价值的技能。
