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

news2024/11/15 15:45:22

目录

一、基础知识

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 和 _nextState
     FSMIterm(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 函数指针和 nextState
    void 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,则将事件更改为event2
        break;
    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 和 _nextState
     FSMIterm(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 函数指针和 nextState
    void 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,则将事件更改为event2
        break;
    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,则将事件更改为EVENT5
        break;
    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 和 _nextState
     FSMIterm(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 函数指针和 nextState
    void 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,则将事件更改为EVENT5
        break;
    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://www.coloradmin.cn/o/1615826.html

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

相关文章

串联超前及对应matlab实现

串联超前校正它的本质是利用相角超前的特性提高系统的相角裕度。传递函数为&#xff1a;下面将以一个实际的例子&#xff0c;使用matlab脚本&#xff0c;实现其校正后的相位裕度≥60。

mysql基础2——字段类型

整数类型 需要考虑存储空间和可靠性的平衡 浮点类型 浮点数类型不精准 将十进制数转换为二进制数存储 浮点数类型&#xff1a;float double real(默认是&#xff0c;double ) 如果需要将real设定为float &#xff0c;那么通过以下语句实现 set sql_mode "real_as…

go语言实现心跳机制样例

1、服务端代码&#xff1a; package mainimport ("fmt""net" )func handleClient(conn net.Conn) {defer conn.Close()fmt.Println("Client connected:", conn.RemoteAddr())// 读取客户端的数据buffer : make([]byte, 1024)for {n, err : conn…

AOC/AGON亮相2024上海国际酒店及商业空间博览会,共话电竞酒店产业新趋势!

摘要&#xff1a;行业头部品牌共聚上海&#xff0c;共话电竞酒店市场未来&#xff01; 春景熙熙&#xff0c;相逢自有时&#xff0c;3月26日-29日&#xff0c;2024上海国际酒店及商业空间博览会以及第二届全国电竞酒店投资交流论坛在上海新国际博览中心圆满帷幕。连续五年蝉联…

腾讯云轻量2核4G5M服务器优惠价格165元1年,2024年多配置报价单

腾讯云轻量2核4G5M服务器优惠价格165元1年。腾讯云服务器价格表2024年最新价格&#xff0c;轻量2核2G3M服务器61元一年、2核2G4M服务器99元1年&#xff0c;三年560元、2核4G5M服务器165元一年、3年900元、轻量4核8M12M服务器646元15个月、4核16G10M配置32元1个月、8核32G配置11…

STM32F407,429参考手册(中文)

发布一个适用STM32F405XX、STM32F407XX、STM32F415XX、STM32F417XX、STM32F427XX、STM32F437XX的中文数据手册&#xff0c;具体内容见下图&#xff1a; 点击下载&#xff08;提取码&#xff1a;spnn&#xff09; 链接: https://pan.baidu.com/s/1zqjKFdSV8PnHAHWLYPGyUA 提取码…

你准备好迎接它了吗?英伟达CEO黄仁勋预言:人形机器人将成为未来主流

在近日举行的“CadenceLIVE 硅谷 2024”大会上&#xff0c;英伟达公司的首席执行官黄仁勋与大会主办方Cadence公司的CEO进行了一场富有深度的对话。在这场引人瞩目的交流中&#xff0c;黄仁勋大胆预测&#xff0c;未来人形机器人将成为主流&#xff0c;引领科技发展的新潮流。 …

three.js(3):添加three. js坐标轴、光源和阴影效果

1 实现步骤 要实现阴影效果同样需要几个重要的概念。 我们首先研究一下日常生活中是如何产生阴影效果的。 需要有光。需要一个物体&#xff0c;比如苹果、狗等。需要一个接受投影的元素&#xff0c;比如地面、桌面等。 在 Three.js 中要产生阴影效果其实和现实世界的原理差…

ollama在windows系统上安装总结以及注意事项

ollama官网 https://ollama.com/ 直接点击下载&#xff0c;会根据你的系统自动下载相应的版本。 下载完之后&#xff0c;直接点击安装&#xff0c;默认安装到c盘。 安装完之后就可以在命令窗口测试一下。 ollama的常用命令如下&#xff1a; ollama serve 启动ollama o…

认识产品经理

一、合格的产品经理 1、什么是产品 解决某个问题的东西&#xff0c;称为产品 键盘可以打字&#xff0c;想喝水了可以用水壶&#xff0c;在超市想找一款扫把会有导购员服务 产品有颜色、大小等等区别&#xff0c;也有有形和无形的区别 2、什么是产品经理 想清楚怎么设计产品…

【氮化镓】GaN HEMT SEEs效应影响因素和机制

研究背景&#xff1a;AlGaN/GaN HEMT因其在高电压、高温和高频率下的操作能力而受到关注&#xff0c;尤其在航空航天和汽车应用中&#xff0c;其辐射响应变得尤为重要。重离子辐射可能导致绝缘体失效&#xff0c;即单事件效应&#xff08;SEEs&#xff09;引起的栅介质击穿。 …

订单分红与异业联盟:共筑电商新生态

大家好&#xff0c;我是微三云周丽&#xff0c;今天给大家分析当下市场比较火爆的商业模式&#xff01; 小编今天跟大伙们分享什么是订单分红与异业联盟&#xff1f; 随着互联网的深入发展和商业模式的不断创新&#xff0c;电子商务行业正迎来qian所未有的发展机遇。近年来&a…

java接口自动化测试

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Linux cmake 初窥【1】

1.开发背景 linux 下编译程序需要用到对应的 Makefile&#xff0c;用于编译应用程序&#xff0c;但是 Makefile 的语法过于繁杂&#xff0c;甚至有些反人类&#xff0c;所以这里引用了cmake&#xff0c;cmake 其中一个主要功能就是用于生成 Makefile&#xff0c;cmake 的语法更…

windows ubuntu 子系统:肿瘤全外篇,bam质控

各个环节的质控&#xff0c; raw和clean都要质控&#xff0c; 比对的各环节的bam文件都要质控&#xff0c; 使用qulima对wes的比对bam文件总结测序深度及覆盖率。 samtools flagstat L1_recalibrated_reads.bam 该命令将输出 BAM 文件的一些统计信息&#xff0c;包括总读取数、…

双周总结#008 - AIGC

本周参与了公司同事对 AIGC 的分享会&#xff0c;分享了 AIGC 在实际项目中的实践经验&#xff0c;以及如何进行 AIGC 的落地。内容分几项内容&#xff1a; 什么是 AIGCAIGC 能做什么AIGC 工具 以年终总结为例&#xff0c;分享了哪些过程应用了 AIGC&#xff0c;以及 AIGC 落地…

使用QQ邮箱进行登录验证

使用场景不多说&#xff0c;接下来直接看实现~ 登录到QQ邮箱&#xff0c;进入设置 打开IMAP/SMTP服务&#xff0c;记得把授权码记录下来&#xff0c;后面配置文件中需要用到 新建application的配置文件 spring:mail:# 指定邮件服务器地址host: smtp.qq.comusername: 你自己的q…

什么是SSRF攻击?该如何防御SSRF攻击?

随着网络安全形式日益严峻&#xff0c;各式各样的攻击频繁发生。当前&#xff0c;应用程序为了给用户提供更多更方便的功能&#xff0c;从另一个URL获取数据的场景越来越多&#xff0c;因此出现了一种安全漏洞攻击-SSRF。并且&#xff0c;由于云服务和体系结构的复杂性&#xf…

自动化测试的三种测试报告模板

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

《HCIP-openEuler实验指导手册》1.3Apache动态功能模块加载卸载练习

1.3.1 配置思路 mod_status 模块可以帮助管理员通过web界面监控Apache运行状态&#xff0c;通过LoadModule指令加载该模块&#xff0c;再配置相关权限&#xff0c;并开启ExtendedStatus后&#xff0c;即可使用该模块。 1.3.2 配置步骤 检查mod_status模块状态&#xff08;使…