C语言中使用“结构体 + 函数指针”来模拟面向对象编程(OOP

C语言中使用"结构体 + 函数指针"来模拟面向对象编程(OOP)的"对象 + 方法"

是C语言中最经典、最常见的"伪OOP"写法之一。

这种写法在很多嵌入式系统、游戏引擎、图形库、驱动程序、GUI框架中都被广泛使用(GTK、libuv、SDL、Linux内核的部分模块等都大量使用类似手法)。

核心思想

把"数据"和"能作用在这份数据上的行为(函数)"打包放在一起。

c 复制代码
typedef struct {
    // 数据成员
    int x;
    int y;
    
    // 方法(函数指针)
    void (*move)(void* self, int dx, int dy);   // 第一个参数通常放自己
    void (*print)(const void* self);
    double (*distance_to_origin)(const void* self);
} Point;

最常见的几种写法对比(由简到复杂)

写法编号 风格 self 参数写法 推荐场景 易读性 维护性
1 显式传 self void (*func)(void* self, ...) 初学者、教学 ★★★☆☆ ★★★☆☆
2 用宏隐藏 self #define this self 个人项目、追求简洁 ★★★★☆ ★★☆☆☆
3 强类型 self(推荐) void (*func)(This* self, ...) 中大型项目、库设计 ★★★★★ ★★★★★
4 带继承(虚函数表) vtable + 结构体开头 需要多态的复杂系统 ★★★☆☆ ★★★★☆

下面我们用最推荐的第3种写法(强类型 self)来完整举例:

推荐写法示例(2024-2025年主流做法)

c 复制代码
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

// 前向声明
typedef struct Point Point;

// ==================== 1. 定义"类"(结构体) ====================
struct Point {
    // 数据部分
    float x;
    float y;

    // 方法表(函数指针)
    void    (*move)(Point* self, float dx, float dy);
    void    (*print)(const Point* self);
    float   (*distance_to)(const Point* self, const Point* other);
    void    (*destroy)(Point* self);   // 类似析构函数
};


// ==================== 2. 方法实现 ====================

static void point_move(Point* self, float dx, float dy) {
    self->x += dx;
    self->y += dy;
}

static void point_print(const Point* self) {
    printf("Point(%.2f, %.2f)\n", self->x, self->y);
}

static float point_distance_to(const Point* self, const Point* other) {
    float dx = self->x - other->x;
    float dy = self->y - other->y;
    return sqrtf(dx*dx + dy*dy);
}

static void point_destroy(Point* self) {
    // 如果有动态分配的成员,这里释放
    // 这里只是示例,实际可以 free(self) 如果是堆上创建的
    printf("Point at %p destroyed\n", (void*)self);
    // free(self);   // 如果是用 malloc 创建的才需要
}


// ==================== 3. 构造函数(工厂函数) ====================

Point* point_new(float x, float y) {
    Point* p = malloc(sizeof(Point));
    if (!p) return NULL;

    // 初始化数据
    p->x = x;
    p->y = y;

    // 绑定方法(最关键一步)
    p->move        = point_move;
    p->print       = point_print;
    p->distance_to = point_distance_to;
    p->destroy     = point_destroy;

    return p;
}


// ==================== 4. 使用方式 ====================

int main() {
    Point* p1 = point_new(3, 4);
    Point* p2 = point_new(0, 0);

    if (!p1 || !p2) {
        printf("内存分配失败\n");
        return 1;
    }

    p1->print(p1);                // Point(3.00, 4.00)
    p1->move(p1, 1, -2);
    p1->print(p1);                // Point(4.00, 2.00)

    printf("距离原点: %.2f\n", p1->distance_to(p1, p2));

    p1->destroy(p1);
    p2->destroy(p2);

    free(p1);
    free(p2);

    return 0;
}

更简洁但牺牲类型安全的写法(风格1)

很多人教学时会这样写(初学者常见):

c 复制代码
typedef struct {
    int x, y;
    void (*move)(void* self, int dx, int dy);
    void (*show)(void* self);
} Shape;

void shape_move(void* self, int dx, int dy) {
    Shape* s = (Shape*)self;
    s->x += dx;
    s->y += dy;
}

带"继承"与多态的进阶写法(简版)

c 复制代码
typedef struct {
    // 虚函数表必须放在最前面
    struct {
        void (*draw)(void* self);
        void (*move)(void* self, int dx, int dy);
    } vtable;

    int x, y;
    // ... 公共数据
} Shape;

typedef struct {
    Shape base;
    int radius;
} Circle;

void circle_draw(void* self) {
    Circle* c = self;
    printf("Circle at (%d,%d) r=%d\n", c->base.x, c->base.y, c->radius);
}

还有(简单版 / 推荐强类型版 / 带继承的多态版 / 其他变种)

相关推荐
炸膛坦客7 小时前
单片机/C/C++八股:(二十三)#define 和 typedef 的区别
c语言·c++·单片机
捷瑞电子工坊9 小时前
C语言取地址符 `&` 极简教程
c语言·c语言基础·取地址符·指针入门
乱七八糟的屋子10 小时前
LibYAML超全教程-C语言YAML解析与生成
c语言·开发语言·配置文件·yaml解析·libyaml·开源库实战
小肝一下10 小时前
2. 顺序表和链表
c语言·数据结构·c++·链表·dijkstra·依蕾娜·香香的依蕾娜
j7~10 小时前
【数据结构初阶】带头双向循环链表 (DSList) --增删查改完整实现详解
c语言·数据结构·链表·带头双向循环链表
无相求码1 天前
【C语言】字符串的本质是什么,为什么 strlen 会读到野内存
c语言·开发语言
LuminousCPP1 天前
C 语言集中实践全记录:顺序表 + 单链表原理实现与通讯录项目实战【附可运行源码】
c语言·开发语言·数据结构·经验分享·笔记
SuperByteMaster1 天前
__set_MSP 和__set_MSPLIM 接口区别
c语言
CS创新实验室1 天前
NumPy数组的C风格和Fortran风格
c语言·开发语言·numpy
LuminousCPP1 天前
单链表专题(应用篇):三道经典题吃透删除、反转与快慢指针
c语言·数据结构·笔记