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、测试结果


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

参考资料:

什么是状态机?-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个状态/扶额苦笑)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nginx第2篇-HTTPS配置教程

背景 我最近做个项目要上线&#xff0c;接口部署到服务器&#xff0c;总不能给别人个ip地址加端口吧&#xff0c;而且小程序上线要有接口不能是ip和http协议&#xff0c;必须是https协议。这里记录下使用Nginx配置HTTPS的过程&#xff0c;主要包含以下三部分。 申请域名SSL证…

远程预付费集抄管理系统

远程预付费集抄管理系统是一种用于能源(如水、电等)预付费管理的智能化系统&#xff0c;其核心在于提供远程集中抄表和费用管理服务。这种系统通过集成先进的远程监控技术和预付费管理功能&#xff0c;为用户提供了便捷的能源管理解决方案。下文将从核心功能、工作流程、优势特…

离世界模型更近一步!Meta开源OpenEQA,评估AI Agent情景理解能力

Yann LeCun 朝着 “世界模型” 又近了一步。 Meta最新的开源工作OpenEQA&#xff1a;从文字模型到世界模型&#xff0c;可以像人一样记忆、推理的新基准&#xff0c;AI理解物理空间又近了一步。 场景1: 假设你正准备离开家&#xff0c;但找不到你的工牌。 现在&#xff0c;…

5.2 iHRM人力资源 - 员工管理 - 使用文件导入导出员工

iHRM人力资源 - 员工管理 - 导入导出员工 文章目录 iHRM人力资源 - 员工管理 - 导入导出员工一、员工导出Excel二、员工导入Excel2.1 Excel导入组件封装2.2 下载导入模板2.3 Excel 导入功能 三、删除员工 一、员工导出Excel 这个地方涉及一个接口二进制流blob 就是下面这一大片…

使用嘉立创EDA打开JSON格式的PCB及原理图

一、将PCB和原理图放同一文件夹 并打包成.zip文件 二、打开嘉立创EDA并导入.zip文件 文件 -> 导入 -> 嘉立创EDA标准版/专业版 三、选择.zip文件并选择 “导入文件并提取库” 四、自定义工程路径 完成导入并转换为.eprj文件 五、视频教学 bilibili_使用立创EDA打开JSO…

香港科技大学广州|数据科学与分析学域硕博招生宣讲会—华东师范大学专场

时间&#xff1a;2024年4月25日&#xff08;星期四&#xff09;13:30 地点&#xff1a;华东师范大学普陀校区文附楼507 报名链接&#xff1a;https://www.wjx.top/vm/Q0cKTUI.aspx# 跨学科研究领域 *数据驱动的人工智能和机器学习 *统计学习和建模 工业和商业分析 *特定行业…

float实现文字环绕效果

实现效果如下&#xff1a; 一、问题分析 接到需求就是右侧显示图片&#xff0c;左侧显示一个标题和内容。第一时间没有想到其他的布局的好的实现方式&#xff0c;就想到了float布局。于是乎去查了下有关float的文档&#xff0c;float 是相当的好用。 float定义如下&#xf…

kibana源码编译

一、安装nodejs16.14.2及yarn &#xff08;一&#xff09;nodejs 1、下载 https://cdn.npmmirror.com/binaries/node/v16.14.2/node-v16.14.2-linux-x64.tar.gz2、解压 tar -zxf node-v16.14.2-linux-x64.tar.gz -C /app cd /app mv node-v16.14.2-linux-x64 node3、配置环…

在Linux系统中设定延迟任务

一、在系统中设定延迟任务要求如下&#xff1a; 要求&#xff1a; 在系统中建立easylee用户&#xff0c;设定其密码为easylee 延迟任务由root用户建立 要求在5小时后备份系统中的用户信息文件到/backup中 确保延迟任务是使用非交互模式建立 确保系统中只有root用户和easylee用户…

Matlab|基于改进遗传算法的配电网故障定位

目录 1 主要内容 2 部分代码 3 部分程序结果 4 下载链接 1 主要内容 该程序复现文章《基于改进遗传算法的配电网故障定位》&#xff0c;将改进的遗传算法应用于配电网故障定位中, 并引入分级处理思想, 利用配电网呈辐射状的特点, 首先把整个配电网划分为主干支路和若干独立…

2024年阿里云4核8G配置云服务器价格低性能高!

阿里云4核8G服务器租用优惠价格700元1年&#xff0c;配置为ECS通用算力型u1实例&#xff08;ecs.u1-c1m2.xlarge&#xff09;4核8G配置、1M到3M带宽可选、ESSD Entry系统盘20G到40G可选&#xff0c;CPU采用Intel(R) Xeon(R) Platinum处理器&#xff0c;阿里云优惠 aliyunfuwuqi…

【Python】高级进阶(专版提升3)

Python 1 程序结构1.1 模块 Module1.1.1 定义1.1.2 作用1.1.3 导入1.1.3.1 import1.1.3.2 from import 1.1.4 模块变量1.1.5 加载过程1.1.6 分类 1.2 包package1.2.1 定义1.2.2 作用1.2.3 导入1.1.3.1 import1.1.3.2 from import 2 异常处理Error2.1 异常2.2 处理 3 迭代3.1 可…

TinyEMU源码分析之访存处理

TinyEMU源码分析之访存处理 1 访存指令介绍2 指令译码3 地址转换3.1 VA与PA3.2 VA转PA 4 判断地址空间范围5 执行访存操作5.1 访问RAM内存5.2 访问非RAM&#xff08;设备&#xff09;内存 6 访存处理流程图 本文属于《 TinyEMU模拟器基础系列教程》之一&#xff0c;欢迎查看其…

数据结构排序算法

排序也称排序算法(SortAlgorithm)&#xff0c;排序是将一组数据&#xff0c;依指定的顺序进行排列的过程。 分类 内部排序【使用内存】 指将需要处理的所有数据都加载到内部存储器中进行排序插入排序 直接插入排序希尔排序 选择排序 简单选择排序堆排序 交换排序 冒泡排序快速…

两阶段提交进阶

两阶段提交之进阶 上一节我们讲了&#xff0c;两阶段提交逻辑上的表现&#xff0c;其实较为肤浅&#xff0c;并且偏向理论&#xff0c;可能大家都能看懂&#xff0c;但是如果放入实际的mysql应用中并联系事务和日志进行分析&#xff0c;又会怎么样呢&#xff1f; 这次就专门分…

Unity类银河恶魔城学习记录13-1 p142 Save system源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili FileDataHandler.cs using System; using System.IO; using UnityEngine; p…

软考133-上午题-【软件工程】-软件项目估算

一、COCOMO 估算模型 COCOMO 模型是一种精确的、易于使用的成本估算模型。 COCOMO 模型按其详细程度分为&#xff1a;基本 COCOMO 模型、中级 COCOMO 模型和详细 COCOMO 模型。 1&#xff09;基本 COCOMO 模型 基本 COCOMO 模型是一个静态单变量模型&#xff0c;用于对整个软…

内衣裤洗衣机如何选购?掌握这六个挑选技巧,轻松选购!

这两年内衣裤洗衣机可以称得上较火的小电器&#xff0c;小小的身躯却有大大的能力&#xff0c;一键可以同时启动洗、漂、脱三种全自动为一体化功能&#xff0c;在多功能和性能的提升上&#xff0c;还可以解放我们双手的同时将衣物给清洗干净&#xff0c;让越来越多小伙伴选择一…

node基础 第二篇

01 ffmpeg开源跨平台多媒体处理工具&#xff0c;处理音视频&#xff0c;剪辑&#xff0c;合并&#xff0c;转码等 FFmpeg 的主要功能和特性:1.格式转换:FFmpeg 可以将一个媒体文件从一种格式转换为另一种格式&#xff0c;支持几乎所有常见的音频和视频格式&#xff0c;包括 MP…

Node Version Manager(nvm):轻松管理 Node.js 版本的利器

文章目录 前言一、名词解释1、node.js是什么&#xff1f;2、nvm是什么&#xff1f; 二、安装1.在 Linux/macOS 上安装2.在 Windows 上安装 二、使用1.查看可安装的node版本2.安装node3. 查看已安装node4.切换node版本5.其它 总结 前言 Node.js 是现代 Web 开发中不可或缺的一部…