状态机实现的方法
状态机实现的方法,可以按两条线来理解“实现方法”:一条是理论模型(输出何时产生/状态如何组织),另一条是工程落地形态(代码怎么写、怎么派发事件/定时器)。
一、常见理论模型
- Moore 型:输出只由“当前状态”决定。
- Mealy 型:输出由“当前状态 + 输入事件”一起决定。
- 分层/层次状态机(HFSM / Harel Statecharts):状态可嵌套、带历史、并行区等;解决大系统状态爆炸。
- 扩展状态机(EFSM):在FSM上引入数据变量与守卫条件(条件转移)。
二、工程实现形态(代码层面的“几种写法”)
-
switch–case 直写型(最基础)
- 思路:
switch(state){case S: if(evt==X){...; state=Y;}} - 优点:简单、零依赖、调试直观;小型逻辑够用。
- 缺点:状态多时易臃肿,耦合高,不利于复用与单元测试。
- 适用:小项目、一次性原型。
- 思路:
-
表驱动(转移表)
- 思路:用数组/表描述“当前状态、事件 → 目标状态、动作函数、守卫”;运行期查表执行。
- 优点:结构清晰、易审计与生成文档/可做配置化;支持脚本/工具生成。
- 缺点:调试跳转路径需要辅助日志;表结构要设计好。
- 适用:状态多、需要审计/合规或自动化生成的项目。
-
函数指针/回调的“状态对象”法(C 里常见)
- 思路:每个状态有
on_enter/on_exit/on_event/on_tick函数指针;事件通过派发器路由到当前状态。 - 优点:内聚、可单测、可拆分文件;与你的代码最接近。
- 缺点:需要良好的约定(返回值、并发、定时器回调)以防混乱。
- 适用:中等规模嵌入式;需要软/硬定时器与事件队列。
- 思路:每个状态有
-
事件队列 + 反应堆(Reactor)/发布订阅
- 思路:事件先入队;主循环(或线程)统一取队→派发→状态机;定时器“转事件”。
- 优点:解耦、易扩展、便于记录/回放;天然支持多来源事件。
- 缺点:需要队列与背压管理;实时性要评估。
- 适用:多外设/多来源事件的系统(网络、串口、GPIO、定时器)。
-
面向对象的 State Pattern(C++/面向对象语言)
- 思路:每个状态是一个类,虚函数处理事件/进入/退出。
- 优点:可读性强、扩展方便、与依赖注入/单测融洽。
- 缺点:嵌入式上可能有开销;需注意内存/RT限制。
- 适用:C++应用、上层业务逻辑、桌面/服务器端。
-
分层状态机/状态图框架(UML Statecharts, HFSM 库)
- 思路:支持父状态、并行区域、历史状态、进入/退出动作等完整语义。
- 优点:复杂系统的维度降噪;跨模块复用强。
- 缺点:学习曲线与实现复杂度更高。
- 适用:大型嵌入式、车载/工业控制、通信协议栈等。
-
代码生成/DSL(如 SCXML、SMC、PlantUML→代码、QP/C 等)
- 思路:用状态图/DSL描述 → 工具生成 C/C++/Java 代码与图;常内建定时事件与事件队列。
- 优点:模型即文档、减少手写错误、便于审计与维护。
- 缺点:引入工具链与生成工序;需适配你的构建系统。
- 适用:对一致性/可追溯性/合规要求高的团队或长期维护项目。
-
数据驱动/配置化 FSM(JSON/表格装配)
- 思路:转移与守卫从配置加载,动作映射到脚本/回调。
- 优点:运行期可切换策略;A/B 试验与快速迭代友好。
- 缺点:调试复杂、需严格校验器与回滚方案。
- 适用:上层业务、游戏AI/规则引擎、网关策略。
三、定时器接入的常见两法
- 内嵌式定时器槽:状态表内就声明定时动作,到期直接回调/跳转。
- 定时器→事件统一化:定时器只负责投递事件(软/硬),由
dispatch()像处理普通事件那样决定动作与跳转;更解耦,便于记录/回放。
四、怎么选
- 小而快:switch–case 或轻量指针法。
- 状态很多/需审计:表驱动或代码生成。
- 多来源事件/需要录制回放:事件队列 + 定时器转事件。
- 复杂层次/并行/历史:HFSM/Statecharts 框架或生成工具。
- 和你现有代码最贴近:函数指针状态对象 + 定时器转事件(可把硬/软定时器统一为事件源,提升可维护性)。
用 C 语言实现状态机主要有 3 种方法:switch—case 法、表格驱动法、函数指针法。
经常使用的是 表格驱动法+函数指针法,合二为一。
