一、把"数据"和"能作用在这份数据上的行为(函数)"打包放在一起
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);
}
希望这些例子能帮忙清晰地理解"结构体+函数指针"模拟OOP的各种写法梯度。
可以考虑最想深入哪一种风格?
(简单版 / 推荐强类型版 / 带继承的多态版 / 其他变种)
八、self风格:#define this self,
#define this self 是一个宏定义 ,作用是把代码中所有的 this 替换成 self。
基本语法
c
#define 宏名 替换内容
预处理器在编译前会把源码中所有出现 宏名 的地方,原样替换成 替换内容。
#define this self 的具体含义
c
#define this self
这行代码让你可以用 this 这个关键字风格的写法来代替 self。
常见于用 C 模拟面向对象编程时:
c
#define this self
typedef struct {
int x;
int y;
} Point;
void Point_move(Point *self, int dx, int dy) {
this->x += dx; // 预处理后变成 self->x += dx
this->y += dy; // 预处理后变成 self->y += dy
}
对比没有这个宏的写法:
c
void Point_move(Point *self, int dx, int dy) {
self->x += dx;
self->y += dy;
}
两者编译结果完全一样,只是写法风格不同。
常见使用场景
| 场景 | 说明 |
|---|---|
模拟 C++ 的 this |
让 C 代码更像面向对象风格 |
| 跨语言风格统一 | 团队习惯用 this,但底层参数命名为 self |
| 嵌入式 / 框架代码 | 一些框架(如 GObject)用 self,宏做兼容层 |
注意事项
- 这是纯文本替换 ,没有类型检查,预处理阶段完成,编译器看不到
this - C++ 中
this是真正的关键字 ,不能这样#define(会报错) - 滥用可能降低代码可读性,建议团队内统一规范
九、补充信息
GTK 是 GIMP Toolkit (GIMP 工具包)的简称,是一个免费开源的跨平台图形用户界面(GUI)工具包。它主要用于创建桌面应用程序的图形界面,从简单的工具软件到完整的应用套件都可以使用。
- 核心特点:提供完整的 UI 组件(窗口、按钮、菜单、工具栏等),支持主题定制和原生外观;基于 GLib 构建,采用面向对象的 C API;跨平台(Linux、Windows、macOS);有 Python、JavaScript、C++、Rust 等多种语言绑定。
- 当前版本(2026 年 4 月):稳定版 GTK 4.22.2(GTK 3 已进入维护阶段)。
- 许可:GNU Lesser General Public License (LGPL)。
- 主要应用:GNOME 桌面环境的核心工具包,广泛用于 Linux 桌面应用(如 GIMP、Inkscape、Evolution、Transmission、HandBrake 等)。它特别适合需要美观、稳定且与 Linux 原生集成良好的 GUI 程序。
libuv 是一个多平台异步 I/O 支持库,专注于提供高效的事件循环和非阻塞 I/O 操作。它是 Node.js 的底层核心引擎之一,但也可独立用于其他项目。
- 核心特点:基于事件循环(event loop)的异步编程模型;支持异步 TCP/UDP 网络、文件系统 I/O、定时器、子进程、DNS 等;使用平台原生机制(如 Linux epoll、macOS kqueue、Windows IOCP、Linux io_uring)实现高性能;纯 C 编写,轻量级且跨平台。
- 与 Node.js 的关系:Node.js 的异步能力主要由 libuv 提供(V8 引擎负责 JavaScript 执行,libuv 负责底层 I/O)。
- 许可:MIT 许可。
- 主要应用:服务器端网络应用、实时系统、游戏服务器、任何需要高并发异步 I/O 的 C/C++ 项目。常被用于构建类似 Node.js 的异步框架或嵌入式高性能服务。
SDL (Simple DirectMedia Layer ,简单直接媒体层)是一个跨平台的低级多媒体开发库,专门为游戏、模拟器和多媒体软件提供对硬件的直接访问。
- 核心特点:提供音频、键盘、鼠标、摇杆、手柄等输入设备的低级访问;支持 2D/3D 图形渲染(OpenGL、Direct3D、Vulkan);跨平台(Windows、macOS、Linux、iOS、Android);用 C 编写,可直接与 C++ 使用,也有 C#、Python 等绑定。
- 当前版本(2026 年 1 月):SDL 3.4(新增 GPU 与 2D 渲染互操作、本地 PNG 支持、更好的 Emscripten/Web 支持、Steam Controller 等新特性)。
- 许可:zlib 许可(非常宽松)。
- 主要应用:游戏开发(Valve 游戏、Humble Bundle 许多独立游戏)、视频播放器、模拟器(如 RetroArch)、VR/AR 工具、任何需要高效硬件访问的跨平台多媒体程序。
简单对比(便于理解三者的定位):
- GTK:高级 GUI 框架 → 做桌面应用界面。
- libuv:底层异步 I/O 引擎 → 做高性能网络/服务器后端。
- SDL:低级多媒体/游戏库 → 做游戏、音视频、硬件直连的程序。
三者都是开源、跨平台且广泛使用的 C 语言库,常被开发者组合使用(如用 SDL 做游戏渲染 + GTK 做设置界面,或在 Node.js 项目里直接调用 libuv)。如果你需要某个库的具体安装、示例代码或教程,可以告诉我,我再详细展开!