类的组织:前缀、init/deinit 与生命周期
04 类的组织:前缀、init/deinit 与生命周期
C 语言 OOP 系列导航
本系列面向会 C 和单片机基础、刚进入企业项目的同学,从
struct封装一路讲到ops、多态和类 Linux 驱动框架。
企业项目里,一个模块通常不是只有一个函数。LED 模块可能有初始化、开灯、关灯、设置亮度、释放资源;电机模块也有初始化、启动、停止、设置速度。
如果大家都写短名字:
1 | int init(void); |
另一个模块也写:
1 | int init(void); |
链接阶段很容易冲突,阅读代码时也不知道函数属于哪个模块。
一句话先懂:C 语言没有 class,所以要用“模块前缀 + init/deinit + initialized 检查”把一个类的边界显式写出来。
先看问题:函数能调用,不代表对象能用
很多嵌入式 bug 都来自生命周期混乱:
| 问题 | 现场表现 |
|---|---|
| GPIO 没初始化就写电平 | 设备不响应或状态异常 |
| UART 没开时钟就发送 | 数据发不出去 |
| PWM 没配置就设置占空比 | 波形不对 |
| 退出时没释放资源 | 下次初始化状态不干净 |
所以模块不只要有“动作函数”,还要有明确的“从不可用到可用,再从可用到退出”的过程。
前缀就是 C 语言里的类名
LED 模块的公共接口统一以 led_ 开头:
1 | int led_init(Led_t *me, uint8_t pin); |
电机模块统一用 motor_:
1 | int motor_init(Motor_t *me, uint8_t pin); |
前缀的作用不是装饰,而是建立归属:
| C 函数前缀 | 等价理解 |
|---|---|
led_ |
LED 类 |
motor_ |
Motor 类 |
button_ |
Button 类 |
uart_ |
UART 类 |
看到 led_on(),读者立刻知道它属于 LED 模块;看到 uart_init(),就知道它是 UART 模块的初始化。
init 负责把对象变成可用状态
对象不能声明后直接用:
1 | Led_t led; |
一个合格的 led_init() 至少做三件事:
| 步骤 | 目的 |
|---|---|
| 参数验证 | 防止空指针、非法引脚 |
| 硬件配置 | 把 GPIO 设置成可用状态 |
| 默认状态 | 初始化成员变量 |
示例:
1 |
|
led_init() 就是 C 语言里的手写构造函数。它负责把一块普通内存变成“可以使用的 LED 对象”。
deinit 负责让对象安全退出
有初始化,就应该有反初始化。led_deinit() 至少要关闭硬件、清理状态:
1 | int led_deinit(Led_t *me) |
C++ 析构函数能在对象离开作用域时自动执行。C 语言没有自动析构,所以调用流程要写清楚:
1 | Led_t led; |
initialized 字段拦住错误调用
公共接口不能假设调用方一定按顺序使用。led_on() 应该主动检查对象是否已经初始化:
1 | int led_on(Led_t *me) |
这不是多余代码,而是模块的自我保护:
| 防御点 | 防住的问题 |
|---|---|
me == NULL |
空指针访问 |
pin 范围检查 |
操作非法引脚 |
initialized |
未初始化对象被使用 |
| 返回错误码 | 调用方能处理失败 |
一个 .h + .c 就是一类对象
把 C 模块映射到面向对象概念:
| C 模块元素 | OOP 概念 |
|---|---|
led.h |
类的公共声明 |
led.c |
类的实现 |
Led_t |
对象数据 |
led_init() |
构造函数 |
led_deinit() |
析构函数 |
led_on() |
成员方法 |
static helper |
private 方法 |
led_ 前缀 |
类名 / 命名空间 |
新人常见误区
| 误区 | 更稳的理解 |
|---|---|
反正单片机不释放资源,可以不写 deinit |
低功耗、外设复用、异常恢复时都可能需要 |
初始化失败就返回 void |
企业项目里应返回错误码,让上层能处理 |
initialized 浪费一个字段 |
它换来的是更可控的失败方式 |
| 函数名前缀太啰嗦 | C 没有命名空间,前缀就是最低成本的边界 |
C/C++ 对照
| C 写法 | C++ 写法 |
|---|---|
Led_t led; led_init(&led, pin); |
Led led(pin); |
led_deinit(&led); |
~Led() |
led_on(&led); |
led.on(); |
led_ 前缀 |
Led:: 作用域 |
initialized 检查 |
构造后对象有效 |
嵌入式项目意义
驱动模块的可靠性,很大一部分来自生命周期清楚:
| 收益 | 说明 |
|---|---|
| 初始化顺序清楚 | 上层知道先调什么 |
| 资源释放明确 | 退出时硬件状态可控 |
| 错误能被发现 | 未初始化调用返回错误 |
| 接口风格统一 | LED、电机、按键都能照这个模板写 |
总结
C 语言没有
class关键字,但模块前缀 + struct + init/deinit + initialized已经能组织出一个可靠的类。










