面向对象 — 结构体与函数指针

← C语言知识地图 | ← 主页

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