10 多态与转型:一个指针管所有LED
C 语言 OOP 系列导航
- C语言你真的会封装吗?
- 三个LED写了三份代码?
- 同事改一行,LED全乱了
- 手搓class:前缀、init/deinit和生命周期
- 你的全局变量,该死了
- 工业库也是同一套封装套路
- struct嵌套消灭复制粘贴
- 写死的函数怎么换
- 把一组函数指针装进对象
- 一个指针管所有LED ⇐ 当前位置
- 虚函数不实现会怎样
- 换硬件不改应用
- 从自动注册到Linux内核OOP全貌
现在我们已经有了两个零件:公共字段 base 和行为表 ops。多态要解决的问题:上层不想知道这颗 LED 到底是 GPIO、PWM 还是 RGB,只想写一句 led_on(led)。
先看问题:if/else 把具体类型泄漏到上层
没有多态时,上层往往这样写:
1 2 3 4 5 6 7
| if (type == LED_TYPE_GPIO) { gpio_led_on(&gpio_led); } else if (type == LED_TYPE_PWM) { pwm_led_on(&pwm_led); } else if (type == LED_TYPE_RGB) { rgb_led_on(&rgb_led); }
|
这段代码的问题不是 if/else 本身,而是上层知道了所有具体类型 — 抽象被打穿,新增类型要改分支,调用入口不统一,具体硬件泄漏到业务层。目标是把这些分支藏到对象自己的 ops 里。
向上转型:从具体对象拿到 base 指针
具体类型把 LedBase_t 放在结构体里:
1 2 3 4
| typedef struct { LedBase_t base; uint8_t pwm_channel; } PwmLed_t;
|
上层只拿基础部分:
1 2
| PwmLed_t pwm_led; LedBase_t *led = &pwm_led.base;
|
这就是 C 语言里的向上转型 — 具体对象 → 基础指针,上层只看抽象。
dispatch:统一入口完成行为分发
不要让外部到处写 led->ops->on(led)。提供统一入口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int led_on(LedBase_t *me) { if (me == NULL) { return -1; }
if (me->ops == NULL) { return -2; }
if (me->ops->on == NULL) { return -3; }
return me->ops->on(me); }
|
调用链路展开后是:
1 2 3
| led_on(led) -> led->ops->on(led) -> gpio_led_on / pwm_led_on / rgb_led_on
|
上层调用同一个 led_on(),具体实现由 ops 决定。C++ 里这叫虚函数调用,C 里我们手动写出 dispatch。
管理器:一个数组管所有LED
有了基础指针,管理器不需要关心具体类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #define LED_MANAGER_MAX_COUNT 8
typedef struct { LedBase_t *items[LED_MANAGER_MAX_COUNT]; uint8_t count; } LedManager_t;
int led_manager_add(LedManager_t *me, LedBase_t *led) { if (me == NULL || led == NULL) { return -1; }
if (me->count >= LED_MANAGER_MAX_COUNT) { return -2; }
me->items[me->count++] = led; return 0; }
void led_manager_turn_all_on(LedManager_t *me) { if (me == NULL) { return; }
for (uint8_t i = 0; i < me->count; i++) { led_on(me->items[i]); } }
|
使用时可以加入不同具体类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| GpioLed_t gpio_led; PwmLed_t pwm_led; RgbLed_t rgb_led;
gpio_led_init(&gpio_led, 5); pwm_led_init(&pwm_led, 6, 1); rgb_led_init(&rgb_led, 7, 8, 9);
LedManager_t manager = {0};
led_manager_add(&manager, &gpio_led.base); led_manager_add(&manager, &pwm_led.base); led_manager_add(&manager, &rgb_led.base);
led_manager_turn_all_on(&manager);
|
管理器全程只与 LedBase_t * 打交道。
向下转型:从 base 找回具体对象
具体实现函数收到的是 LedBase_t *,但 PWM LED 需要访问 pwm_channel:
1 2 3 4 5 6 7 8 9
| static int pwm_led_on(LedBase_t *base) { PwmLed_t *me = container_of(base, PwmLed_t, base);
me->base.is_on = true; pwm_set_duty(me->pwm_channel, me->base.brightness);
return 0; }
|
container_of 的本质是已知结构体某个成员的地址,反推出整个结构体的起始地址:
1 2
| #define container_of(ptr, type, member) \ ((type *)((char *)(ptr) - offsetof(type, member)))
|
这就是 C 语言里的向下转型。(C++ 的 dynamic_cast 做类似的事,但 C 里就是一行地址运算。)
嵌入式里多态解决什么
多态真正解决的是上层稳定:上层只认 LedBase_t *,新增类型只注册新对象,硬件差异封在具体实现里,代码围绕统一接口扩展。这对驱动框架、设备管理、协议栈都非常重要。
1 2 3 4
| led_on(&gpio_led.base); led_on(&pwm_led.base); led_on(&rgb_led.base);
|