多态与转型:base 指针与 dispatch
10 多态与转型:base 指针与 dispatch
C 语言 OOP 系列导航
本系列面向会 C 和单片机基础、刚进入企业项目的同学,从
struct封装一路讲到ops、多态和类 Linux 驱动框架。
现在我们已经有了两个零件:公共字段 base,以及行为表 ops。
多态要解决的问题是:上层不想知道这颗 LED 到底是 GPIO LED、PWM LED 还是 RGB LED。上层只想写:
1 | led_on(led); |
具体怎么点亮,由对象自己决定。
一句话先懂:C 语言的多态,就是上层只拿 base 指针,统一调用 dispatch 函数,再由 ops 分发到具体实现。
先看问题:if/else 会把具体类型泄漏到上层
没有多态时,上层往往会这样写:
1 | if (type == LED_TYPE_GPIO) { |
这段代码的问题不是 if/else 本身,而是上层知道了所有具体类型。
| 问题 | 后果 |
|---|---|
| 上层知道所有具体类型 | 抽象被打穿 |
| 新增类型要改分支 | 旧代码不断膨胀 |
| 调用入口不统一 | 管理器越来越复杂 |
| 具体硬件泄漏到业务层 | 换硬件成本高 |
目标是把这些分支藏到对象自己的 ops 里。
向上转型:从具体对象拿到 base 指针
具体类型把 LedBase_t 放在结构体里:
1 | typedef struct { |
上层只拿基础部分:
1 | PwmLed_t pwm_led; |
这就是 C 语言里的向上转型。
| 概念 | C 写法 |
|---|---|
| 具体对象 | PwmLed_t pwm_led |
| 基础部分 | pwm_led.base |
| 基础指针 | LedBase_t *led = &pwm_led.base |
| 上层只看抽象 | 只保存 LedBase_t * |
dispatch:统一入口完成行为分发
不要让外部到处写 led->ops->on(led)。更好的做法是提供统一入口:
1 | int led_on(LedBase_t *me) |
其他接口同理:
1 | int led_off(LedBase_t *me) |
调用链路展开后是:
1 | led_on(led) |
上层调用同一个 led_on(),具体实现由 ops 决定。
管理器:一个数组保存不同类型
有了基础指针,管理器不需要关心具体类型:
1 |
|
统一操作只需要遍历基础指针:
1 | void led_manager_turn_all_on(LedManager_t *me) |
使用时可以加入不同具体类型:
1 | GpioLed_t gpio_led; |
管理器全程只与 LedBase_t * 打交道。
向下转型:从 base 找回具体对象
具体实现函数收到的是 LedBase_t *,但有时需要访问派生类型自己的字段。比如 PWM LED 需要 pwm_channel:
1 | static int pwm_led_on(LedBase_t *base) |
container_of 的本质是:已知结构体某个成员的地址,反推出整个结构体的起始地址。
1 |
这就是 C 语言里的向下转型。
新人常见误区
| 误区 | 更准确的理解 |
|---|---|
| 多态就是函数指针 | 还需要基础指针、统一入口和对象绑定 |
上层可以直接调 ops |
最好通过 dispatch 统一检查和默认处理 |
container_of 很神秘 |
它只是用成员地址减去成员偏移 |
| 所有分支都要消灭 | 具体实现内部仍然可以有必要分支 |
C/C++ 对照
| C++ | C |
|---|---|
基类指针 Led * |
LedBase_t * |
| 派生类对象 | 内嵌 LedBase_t base 的结构体 |
| 向上转型 | &derived.base |
| 虚函数调用 | me->ops->on(me) |
| 动态绑定 | 初始化时绑定不同 ops |
| 向下转型 | container_of |
嵌入式项目意义
多态真正解决的是上层稳定:
| 没有多态 | 有多态 |
|---|---|
| 上层判断具体类型 | 上层只认基础接口 |
| 新增类型要改管理器 | 新增类型只注册新对象 |
| 硬件细节到处泄漏 | 硬件差异封在具体实现里 |
| 代码越写越多分支 | 代码围绕统一接口扩展 |
这对驱动框架、设备管理、协议栈都非常重要。
总结
C 语言的多态 =
base指针 +ops操作表 + dispatch 函数;上层面向抽象,具体对象自己决定行为。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Rvosy的小破站!







