摘要:在嵌入式开发中,我们习惯用 switch-case 来实现有限状态机(FSM)。但随着业务逻辑变复杂,状态从 3 个变成 20 个,case 分支里的代码会膨胀成难以维护的“面条代码”。本文将介绍如何利用 C++ 的多态(Polymorphism)和状态模式(State Pattern),将每个状态封装为独立的类,实现逻辑的彻底解耦与零成本扩展。
一、 噩梦的开始:传统的 Switch-Case 状态机
假设你正在做一个扫地机器人。最初只有三个状态:待机、清扫、充电。 C 语言的写法通常是这样的:
typedef enum { STATE_IDLE, STATE_CLEAN, STATE_CHARGE, STATE_ERROR } RobotState;
void Robot_Run() {
static RobotState currentState = STATE_IDLE;
switch (currentState) {
case STATE_IDLE:
if (Battery < 20) currentState = STATE_CHARGE;
if (UserPressStart) currentState = STATE_CLEAN;
// … 此处省略 50 行 LED 控制代码 …
break;
case STATE_CLEAN:
Motor_Start();
if (ObstacleDetected) { /* 避障逻辑 100 行 */ }
if (Battery < 10) currentState = STATE_CHARGE;
break;
case STATE_CHARGE:
// … 充电逻辑 …
break;
}
}
这种写法的痛点:
文件臃肿:所有的逻辑都塞在 main.c 或 robot.c 的一个函数里,这个函数很快就会超过 1000 行。
变量污染:为了在不同状态间共享数据,你不得不定义大量的全局变量或静态变量。
扩展地狱:现在老板让你加一个“手动遥控模式”。你必须去改那个巨大的 switch,很容易不小心把“自动清扫”的逻辑搞坏。
二、 破局:面向对象的状态模式
我们要转换思维:状态不是一个枚举值,状态应该是一个对象。
-
待机是一个对象,它只管待机该干的事。
-
清扫是一个对象,它只管清扫该干的事。
-
机器人(Context)是一个容器,它手里拿着当前的状态对象。
1. 定义状态基类 (The Interface)
这是所有状态必须遵守的契约。注意我们定义了 Enter(进入)、Run(运行)、Exit(退出)三个标准生命周期。
// RobotState.h
class RobotContext; // 前置声明
class RobotState {
public:
virtual ~RobotState() {}
// 进入该状态时触发(比如:开电机、亮绿灯)
virtual void Enter(RobotContext* ctx) = 0;
// 状态的主循环逻辑(比如:PID计算、传感器检测)
virtual void Run(RobotContext* ctx) = 0;
// 退出该状态时触发(比如:关电机、保存数据)
virtual void Exit(RobotContext* ctx) = 0;
};
2. 定义机器人上下文 (The Context)
这个类负责管理当前是谁在“当家”。
// RobotContext.h
#include "RobotState.h"
class RobotContext {
private:
RobotState* currentState; // 当前状态指针(核心)
public:
RobotContext() : currentState(nullptr) {}
// 切换状态的魔法函数
void ChangeState(RobotState* newState) {
// 1. 退出旧状态
if (currentState) {
currentState->Exit(this);
}
// 2. 切换指针
currentState = newState;
// 3. 进入新状态
if (currentState) {
currentState->Enter(this);
}
}
// 主循环调用它
void Loop() {
if (currentState) {
currentState->Run(this);
}
}
// 硬件接口(供状态类调用)
void SetMotor(int speed) { /*…*/ }
int GetBattery() { return 80; }
};
三、 实现具体状态:解耦的艺术
现在,我们可以把每个状态写在独立的文件里。
1. 待机状态 (IdleState)
// IdleState.cpp
#include "IdleState.h"
#include "CleaningState.h" // 需要引用下一个可能跳转的状态
// 为了避免频繁 new/delete,我们可以使用单例模式(静态局部变量)
RobotState* IdleState::Instance() {
static IdleState instance;
return &instance;
}
void IdleState::Enter(RobotContext* ctx) {
printf("[Idle] Stop Motors, LED Green.\\n");
ctx->SetMotor(0);
}
void IdleState::Run(RobotContext* ctx) {
// 只需要关注待机时的逻辑
if (ctx->GetBattery() < 10) {
// 切换到充电状态
// ctx->ChangeState(ChargingState::Instance());
}
if (User_Is_Pressed()) {
// 切换到清扫状态
ctx->ChangeState(CleaningState::Instance());
}
}
void IdleState::Exit(RobotContext* ctx) {
printf("[Idle] Preparing to work…\\n");
}
2. 清扫状态 (CleaningState)
// CleaningState.cpp
void CleaningState::Enter(RobotContext* ctx) {
printf("[Clean] Motor ON, LED Blue.\\n");
ctx->SetMotor(100);
}
void CleaningState::Run(RobotContext* ctx) {
// 只写清扫逻辑,完全不用管怎么待机
DoPathPlanning();
if (Error_Occurred()) {
// 发生故障,切换故障状态
// ctx->ChangeState(ErrorState::Instance());
}
}
void CleaningState::Exit(RobotContext* ctx) {
ctx->SetMotor(0); // 退出清扫必须关电机,防止暴走
}
四、 这种架构好在哪里?
1. 彻底消灭了 if-else / switch
在 RobotContext::Loop() 中,你只看到一行代码:
currentState->Run(this);
系统完全不需要知道当前是 Idle 还是 Cleaning,它利用 C++ 的多态性(Virtual Function) 自动跳转到了对应的代码段。这叫“动态绑定”。
2. 新增状态 = 新增文件
如果老板要加“遥控模式”:
新建 RemoteState.cpp。
继承 RobotState,实现 Enter/Run/Exit。
在 Idle 状态里加一个判断跳转过去。 原有的 CleaningState、ChargingState 代码一行都不用动! 这极大地降低了引入 Bug 的风险。
3. 原子化的生命周期管理
以前写 Switch 时,从“清扫”跳到“待机”,很容易忘记关电机。 现在,ChangeState 函数强制执行 oldState->Exit()。你只需要在 CleaningState::Exit 里写上关电机,无论你是因为没电了退出,还是因为用户按停了退出,电机一定会被关闭。
五、 嵌入式特供优化:内存碎片问题
标准的 Design Pattern 经常在切换状态时使用 new CleaningState(),旧状态 delete this。 但在 STM32 上,频繁的堆内存分配会导致碎片化。
解决方案:单例状态 (Singleton States) 如代码所示,每个状态类内部维护一个 static 实例:
static IdleState instance;
return &instance;
这样所有状态对象都在静态存储区(BSS/Data段),程序运行期间内存占用是固定的,零 malloc,零风险。
六、 总结
很多嵌入式工程师认为 C++ 臃肿,是因为他们还在用 C 的思维写 C++(Class 只是 struct 的别名)。
真正的 C++ 威力在于架构设计。通过状态模式,我们将复杂的业务逻辑拆解成了独立的、可测试的微小单元。
-
Switch-Case 适合简单的、状态少于 3 个的逻辑。
-
状态模式 是中大型项目(机器人、飞行器、复杂工控)的标配。
下一次,当你的 switch 写到第 500 行的时候,记得回来看看这篇文章。
网硕互联帮助中心




![[oAuth2授权]Web前端+Node&Coze API Web后端程序+Coze授权服务器工作流程详解-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2025/04/20250418203650-6802b7e2744d9-220x150.png)

评论前必须登录!
注册