news 2026/5/6 0:20:22

C语言从句柄到对象

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言从句柄到对象

C语言从句柄到对象 (一) —— 全局变量的噩梦与“多实例”的救赎

代码里的句柄(Handle) 到底是个什么东西?为什么大厂的代码库(SDK)里到处都是句柄?” 其实,“句柄” (Handle) 不仅仅是一个指针,它是 C 语言通向模块化和面向对象架构的第一把钥匙。 今天,我们不谈枯燥的语法,只谈一个最痛的实际问题:当产品需求从“控制 1 个电机”变成“控制 10 个电机”时,你的代码需要推倒重写吗?

一、 这种“菜鸟代码”,你一定写过

假设我们接到了一个任务:写一个电机驱动,控制转速和启停。 对于大多数初学者,或者在为了赶进度的场景下,代码通常是这样写出来的:

1.1 典型的“隐式单例”写法

motor.c (驱动文件) 我们把电机的状态定义为文件内部的全局变量(static),然后用函数直接操作它们。

// [数据区] 全局变量:隐式地只支持一个设备 static uint8_t g_current_speed = 0; static uint8_t g_is_running = 0; // [代码区] 操作函数 void Motor_Init(void) { HAL_GPIO_Init(GPIOA, GPIO_PIN_0, ...); // 硬件引脚写死在代码里 g_current_speed = 0; } void Motor_SetSpeed(uint8_t speed) { g_current_speed = speed; // ... 操作硬件寄存器 ... }

main.c (应用文件)

int main(void) { Motor_Init(); Motor_SetSpeed(50); // 设置速度 }

1.2 这种写法有问题吗?

坦白说,如果你的板子上这辈子只会有 1 个电机,这种写法完全没问题。 这种写法叫 “单例模式” (Singleton)。它的优点是简单、直观、调用方便,不需要传参。在嵌入式系统中,对于 全局唯一的资源(比如系统滴答定时器 SysTick、唯一的看门狗 WDT、调试日志 Log),这种写法甚至是推荐的。

但是,噩梦往往源于那句:“老板说,新产品要加功能。”

二、 复制粘贴的灾难

老板突然走过来说:“这一版硬件升级了,我们需要控制 4 个电机。”

这时候,如果你坚持上面的写法,你只能祭出“复制粘贴大法”:

// 这种写法被称为“维护者的噩梦” // --- 电机 1 --- static uint8_t g_speed_1 = 0; void Motor1_SetSpeed(uint8_t speed) { ... } // --- 电机 2 --- static uint8_t g_speed_2 = 0; void Motor2_SetSpeed(uint8_t speed) { ... } // --- 电机 3 ... (代码量膨胀 4 倍)

后果是显而易见的: 代码冗余:同样的逻辑重复了 4 遍,Flash 空间被浪费。 维护困难:如果发现电机启动逻辑有个 Bug,你得去改 4 个地方。只要漏改一个,Bug 就依然存在。 无法扩展:如果明天老板要 10 个电机呢? 这就是 “面向过程” 编程在面对 “多实例” 需求时的死穴。

三、 句柄的诞生:将“数据”与“逻辑”剥离

为了解决这个问题,我们需要转换思维。 仔细观察上面的代码,你会发现:

变的(数据):每个电机的引脚、当前速度、运行状态,是各自独立的。

不变的(逻辑):设置 PWM、计算转速的算法(代码指令),对所有电机都是一样的。 我们为什么不能 只写一份代码,让它去操作 多份数据 呢?

3.1 第一步:定义对象 (The Context)

我们把原本散落在全局变量里的东西,全部“打包”进一个结构体。这个结构体,在架构术语里叫 Context(上下文),或者是 Instance(实例)。

// motor_driver.h typedef struct { // 静态属性 (配置) GPIO_TypeDef *gpio_port; uint16_t gpio_pin; // 动态状态 (变量) uint8_t current_speed; uint8_t is_running; } Motor_t;

3.2 第二步:引入句柄 (The Handle)

现在,我们的函数不再操作死板的全局变量,而是操作作为参数传进来的指针。 这个指针,就是我们俗称的 “句柄”。

// motor_driver.h // 定义句柄类型:也就是指向 Motor_t 的指针 typedef Motor_t* Motor_Handle; // 接口函数的第一个参数,永远是句柄! // 翻译过来就是:“你要让我操作哪一个电机?” void Motor_Init(Motor_Handle h, GPIO_TypeDef *port, uint16_t pin); void Motor_SetSpeed(Motor_Handle h, uint8_t speed);

3.3 第三步:实现逻辑 (Implementation)

在 .c 文件里,我们通过句柄(指针)来访问具体的数据。

// motor_driver.c void Motor_Init(Motor_Handle h, GPIO_TypeDef *port, uint16_t pin) { // 【核心】把配置信息存入句柄指向的内存区域 h->gpio_port = port; h->gpio_pin = pin; h->current_speed = 0; // 使用句柄里的信息初始化硬件 HAL_GPIO_Init(h->gpio_port, h->gpio_pin, ...); } void Motor_SetSpeed(Motor_Handle h, uint8_t speed) { // 1. 修改句柄指向的状态 h->current_speed = speed; // 2. 操作句柄指向的硬件 Set_PWM_Hardware(h->gpio_port, h->gpio_pin, speed); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/5 3:52:15

Keil5开发环境搭建:手把手教程(从零配置)

Keil5开发环境搭建:从零开始的实战指南你有没有过这样的经历?买了一块崭新的STM32开发板,兴致勃勃地打开电脑准备“点灯”,结果卡在第一步——Keil打不开、编译报错一堆、下载程序失败……最后只能对着闪烁的ST-Link指示灯发呆。别…

作者头像 李华
网站建设 2026/5/5 14:47:00

当储能系统遇上代码:聊聊那些藏在电池里的“平衡术

储能逆变器,储能系统,soc均衡控制,soc均衡,蓄电池充放电控制,电动汽车充电桩控制,充电桩模拟 根据您提供的一段话,我重新表述如下:"储能逆变器是一种用于储能系统的设备&#x…

作者头像 李华
网站建设 2026/5/1 17:25:07

零基础学习Proteus元器件库大全与原理图绘制流程

从零开始掌握Proteus:元器件库怎么用?原理图如何画?你是不是也遇到过这种情况——刚打开Proteus,想做个简单的LED闪烁电路,结果在“Pick Device”窗口里翻来覆去找不到AT89C51?或者好不容易把元件放好了&am…

作者头像 李华
网站建设 2026/4/22 21:26:13

RISC架构下实时操作系统移植:项目应用

RISC架构下实时操作系统移植:从原理到实战的深度实践在工业自动化、智能驾驶和边缘计算飞速发展的今天,嵌入式系统早已不再是“跑个循环”的简单设备。越来越多的应用要求毫秒级响应、任务间精确协同、资源高效调度——这些正是实时操作系统(…

作者头像 李华
网站建设 2026/5/3 8:12:35

基于Keil的Cortex-M中断向量表设置完整指南

深入理解Keil下的Cortex-M中断向量表:从启动到IAP实战你有没有遇到过这样的情况?系统上电后,MCU卡在HardFault里出不来;或者做了IAP升级,新固件跑起来了,但一来中断就崩。这类问题背后,往往藏着…

作者头像 李华
网站建设 2026/5/3 10:17:30

CSS id 和 class

CSS id 和 class 的区别id 和 class 是 CSS 中用于选择元素的两种常用方式,但它们在用法和特性上有显著差异。id 具有唯一性,一个页面中相同的 id 只能出现一次,通常用于标识特定元素。class 可以重复使用,适用于多个具有相同样式…

作者头像 李华