1. 一个常年写C的人,到底被面向对象救了什么
很多教程把面向对象编程放进教材第5章,前面通常讲变量、流程控制、函数和结构体,后面才开始讲类和对象。这个位置其实很讲究,因为大部分人是被C语言里的switch和散落全局数据弄得头疼之后,才会真正想明白它解决什么问题。
我自己接触过的第一个“非改不可”的案例是传感器接入层。当时在一个设备项目里,要管理温度、湿度、气压三类传感器。它们不是单纯调一个函数就完事,而是各自有校准参数、有通信协议区分、要定期采样、异常时还要带状态上报。用C的写法,就是一个大枚举加一堆switch,结构体把所有传感器统一描述成宽表:
c复制typedef struct {
uint8_t type; // SENSOR_TEMP / SENSOR_HUMI / SENSOR_PRES
char name[16];
int32_t raw_value;
int32_t calibrated_value;
uint8_t error_code;
uint8_t calib_coeff[8];
uint32_t sample_interval_ms;
// 后面每加一种传感器,就往里塞字段
} sensor_t;
static sensor_t g_sensors[MAX_SENSOR_COUNT];
一开始这样写没什么,新增传感器类型时,只要在枚举里加一项、结构体里加几个字段、采样函数里加一个case、上报函数里再加一个case。问题是这种改动会出现在很多文件里:协议解析、数据校验、日志上报、界面显示都要判断type。等接完第六种传感器时,光是找全所有要改的case就得靠全局搜索。
而且还有更难受的:calib_coeff是给温度校准用的,气压传感器不用;而气压传感器自己的海拔补偿值又需要另外塞,结果结构体越来越大。上层的界面模块只要拿到这个结构体,就能随手改字段,整个系统的数据一致性全靠大家心照不宣。这也是我后来理解封装价值的起点:不是private有多高级,而是当一堆代码都能乱动你内部字段的时候,系统根本没有边界可言。
面向对象之所以会在教材这个位置出现,本质上不是换了一种语法,而是改变组织代码的单位。C语言习惯按函数组织,数据结构和操作它的函数是分开的,两者通过参数连接。而面向对象希望你把“一组数据”和“这些数据上合理的操作”绑在一起,并且让外部只能看到该看的入口。结构体里的字段还在,但外部不再能无条件访问它。
很多新手在学类的时候,把注意力放在构造方法、可见性修饰符这些零散语法上。这有点可惜,因为类只是封装、多态这套思想的载体。先看透问题本质,再去看语法,效率会高很多。之后的几个部分,我会结合C嵌入式场景和Java工程里常见的写法,把面向对象本身拆开讲清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 嵌入式C没有class,但同样能看出一条对象化的演进路径
搜索“c语言面向对象编程 嵌入式实战”的资料很多,刚开始看会觉得这是在搞奇技淫巧。但实际在驱动层工作久了就会承认:C语言虽然不支持class关键字,不代表不能用面向对象思维组织代码。嵌入式里没有class往往反而是优势,因为它逼你把对象思想落到最朴素的机制上——结构体加函数指针。
2.1 用结构体把状态和行为绑在一起
举个最简单的外设抽象:LED和继电器这类开关设备,控制逻辑几乎一样,都是初始化、打开、关闭、读取状态。用C写,通常会提供一组接口函数:
c复制void led_init(uint8_t pin);
void led_on(void);
void led_off(void);
void button_init(uint8_t pin);
uint8_t button_is_pressed(void);
当外设少的时候没问题,但一旦板子上有多个LED、多个按键,函数就只能靠参数区分,或者不断复制同一套代码。这时如果用一个结构体把“这个外设自己的状态”和“操作它的函数指针”绑在一起,立刻就能看出对象化
