3、状态机表示方法:状态图、状态转移表、状态转移矩阵
有限状态机(FSM)与层次状态机(HSM)的建模,本质上是对系统行为的一种精确描述。在工程实践中,我们面临的首要问题不是“如何设计状态机”,而是“如何把脑子里的状态逻辑清晰、无歧义地表达出来”。状态图(State Diagram)、状态转移表(State Transition Table)和状态转移矩阵(State Transition Matrix)是三种最核心的表示方法,它们分别服务于人类直觉沟通、形式化验证和算法化处理。本章将深入剖析这三种表示法的内在结构、适用场景及相互转换关系,帮助你在嵌入式、协议栈或业务编排中做出正确的建模选择。
上图展示了三种表示方法在状态机建模生态中的定位:状态图是面向人脑的“直觉层”,状态转移表是面向文档与验证的“结构层”,状态转移矩阵则是面向计算机与算法的“数据层”。三者描述的是同一逻辑内核,但表达维度和信息密度不同。下面我们逐一展开。
3.1 状态图:可视化建模的黄金标准
核心原理
状态图(State Diagram)源自UML(统一建模语言)状态机图,它用图形化元素表达状态、事件、动作和转移关系。其基本构成包括:圆角矩形(状态)、实心圆(初始伪状态)、牛眼符号(终止状态)、带箭头的连线(转移)。每个转移上标注“事件[守卫条件]/动作”的语法,例如 e1[count > 3] / sendAlert()。状态图的最大优势在于层次化表达能力——它天然支持嵌套状态(子状态)、历史状态(H*)和并发区域(fork/join),这是层次状态机(HSM)建模的首选工具。
判断方法与操作流程
在工程中,判断何时使用状态图,可遵循以下准则:
- 当系统状态数 < 10 且交互逻辑复杂时,状态图能显著降低沟通成本。
- 当需要表达状态嵌套或并发行为时(如通信协议栈、GUI控件),状态图几乎是唯一直观的选择。
- 当团队进行设计评审时,状态图作为讨论媒介,比表格更容易发现遗漏的转移路径。
绘制状态图的标准流程为:
- 列出所有候选状态,剔除重复或可合并的状态。
- 确定初始状态和终止状态(若有)。
- 对每个状态,枚举所有可能触发转移的事件。
- 为每个转移标注事件、守卫条件(可选)和动作(可选)。
- 检查是否存在“非确定性转移”(同一事件在同一状态产生多个未加守卫的转移)。
案例拆解:电梯控制器状态图
考虑一个简化电梯控制器,状态包括:Idle(待机)、MovingUp(上行)、MovingDown(下行)、DoorOpen(开门)。事件包括:callUp、callDown、arriveFloor、doorClose。状态图可清晰表达:在 Idle 状态收到 callUp 后转移至 MovingUp 并执行 startMotor();在 MovingUp 状态收到 arriveFloor 后转移至 DoorOpen 并执行 stopMotor()。若在 MovingUp 时收到 callDown,则需守卫条件 [currentFloor > targetFloor] 才能响应,否则忽略。这种带守卫的转移在表格中表达较为繁琐,但在状态图中一目了然。
3.2 状态转移表:精确性与可验证性的基石
核心原理
状态转移表(State Transition Table)以行表示当前状态,以列表示事件(或输入),单元格内填写“下一状态/动作”。它消除了状态图的视觉歧义,每个转移都是显式且唯一的。对于有限状态机(FSM),转移表是形式化验证(如模型检测、等价性检查)的输入格式。在安全关键系统(如航空电子、医疗设备)中,转移表是需求文档的必备附件,因为它可以逐行审查,确保没有遗漏任何事件-状态组合。
操作流程与表格设计
构建状态转移表的标准步骤:
- 定义状态集合 S = {s1, s2, ..., sn}。
- 定义事件集合 E = {e1, e2, ..., em}。
- 对每个 (si, ej) 组合,确定转移目标 T(si, ej) 和输出动作 A(si, ej)。
- 若某组合无转移,则标记为“-”或“忽略”,但必须显式写出,避免歧义。
一个典型的转移表结构如下:
| 当前状态 | 事件 e1 | 事件 e2 | 事件 e3 |
|---|---|---|---|
| S1 | S2 / action1 | - | S3 / action3 |
| S2 | - | S1 / action2 | S3 / - |
| S3 | S1 / action1 | S2 / - | - |
注意,表中“-”表示该事件在当前状态下被忽略,但必须显式标注,否则在代码实现时容易产生未定义行为。
实战场景:通信协议解析器
在实现一个UART通信协议解析器时,状态包括 WAIT_START、WAIT_LEN、WAIT_DATA、WAIT_CRC。事件为接收到的字节类型。使用状态转移表可以精确描述:在 WAIT_START 收到 0xAA 时转移至 WAIT_LEN;收到其他字节则停留在 WAIT_START 并丢弃。这种表格可以直接转化为C语言的switch-case结构,且每个case都有明确的输入-输出映射,便于单元测试用例的生成。
3.3 状态转移矩阵:算法化与模型检测的利器
核心原理
状态转移矩阵(State Transition Matrix)是转移表的数学化表达,通常以二维矩阵 M 表示,其中 M[i][j] 表示从状态 i 经事件 j 转移到的目标状态。矩阵元素可以是目标状态编号、动作编码或“无效”(用 -1 或 INF 表示)。这种表示法在模型检测工具(如NuSMV、SPIN)、自动代码生成(如MATLAB Stateflow)和图搜索算法(如BFS/DFS遍历状态空间)中广泛使用。
判断方法与构建要点
选择矩阵表示法的判断依据:
- 需要执行状态空间搜索(如寻找最短路径、检测死锁)时,矩阵是标准输入。
- 需要做矩阵运算(如计算传递闭包、检测可达性)时,矩阵是唯一选择。
- 状态数较多(> 20)且事件数固定时,矩阵比表格更紧凑。
构建矩阵时需注意:
- 矩阵的行和列必须明确标注状态名和事件名,避免索引错位。
- 对于无转移的单元格,建议填入特殊值(如 -1),而不是留空。
- 若同一事件在不同守卫条件下产生不同转移,矩阵无法直接表达,需拆分为多个事件(如 e1_guard_true、e1_guard_false)。
案例拆解:自动售货机状态矩阵
设自动售货机状态:S0(空闲)、S1(已投币5元)、S2(已投币10元)。事件:coin5、coin10、dispense。状态转移矩阵如下:
| 当前状态 | coin5 | coin10 | dispense |
|---|---|---|---|
| S0 | S1 | S2 | -1 |
| S1 | S2 | -1 | S0 |
| S2 | -1 | -1 | S0 |
该矩阵可直接用于生成状态转移图,也可用于验证“从S0出发,经过任意事件序列,最终能否回到S0”的可达性分析。在Python中,可用二维列表或NumPy数组表示,配合BFS算法快速检测是否存在不可达状态。
3.4 常见误区与选型复盘
误区一:状态图与流程图混淆
状态图描述的是“状态因事件而迁移”,流程图描述的是“步骤因条件而顺序执行”。例如,电梯的“开门-关门-上行”是流程,而“DoorOpen状态在收到doorClose事件后转移至MovingUp状态”才是状态图。若将流程逻辑画成状态图,会导致状态爆炸且难以维护。
误区二:转移表遗漏“忽略”事件
许多工程师在转移表中只填写有转移的单元格,忽略无转移的情况。这会导致代码实现时出现未处理的输入,引发运行时错误。正确做法是显式填写“-”或“忽略”,并在代码中生成默认分支。
误区三:矩阵中混淆守卫条件
状态转移矩阵无法直接表达守卫条件(Guard)。若强行将带守卫的转移写入矩阵,会导致矩阵元素不唯一。解决方案是将守卫条件转化为事件的一部分,例如将 e1[count>3] 拆分为 e1_high 和 e1_low 两个事件,分别填入矩阵的不同列。
选型决策清单
| 场景 | 推荐表示法 | 理由 |
|---|---|---|
| 设计评审、团队沟通 | 状态图 | 直观、层次化、易发现逻辑漏洞 |
| 安全关键系统需求文档 | 状态转移表 | 可逐行审查、无歧义、便于形式化验证 |
| 自动代码生成、模型检测 | 状态转移矩阵 | 算法友好、支持矩阵运算、紧凑 |
| 状态数 > 20 且含嵌套 | 层次状态图 + 转移表 | 平面表示法会指数膨胀 |
在实际项目中,建议在需求阶段使用状态图进行探索性设计,在设计定型后转换为状态转移表作为正式规格,最后在编码阶段将转移表映射为矩阵或直接生成代码。这种“图-表-矩阵”的演进路径,既能保证沟通效率,又能确保实现精度。