C++学习进阶版(一):用C++写简单的状态机实现

目录

一、基础知识

1、状态机

2、四大要素

3、描述方式

4、设计步骤

5、实现过程中需注意

(1) 状态定义

(2) 状态转换规则

(3) 输入处理

(4) 状态机的封装

(5) 状态机的可扩展性和维护性

(6) 避免状态爆炸

(7) 并发和同步

(8) 资源管理

(9) 异常处理

(10) 清晰注释和文档

二、状态机实现

1、绘制状态转移图

2、创建状态转移的FSMIterm类

3、创建有限状态机FSM类

4、测试FSM

5、完整代码

6、测试结果

三、多分支状态机实现

1、需要修改的部分

2、更改输出状态的字符形式表达

3、新增了一个分支选择变量takeGamePath

4、完整代码

5、运行结果


前阵子在改的程序已经暂时告一段落了,现在要和其他同学所做的项目联系起来,其中有一部分涉及到状态机的设计,所以简单写个笔记吧。

参考资料:

什么是状态机?-CSDN博客

c++状态机的使用_c++ 状态机-CSDN博客

一、基础知识

1、状态机

状态机是有限状态自动机(Finite State Machine,FSM)的简称,通过状态图可以清晰表达整个状态的流转。

其中涉及到四个概念:

  1. 状态(state):指事物的不同状态,一个状态机至少有两个状态。例如一个灯泡,有“亮”和“灭”两种。
  2. 事件(event):执行某个操作的触发条件或者口令。例如对于事物“灯泡”来说,有“打开开关”和“关闭开关”两个事件。
  3. 动作(action):事件发生以后要执行动作。例如对于事件“打开开关”,动作就是“开灯”。一般情况下,一个action一般就对应一个函数。
  4. 转换(transition):从一个状态转变成另一个状态。例如,“开灯过程”就是一个变换。

2、四大要素

  • 现态:当前所处状态
  • 次态:当条件满足后,即将转移的下一个状态
  • 动作:当满足某个事件时执行的动作;动作执行完毕后可以转移到另一个状态或保持原有状态
  • 条件:转移状态所需的条件,当满足条件时,会触发一个动作或进行状态转移

3、描述方式

  • 状态转移图
  • 状态转移表
  • HDL描述

4、设计步骤

  • 逻辑抽象,得到状态转移图:确定输入、输出、状态变量、画状态转移图;
  • 状态简化,得到最简的状态转移图:合并等价状态;
  • 状态编码;
  • 用C++描述;

5、实现过程中需注意

(1) 状态定义

  • 明确定义状态集合,并确保状态之间转换的完备性和一致性。
  • 可以使用枚举类型来定义状态,便于理解和管理。

(2) 状态转换规则

  • 清晰地定义每个状态下接收哪些输入信号以及接收到这些信号时如何转换到新的状态。
  • 使用某种机制(如switch-case、查找表、函数指针等)来实现状态间的转换。

(3) 输入处理

  • 确保状态机能正确响应所有有效的输入信号,并且对于无效输入有合适的默认处理策略。
  • 如果存在多种输入同时有效的情况,要考虑如何优先级排序或冲突处理。

(4) 状态机的封装

  • 将状态机实现封装在一个模块中,隐藏内部状态变化细节,对外暴露接口供其他部分调用。
  • 使用私有变量保存当前状态,并通过公共函数改变状态。

(5) 状态机的可扩展性和维护性

  • 设计时尽量使状态机易于添加新状态或修改现有状态转换逻辑。
  • 使用表驱动(table-driven)方法可以提高代码的可读性和可维护性,尤其是当状态数量较多时。

(6) 避免状态爆炸

  • 当状态过多时,要考虑是否存在冗余状态,尝试优化和归并相似状态,以减少状态总数。

(7) 并发和同步

  • 若状态机涉及多线程或中断处理,要特别注意状态访问和修改的原子性,可能需要使用互斥锁等同步机制。

(8) 资源管理

  • 如果状态机操作伴随着资源(如内存、文件句柄等)的申请和释放,务必在合适的状态转换时完成相应的清理工作,避免资源泄露。

(9) 异常处理

  • 确保状态机在发生错误或异常情况下能够恢复到安全状态或报告错误。

(10) 清晰注释和文档

  • 详细记录状态机的工作流程、状态转换图以及关键状态和转换的解释,有助于后期维护和他人理解代码。

二、状态机实现

在这里,我借鉴了脚本之家--《C++有限状态机实现详解》中的学生的日常生活示例。

  • 事物:学生;
  • 学生状态:起床、上学、吃午饭、写作业、睡觉;
  • 状态之间需要执行相应的事件进行转移。

1、绘制状态转移图

2、创建状态转移的FSMIterm类

  • 枚举所有状态State、所有事件Event;
  • 成员变量:现态_curState、事件_event、次态_nextState;
  • 成员函数:动作函数
//FSM状态项
class  FSMIterm
{friend class FSM;//声明 FSM 类为 FSMIterm 类的朋友类//这意味着 FSM 类可以访问 FSMIterm 类的所有成员,包括私有和保护成员
private://状态对应的动作函数static void getup(){cout << "student is getting up!" <<endl;}static void gotoschool(){cout << "student is going to school!" <<endl;}static void havelunch(){cout << "student is having lunch!" <<endl;}static void dohomework(){cout << "student is doing homework!" <<endl;}static void sleeping(){cout << "student is sleeping!" <<endl;}public://枚举所有可能的状态enum State {GETUP = 0,GOTOSCHOOL,HAVELUNCH,DOHOMEWORK,SLEEP};//枚举所有可能触发状态转换的事件enum Events{EVENT1 = 0,EVENT2,EVENT3};//初始化构造函数//构造函数接受四个参数:现态curState、条件event、动作(一个指向函数的指针action)、次态nextState//初始化列表分别用来初始化私有成员变量 _curState、_event、_action 和 _nextStateFSMIterm(State curState, Events event, void(*action)(), State nextState):_curState(curState), _event(event), _action(action), _nextState(nextState){}private://前下划线表示为私有成员变量State _curState;    //现态Events _event;      //条件void (*_action)();  //动作//*action是一个指向无参数无返回值函数的指针,用于执行与当前状态相关联的动作State _nextState;   //次态
};

为了方便后面仿照写函数,对每行进行了注释。

其主要的思想还是上面的三个步骤。

3、创建有限状态机FSM类

  • 成员变量:状态转移表vector<FSMIterm*> _fsmTable
  • 成员函数:初始化状态转移表、状态转移、根据事件执行相应动作
class  FSM
{
private://根据状态图初始化状态转移表void initFSMTable(){        //负责根据状态转移规则初始化一个状态转移表//每个 FSMIterm 实例都包含了状态转移的信息,如现态、触发事件、动作以及次态_fsmTable.push_back(new FSMIterm(FSMIterm::GETUP, FSMIterm::EVENT1, &FSMIterm::getup, FSMIterm::GOTOSCHOOL));_fsmTable.push_back(new FSMIterm(FSMIterm::GOTOSCHOOL, FSMIterm::EVENT2, &FSMIterm::gotoschool, FSMIterm::HAVELUNCH));_fsmTable.push_back(new FSMIterm(FSMIterm::HAVELUNCH, FSMIterm::EVENT3, &FSMIterm::havelunch, FSMIterm::DOHOMEWORK));_fsmTable.push_back(new FSMIterm(FSMIterm::DOHOMEWORK, FSMIterm::EVENT1, &FSMIterm::dohomework, FSMIterm::SLEEP));_fsmTable.push_back(new FSMIterm(FSMIterm::SLEEP, FSMIterm::EVENT2, &FSMIterm::sleeping, FSMIterm::GETUP));}vector<FSMIterm*> _fsmTable;    //定义私有变量_fsmTable,用来存储状态转移表public://初始化当前状态(_curState)为指定状态(默认为 GETUP)//立即调用 initFSMTable 方法初始化状态转移表FSM(FSMIterm::State curState = FSMIterm::GETUP):_curState(curState){initFSMTable();}//状态转移void transferState(FSMIterm::State nextState){_curState = nextState;   将传入的 nextState 赋值给_fsm 的现态 _curState}//当接收到一个事件(event)时,遍历状态转移表寻找匹配当前状态及事件的状态项//若找到匹配项,则设置标志 flag 为真,记录对应的 action 函数指针和 nextStatevoid handleEvent(FSMIterm::Events event){FSMIterm::State curState = _curState;   //现态void (*action)() = nullptr;             //动作FSMIterm::State nextState;              //次态bool flag = false;for (int i = 0; i < _fsmTable.size(); i++){if(event == _fsmTable[i]->_event && curState == _fsmTable[i]-> _curState){flag = true;action = _fsmTable[i]->_action;nextState = _fsmTable[i]->_nextState;break;}}//找到对应的状态项,调用对应的动作函数action,然后调用transferState函数更新状态机到新状态if(flag){if(action){action();}transferState(nextState);}}
//公共部分定义一个成员变量,表示有限状态机的当前状态,可供外部访问
public:FSMIterm::State _curState;      };

4、测试FSM

//测试事件变换,用来改变传入事件的值,使其按照一定的顺序循环变化
void testEvent(FSMIterm::Events& event){switch (event){case FSMIterm::EVENT1:event = FSMIterm::EVENT2;   //如果事件为event1,则将事件更改为event2break;case FSMIterm::EVENT2:event = FSMIterm::EVENT3;break;case FSMIterm::EVENT3:event = FSMIterm::EVENT1;break;}
}int main(){FSM *fsm = new FSM();   //创建一个FSM类的实例,并将其指针存储在fsm中auto event = FSMIterm::EVENT1;int i = 0;while(i < 12){cout << "event " << event << " is coming……" <<endl;fsm->handleEvent(event);cout << "fsm current state is " << fsm->_curState << endl;testEvent(event);i++;}cout << "event: " << event <<endl;cout << "curState: " << fsm->_curState <<endl;  //打印当前状态return 0;}

5、完整代码

#include <iostream>
#include <vector>
using namespace std;//FSM状态项
class  FSMIterm
{friend class FSM;//声明 FSM 类为 FSMIterm 类的朋友类//这意味着 FSM 类可以访问 FSMIterm 类的所有成员,包括私有和保护成员
private://状态对应的动作函数static void getup(){cout << "student is getting up!" <<endl;}static void gotoschool(){cout << "student is going to school!" <<endl;}static void havelunch(){cout << "student is having lunch!" <<endl;}static void dohomework(){cout << "student is doing homework!" <<endl;}static void sleeping(){cout << "student is sleeping!" <<endl;}public://枚举所有可能的状态enum State {GETUP = 0,GOTOSCHOOL,HAVELUNCH,DOHOMEWORK,SLEEP};//枚举所有可能触发状态转换的事件enum Events{EVENT1 = 0,EVENT2,EVENT3};//初始化构造函数//构造函数接受四个参数:现态curState、条件event、动作(一个指向函数的指针action)、次态nextState//初始化列表分别用来初始化私有成员变量 _curState、_event、_action 和 _nextStateFSMIterm(State curState, Events event, void(*action)(), State nextState):_curState(curState), _event(event), _action(action), _nextState(nextState){}private://前下划线表示为私有成员变量State _curState;    //现态Events _event;      //条件void (*_action)();  //动作//*action是一个指向无参数无返回值函数的指针,用于执行与当前状态相关联的动作State _nextState;   //次态
};class  FSM
{
private://根据状态图初始化状态转移表void initFSMTable(){        //负责根据状态转移规则初始化一个状态转移表//每个 FSMIterm 实例都包含了状态转移的信息,如现态、触发事件、动作以及次态_fsmTable.push_back(new FSMIterm(FSMIterm::GETUP, FSMIterm::EVENT1, &FSMIterm::getup, FSMIterm::GOTOSCHOOL));_fsmTable.push_back(new FSMIterm(FSMIterm::GOTOSCHOOL, FSMIterm::EVENT2, &FSMIterm::gotoschool, FSMIterm::HAVELUNCH));_fsmTable.push_back(new FSMIterm(FSMIterm::HAVELUNCH, FSMIterm::EVENT3, &FSMIterm::havelunch, FSMIterm::DOHOMEWORK));_fsmTable.push_back(new FSMIterm(FSMIterm::DOHOMEWORK, FSMIterm::EVENT1, &FSMIterm::dohomework, FSMIterm::SLEEP));_fsmTable.push_back(new FSMIterm(FSMIterm::SLEEP, FSMIterm::EVENT2, &FSMIterm::sleeping, FSMIterm::GETUP));}vector<FSMIterm*> _fsmTable;    //定义私有变量_fsmTable,用来存储状态转移表public://初始化当前状态(_curState)为指定状态(默认为 GETUP)//立即调用 initFSMTable 方法初始化状态转移表FSM(FSMIterm::State curState = FSMIterm::GETUP):_curState(curState){initFSMTable();}//状态转移void transferState(FSMIterm::State nextState){_curState = nextState;   将传入的 nextState 赋值给_fsm 的现态 _curState}//当接收到一个事件(event)时,遍历状态转移表寻找匹配当前状态及事件的状态项//若找到匹配项,则设置标志 flag 为真,记录对应的 action 函数指针和 nextStatevoid handleEvent(FSMIterm::Events event){FSMIterm::State curState = _curState;   //现态void (*action)() = nullptr;             //动作FSMIterm::State nextState;              //次态bool flag = false;for (int i = 0; i < _fsmTable.size(); i++){if(event == _fsmTable[i]->_event && curState == _fsmTable[i]-> _curState){flag = true;action = _fsmTable[i]->_action;nextState = _fsmTable[i]->_nextState;break;}}//找到对应的状态项,调用对应的动作函数action,然后调用transferState函数更新状态机到新状态if(flag){if(action){action();}transferState(nextState);}}
//公共部分定义一个成员变量,表示有限状态机的当前状态,可供外部访问
public:FSMIterm::State _curState;      };//测试事件变换,用来改变传入事件的值,使其按照一定的顺序循环变化
void testEvent(FSMIterm::Events& event){switch (event){case FSMIterm::EVENT1:event = FSMIterm::EVENT2;   //如果事件为event1,则将事件更改为event2break;case FSMIterm::EVENT2:event = FSMIterm::EVENT3;break;case FSMIterm::EVENT3:event = FSMIterm::EVENT1;break;}
}int main(){FSM *fsm = new FSM();   //创建一个FSM类的实例,并将其指针存储在fsm中auto event = FSMIterm::EVENT1;int i = 0;while(i < 12){cout << "event " << event << " is coming……" <<endl;fsm->handleEvent(event);cout << "fsm current state is " << fsm->_curState << endl;testEvent(event);i++;}cout << "event: " << event <<endl;cout << "curState: " << fsm->_curState <<endl;  //打印当前状态return 0;}

6、测试结果

原版中的状态机是个无限循环状态,在这里我是定义了一个int型整数,只执行12次(至于为什么是12次,刚开始以为是4种状态,可以每个状态执行3遍,运行后发现是5个状态/扶额苦笑)

三、多分支状态机实现

为了区分不同事件驱动不同状态,所以在这里把原来可以不同状态相同事件驱动的改成不同状态驱动了。

同时,前面的状态机实现是单个分支的,为了方便后续复杂状态机的开发,所以我新增了一个分支路,即:状态GOTOSCHOOL--->(EVENT4驱动)--->状态PLAYGAME--->(EVENT5驱动)--->状态DOHOMEWORK。同时为状态PLAYGAME添加一个一个静态函数playinggame()。

1、需要修改的部分

  • FSMIterm类中添加新的静态动作函数playinggame
  • FSMIterm的枚举中添加EVENT4EVENT5、EVENT6EVENT7
  • FSMIterm的枚举中添加PLAYGAME状态。
  • FSM类的initFSMTable函数中添加新的状态转移项。
  • 更新testEvent函数以处理新的事件。

2、更改输出状态的字符形式表达

cout << "fsm current state is " << fsm->_curState << endl;

状态输出时,输出的是枚举值。

新增一个输出枚举值对应的枚举名称,新增一个字符串化的辅助函数。

在FSM类中新增一个静态函数,用于将状态枚举值转换为对应的状态名称字符串。

class FSM {// ...(省略了之前的 FSM 类成员和定义)// 新增一个函数,用于将状态枚举值转换为对应的状态名称字符串static string getStateName(FSMIterm::State state) {switch (state) {case FSMIterm::GETUP: return "GETUP";case FSMIterm::GOTOSCHOOL: return "GOTOSCHOOL";case FSMIterm::PLAYGAME: return "PLAYGAME";case FSMIterm::HAVELUNCH: return "HAVELUNCH";case FSMIterm::DOHOMEWORK: return "DOHOMEWORK";case FSMIterm::SLEEP: return "SLEEP";default: return "UNKNOWN_STATE";}}// ...(省略了之前的 FSM 类其他成员和定义)
};

3、新增了一个分支选择变量takeGamePath

因为现在多了个选择分支,需要变量判断选择走哪个分支。所以在main函数中定义了一个bool类型的takeGamePath变量。如果为true,则走PLAYGAME的分支;如果为false,则走HAVELUNCH的分支。

void testEvent(FSMIterm::Events& event, bool& takeGamePath){switch (event){case FSMIterm::EVENT1:if(takeGamePath){event = FSMIterm::EVENT4;}else{event = FSMIterm::EVENT2;   //如果事件为event1,则将事件更改为event2}takeGamePath = !takeGamePath;break;case FSMIterm::EVENT2:event = FSMIterm::EVENT3;break;case FSMIterm::EVENT3:event = FSMIterm::EVENT6;break;case FSMIterm::EVENT4:event = FSMIterm::EVENT5; // 如果事件为EVENT4,则将事件更改为EVENT5break;case FSMIterm::EVENT5:event = FSMIterm::EVENT6; break;case FSMIterm::EVENT6:event = FSMIterm::EVENT7; break;case FSMIterm::EVENT7:event = FSMIterm::EVENT1; break;}
}

这里选择的分支其实是下一个要变换的事件。

其中,在传入函数之后,使用bool&引用,然后在case FSMIterm::EVENT1分支中,直接takeGamePath = !takeGamePath;对变量取反,这样执行两轮,第一轮就是走玩游戏分支,第二轮就是走吃午饭分支。

4、完整代码

#include <iostream>
#include <vector>
using namespace std;//FSM状态项
class  FSMIterm
{friend class FSM;//声明 FSM 类为 FSMIterm 类的朋友类//这意味着 FSM 类可以访问 FSMIterm 类的所有成员,包括私有和保护成员
private://状态对应的动作函数static void getup(){cout << "student is getting up!" <<endl;}static void gotoschool(){cout << "student is going to school!" <<endl;}static void havelunch(){cout << "student is having lunch!" <<endl;}static void dohomework(){cout << "student is doing homework!" <<endl;}static void sleeping(){cout << "student is sleeping!" <<endl;}static void playgame() {     cout << "student is playing game!" << endl;}public://枚举所有可能的状态enum State {GETUP = 1,GOTOSCHOOL = 2,PLAYGAME = 3, // 新增PLAYGAME状态HAVELUNCH = 4,DOHOMEWORK = 5,SLEEP = 6};//枚举所有可能触发状态转换的事件enum Events{EVENT1 = 1,EVENT2 = 2,EVENT3 = 3,EVENT4 = 4, // 新增EVENT4事件EVENT5 = 5, // 新增EVENT5事件EVENT6 = 6,EVENT7 = 7 };//初始化构造函数//构造函数接受四个参数:现态curState、条件event、动作(一个指向函数的指针action)、次态nextState//初始化列表分别用来初始化私有成员变量 _curState、_event、_action 和 _nextStateFSMIterm(State curState, Events event, void(*action)(), State nextState):_curState(curState), _event(event), _action(action), _nextState(nextState){}private://前下划线表示为私有成员变量State _curState;    //现态Events _event;      //条件void (*_action)();  //动作//*action是一个指向无参数无返回值函数的指针,用于执行与当前状态相关联的动作State _nextState;   //次态
};class  FSM
{
private://根据状态图初始化状态转移表void initFSMTable(){        //负责根据状态转移规则初始化一个状态转移表//每个 FSMIterm 实例都包含了状态转移的信息,如现态、触发事件、动作以及次态_fsmTable.push_back(new FSMIterm(FSMIterm::GETUP, FSMIterm::EVENT1, &FSMIterm::getup, FSMIterm::GOTOSCHOOL));_fsmTable.push_back(new FSMIterm(FSMIterm::GOTOSCHOOL, FSMIterm::EVENT2, &FSMIterm::gotoschool, FSMIterm::HAVELUNCH));_fsmTable.push_back(new FSMIterm(FSMIterm::GOTOSCHOOL, FSMIterm::EVENT4, &FSMIterm::gotoschool, FSMIterm::PLAYGAME));_fsmTable.push_back(new FSMIterm(FSMIterm::PLAYGAME, FSMIterm::EVENT5, &FSMIterm::playgame, FSMIterm::DOHOMEWORK)); _fsmTable.push_back(new FSMIterm(FSMIterm::HAVELUNCH, FSMIterm::EVENT3, &FSMIterm::havelunch, FSMIterm::DOHOMEWORK));_fsmTable.push_back(new FSMIterm(FSMIterm::DOHOMEWORK, FSMIterm::EVENT6, &FSMIterm::dohomework, FSMIterm::SLEEP));_fsmTable.push_back(new FSMIterm(FSMIterm::SLEEP, FSMIterm::EVENT7, &FSMIterm::sleeping, FSMIterm::GETUP));}vector<FSMIterm*> _fsmTable;    //定义私有变量_fsmTable,用来存储状态转移表public://初始化当前状态(_curState)为指定状态(默认为 GETUP)//立即调用 initFSMTable 方法初始化状态转移表FSM(FSMIterm::State curState = FSMIterm::GETUP):_curState(curState){initFSMTable();}//状态转移void transferState(FSMIterm::State nextState){_curState = nextState;   将传入的 nextState 赋值给_fsm 的现态 _curState}//当接收到一个事件(event)时,遍历状态转移表寻找匹配当前状态及事件的状态项//若找到匹配项,则设置标志 flag 为真,记录对应的 action 函数指针和 nextStatevoid handleEvent(FSMIterm::Events event){FSMIterm::State curState = _curState;   //现态void (*action)() = nullptr;             //动作FSMIterm::State nextState;              //次态bool flag = false;for (int i = 0; i < _fsmTable.size(); i++){if(event == _fsmTable[i]->_event && curState == _fsmTable[i]-> _curState){flag = true;action = _fsmTable[i]->_action;nextState = _fsmTable[i]->_nextState;break;}}//找到对应的状态项,调用对应的动作函数action,然后调用transferState函数更新状态机到新状态if(flag){if(action){action();}transferState(nextState);}}//获取状态名字,而不是返回数值static string getStateName(FSMIterm::State state) {switch (state) {case FSMIterm::GETUP: return "GETUP";case FSMIterm::GOTOSCHOOL: return "GOTOSCHOOL";case FSMIterm::PLAYGAME: return "PLAYGAME";case FSMIterm::HAVELUNCH: return "HAVELUNCH";case FSMIterm::DOHOMEWORK: return "DOHOMEWORK";case FSMIterm::SLEEP: return "SLEEP";default: return "UNKNOWN_STATE";}}//获取事件名字,而不是返回数值static string getEventName(FSMIterm::Events event) {switch (event) {case FSMIterm::EVENT1: return "EVENT1";case FSMIterm::EVENT2: return "EVENT2";case FSMIterm::EVENT3: return "EVENT3";case FSMIterm::EVENT4: return "EVENT4";case FSMIterm::EVENT5: return "EVENT5";default: return "UNKNOWN_EVENT";}}
//公共部分定义一个成员变量,表示有限状态机的当前状态,可供外部访问
public:FSMIterm::State _curState;      };//测试事件变换,用来改变传入事件的值,使其按照一定的顺序循环变化
void testEvent(FSMIterm::Events& event, bool& takeGamePath){switch (event){case FSMIterm::EVENT1:if(takeGamePath){event = FSMIterm::EVENT4;}else{event = FSMIterm::EVENT2;   //如果事件为event1,则将事件更改为event2}takeGamePath = !takeGamePath;break;case FSMIterm::EVENT2:event = FSMIterm::EVENT3;break;case FSMIterm::EVENT3:event = FSMIterm::EVENT6;break;case FSMIterm::EVENT4:event = FSMIterm::EVENT5; // 如果事件为EVENT4,则将事件更改为EVENT5break;case FSMIterm::EVENT5:event = FSMIterm::EVENT6; break;case FSMIterm::EVENT6:event = FSMIterm::EVENT7; break;case FSMIterm::EVENT7:event = FSMIterm::EVENT1; break;}
}int main(){FSM *fsm = new FSM();   //创建一个FSM类的实例,并将其指针存储在fsm中auto event = FSMIterm::EVENT1;int i = 0;bool takeGamePath = true;   //先走了玩游戏的路径while(i < 10){cout << "event " << FSM::getEventName(event) << " is coming ~" <<endl;fsm->handleEvent(event);cout << "fsm current state is " << fsm->_curState << endl;cout << "fsm current state is " << FSM::getStateName(fsm->_curState) << endl;testEvent(event, takeGamePath);i++;}cout << "event: " << static_cast<int>(event) << endl;cout << "curState: " << FSM::getStateName(fsm->_curState) << endl;return 0;}

5、运行结果

第三部分主要完成了多分支的状态机实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2979624.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

本地部署Docker容器可视化图形管理工具DockerUI并实现无公网IP远程访问——“cpolar内网穿透”

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

路由过滤与引入

1、实验拓扑 2、实验要求 1、按照图示配置 IP 地址&#xff0c;R1&#xff0c;R3&#xff0c;R4 上使用 1oopback口模拟业务网段 2、运行 oSPF&#xff0c;各自协议内部互通 3、R1 和 R2 运行 RIPv2,R2&#xff0c;R3和R4在 RIP 和 oSPF 间配置双向路由引入,要求除 R4 上的业务…

基于51单片机的温度、烟雾、防盗、GSM上报智能家居系统

基于51单片机的智能家居系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.DS18B20检测温度&#xff0c;MQ-2检测烟雾、ADC0832实现模数转换&#xff1b; 2.按键可以设置温度、烟雾浓度阈值&#x…

【Java--数据结构】提升你的编程段位:泛型入门指南,一看就会!

前言 泛型是一种编程概念&#xff0c;它允许我们编写可以适用于多种数据类型的代码。通过使用泛型&#xff0c;我们可以在编译时期将具体的数据类型作为参数传递给代码&#xff0c;从而实现代码的复用和灵活性。 在传统的编程中&#xff0c;我们通常需要为不同的数据类型编写不…

10 JavaScript学习:函数

函数的概念 JavaScript中的函数是一段可重复使用的代码块&#xff0c;它接受输入&#xff08;称为参数&#xff09;&#xff0c;执行特定的任务&#xff0c;并返回一个值。函数可以被调用&#xff08;或者说被执行&#xff09;&#xff0c;并且可以接受不同的输入来产生不同的…

提升效率!微信自动统计数据报表,轻松实现!

在数字化时代&#xff0c;提高工作效率是每个人的追求。下面就给大家分享一个能够自动统计微信号运营数据的神器——个微管理系统&#xff0c;让大家无需手动整理和计算&#xff0c;提高工作效率&#xff01; 1、好友统计报表 它分为通讯录好友统计、新增好友统计和删除好友统…

python创建线程和结束线程

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 python创建线程和结束线程 在 Python 中&#xff0c;线程是一种轻量级的执行单元&#xff…

Mysql 存在多条数据,按时间取最新的那一组数据

1、数据如下&#xff0c;获取每个用户最近的一次登录数据 思路1&#xff1a;order by group by 先根据UserIdLogInTime排序&#xff0c;再利用Group分组&#xff0c;即可得到每个User_Id的最新数据。 1 SELECT * FROM login_db l ORDER BY l.user_id, l.login_time DESC; 排…

【Linux】实现一个进度条

我们之前也学了gcc/vim/make和makefile&#xff0c;那么我们就用它们实现一个进度条。 在实现这个进度条之前&#xff0c;我们要先简单了解一下缓冲区和回车和换行的区别 缓冲区其实就是一块内存空间&#xff0c;我们先看这样一段代码 它的现象是先立马打印&#xff0c;三秒后程…

使用表格法插入公式和编号

如何将公式和编号优雅地插入到论文当中呢&#xff1f; 首先插入一个1行2列的表格 调整一下 输入公式方法一&#xff1a;感觉墨迹公式挺好用的&#xff0c;word自带的 输入公式方法二&#xff1a;图片转LATEX代码 这个方法更快 分享一个公式识别网站 图片识别得到LATEX代码&…

惠海H6212L DCDC同步降压芯片IC 24V30V36V48V转3.3V5V12V3A大电流方案 带线损

同步降压芯片IC 24V30V36V48V转3.3V5V12V3A大电流方案是一种电源管理方案&#xff0c;它采用同步整流技术&#xff0c;将较高的输入电压&#xff08;如24V、30V、36V、48V&#xff09;转换为较低的输出电压&#xff08;如3.3V、5V、12V&#xff09;&#xff0c;并提供高达3A的大…

代码随想录训练营Day 29|Python|Leetcode|● 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球

860.柠檬水找零 在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品&#xff0c;&#xff08;按账单 bills 支付的顺序&#xff09;一次购买一杯。 每位顾客只买一杯柠檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确…

openGauss学习笔记-269 openGauss性能调优-TPCC性能调优测试指导-数据库服务端及客户端绑核

文章目录 openGauss学习笔记-269 openGauss性能调优-TPCC性能调优测试指导-数据库服务端及客户端绑核269.1 安装openGauss数据库269.2 停止数据库269.3 使用gs_guc工具修改数据库端口、IP等269.4 使用gs_guc工具设置如下参数269.5 执行如下命令以绑核方式启动服务端数据库269.6…

【计算机毕业设计】药品销售系统产品功能介绍——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

手把手教数据结构与算法:有序线性表设计

问题描述 设计一个有序线性表类&#xff0c;要求完成初始化&#xff0c;插入和遍历功能&#xff0c;使得表内元素实现有序排列&#xff08;从小到大&#xff09;。同时实现合并功能&#xff0c;使得两个线性表能够合并为一个线性表&#xff08;可能存在重复元素&#xff09;。…

半导体存储器整理

半导体存储器用来存储大量的二值数据&#xff0c;它是计算机等大型数字系统中不可缺少的组成部分。按照集成度划分&#xff0c;半导体存储器属于大规模集成电路。 目前半导体存储器可以分为两大类&#xff1a; 只读存储器&#xff08;ROM&#xff0c;Read Only Memory&#xff…

ThingsBoard服务端使用RPC通过网关给设备发送消息

一、概述 1、发送服务器端网关RPC 二、案例&#xff1a; 1、建立设备与网关之间的通讯 2、查看设备和网关是否在线状态啊 3、通过 仪表盘&#xff0c;创建设备A的模拟RPC调用的窗口链接 4、在客户端的网关设备上订阅RPC网关的主题信息 5、通过服务端的窗口&#xff0c;发…

DaPy:实现数据分析与处理

DaPy&#xff1a;实现数据分析与处理 DaPy是一个用于数据分析和处理的Python库&#xff0c;它提供了一系列强大的工具和功能&#xff0c;使开发者能够高效地进行数据清洗、转换和分析。本文将深入解析DaPy库的特点、功能以及使用示例&#xff0c;帮助读者了解如何利用DaPy库处理…

Oracle使用内部包自定义创建表空间和用户

如果之前有类似的表空间,可以使用dbms自动生成对应的表空间和数据文件 select dbms_metadata.get_ddl(TABLESPACE,ts.tablespace_name) from dba_tablespaces ts; 可以使用类似的 SQL> set echo off SQL> spool /data/logs/create_tablespace.log SQL> select dbms…

【详细实现】v1.0 随机点名应用

1.引言 前面对这个应用的功能做了详细的分析&#xff08;长什么样、能干什么&#xff09;&#xff0c;以先这样对一个项目最开始的分析成为需求分析&#xff0c;需求分析之后就是设计阶段。 那么一般的项目&#xff0c;在设计阶段都需要干什么呢&#xff1f;在需求分析阶段确定…