10 多态与转型:一个指针管所有LED

C 语言 OOP 系列导航

  1. C语言你真的会封装吗?
  2. 三个LED写了三份代码?
  3. 同事改一行,LED全乱了
  4. 手搓class:前缀、init/deinit和生命周期
  5. 你的全局变量,该死了
  6. 工业库也是同一套封装套路
  7. struct嵌套消灭复制粘贴
  8. 写死的函数怎么换
  9. 把一组函数指针装进对象
  10. 一个指针管所有LED ⇐ 当前位置
  11. 虚函数不实现会怎样
  12. 换硬件不改应用
  13. 从自动注册到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,同一套调用
led_on(&gpio_led.base); // -> gpio_led_on()
led_on(&pwm_led.base); // -> pwm_led_on()
led_on(&rgb_led.base); // -> rgb_led_on()