面向对象 — 结构体与函数指针
C 不是面向对象语言,但通过结构体 + 函数指针可以模拟封装与多态,嵌入式 RTOS、驱动框架里大量使用这种模式。
1. 封装:结构体捆绑数据 + 方法
// .h
typedef struct {
int x;
int y;
void (*move)(struct Point*, int dx, int dy);
} Point;
void Point_init(Point* self, int x, int y);
// .c
static void Point_move(Point* self, int dx, int dy) {
self->x += dx;
self->y += dy;
}
void Point_init(Point* self, int x, int y) {
self->x = x;
self->y = y;
self->move = Point_move; // 绑定"方法"
}
// 使用
Point p;
Point_init(&p, 0, 0);
p.move(&p, 10, 20); // p.x = 10, p.y = 20
self指针就是 C++ 里隐式的this。C 里必须显式传,没有语法糖。
2. 继承:结构体嵌套 + 强制转换
// 基类
typedef struct {
int x, y;
void (*draw)(void* self);
} Shape;
// 派生类 — 第一个成员是基类
typedef struct {
Shape base; // 继承 Shape
int radius;
} Circle;
// 利用指针可互换:Circle* 可以安全转成 Shape*
Circle c;
Shape* s = (Shape*)&c; // 基类指针指向派生类
s->draw(s); // 调用派生类自己的 draw关键规则:派生结构体的第一个成员必须是基类结构体,这样
(Base*)&derived才安全。
3. 多态:函数指针实现虚函数
typedef struct Animal {
void (*speak)(struct Animal* self);
} Animal;
// Cat
typedef struct { Animal base; } Cat;
static void Cat_speak(Animal* self) { printf("喵\n"); }
void Cat_init(Cat* self) {
self->base.speak = Cat_speak;
}
// Dog
typedef struct { Animal base; } Dog;
static void Dog_speak(Animal* self) { printf("汪\n"); }
void Dog_init(Dog* self) {
self->base.speak = Dog_speak;
}
// 统一调用
void make_sound(Animal* a) {
a->speak(a); // 多态:实际调哪个看运行时绑定
}
Cat cat; Cat_init(&cat);
Dog dog; Dog_init(&dog);
make_sound((Animal*)&cat); // 喵
make_sound((Animal*)&dog); // 汪4. 虚表(vtable):批量函数指针
方法多了每个对象都挂一堆指针浪费内存,在嵌入式里常用的做法是共享一个虚表:
typedef struct AnimalVtable {
void (*speak)(void*);
void (*move)(void*, int, int);
} AnimalVtable;
typedef struct {
const AnimalVtable* vtable;
char name[16];
} Animal;
// Cat 虚表 — 全局唯一,所有 Cat 共享
static void Cat_speak(void* self) { printf("喵\n"); }
static void Cat_move(void* self, int x, int y) { /* ... */ }
static const AnimalVtable cat_vtable = {
.speak = Cat_speak,
.move = Cat_move,
};
void Cat_init(Animal* self) {
self->vtable = &cat_vtable; // 只需要一个指针
}
// 调用
animal->vtable->speak(animal);这就是 C++ 虚函数表的底层实现原理。Linux 内核的 VFS、FreeRTOS 的任务控制块都用这种模式。
小结
| 特性 | C 实现方式 | C++ 对应 |
|---|---|---|
| 封装 | struct + 函数指针 | class |
| 继承 | 结构体嵌套,首成员为基类 | : public Base |
| 多态 | 函数指针/虚表 | virtual |
| this | 显式传 self 指针 | 隐式 this |