Qt 状态机框架:The State Machine Framework (二)

news2025/1/11 14:50:01

传送门:
Qt 状态机框架:The State Machine Framework (一)
Qt 状态机框架:The State Machine Framework (二)

1、利用并行态避免态的组合爆炸

假设您想在单个状态机中对汽车的一组互斥属性进行建模。假设我们感兴趣的属性是干净与肮脏,以及移动与不移动。需要四个相互排斥的状态和八个转换才能表示所有可能的组合并在它们之间自由移动。
在这里插入图片描述
如果我们增加第三个属性(比如红色与蓝色),状态的总数将翻一番,达到8个;如果我们增加第四个属性(比如,封闭与可转换),状态的总数将再次翻倍,达到16。

使用并行状态,状态和转换的总数随着我们添加更多属性而线性增长,而不是指数增长。此外,状态可以添加到并行状态或从并行状态中删除,而不会影响其任何兄弟状态。

在这里插入图片描述
【codes】:


#include <QApplication>
#include <QWidget>

#include <QState>
#include <QStateMachine>
#include <QPushButton>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;

    QPushButton* controlButton_0 = new QPushButton(QStringLiteral("雨刷"),&w);
    QPushButton* controlButton_1 = new QPushButton(QStringLiteral("刹车"),&w);
    QLabel* label_0 = new QLabel(&w);
    QLabel* label_1 = new QLabel(&w);
    controlButton_0->setGeometry(0,0,200,60);
    controlButton_1->setGeometry(0,70,200,60);
    label_0->setGeometry(200,0,200,60);
    label_1->setGeometry(200,70,200,60);

    QStateMachine machine;

    QState* s1  = new QState(QState::ParallelStates);
    {
        QState* s11 = new QState(s1);
        {
            QState* s11_clean = new QState(s11);
            QState* s11_dirty = new QState(s11);

            s11_clean->assignProperty(label_0,"text","clean");
            s11_dirty->assignProperty(label_0,"text","dirty");

            s11_clean->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_dirty);
            s11_dirty->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_clean);

            s11->setInitialState(s11_clean);
        }


        QState* s12 = new QState(s1);
        {
            QState* s12_notMoving = new QState(s12);
            QState* s12_moving = new QState(s12);

            s12_notMoving->assignProperty(label_1,"text","not moving");
            s12_moving->assignProperty(label_1,"text","moving");

            s12_notMoving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_moving);
            s12_moving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_notMoving);

            s12->setInitialState(s12_notMoving);
        }
    }

    machine.addState(s1);
    machine.setInitialState(s1);
    machine.start();

    w.show();
    return a.exec();
}

【运行效果】:
假设汽车已经启动。开/关雨刷和是否踩住刹车是两个并行的,互不干扰的状态。
在这里插入图片描述

2、状态结束状态检测

在示例1的基础上,我们对汽车的 【启动->运行->结束】这一完整的过程进行简单的状态模拟。
在实际生活中,我们踩住刹车同时按住【一键启动】按钮,汽车点火,进入s1启动状态。当汽车熄火,进入s2状态。当汽车驻车和锁闭车门后,进入s3状态。生活中的car 启停和运行过程中的操作状态切换模型,远比我们下图中所绘制的要复杂的多。本例对模型进行适当的简化。

在启动状态下,我们可以对汽车进行一些操作,有些操作状态时串行的,也有一些操作状态是并行的。 并行的状态譬如我们在示例1或者如下状态表所示的 开关雨刷与控制汽车的行驶和停止;串行的状态,譬如我们汽车点火和熄火两个状态,他不能同时存在,类似这样的状态我们认为是串行的。不过,串行的状态还可以分为循环状态和 可中止状态。 譬如我们打开车门,在主驾驶座位上可以循环重复【打火启动】->【停车熄火】这两个状态的切换;但是如果我们已经下车,在【熄火】状态下,我们可以将车门重【未锁车】状态切换到【锁车】终止状态。

当进入到终止状态(QFinalState)后,会发送 finished信号
在这里插入图片描述

【codes】:


#include <QApplication>
#include <QWidget>

#include <QState>
#include <QStateMachine>
#include <QFinalState>

#include <QPushButton>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;


    QPushButton* fire_button = new QPushButton(QStringLiteral("点火/熄火"),&w);
    QPushButton* controlButton_0 = new QPushButton(QStringLiteral("雨刷"),&w);
    QPushButton* controlButton_1 = new QPushButton(QStringLiteral("刹车"),&w);
    QPushButton* lock_button = new QPushButton(QStringLiteral("锁车"),&w);

    QLabel* label_0 = new QLabel(&w);
    QLabel* label_1 = new QLabel(&w);


    fire_button->setGeometry(0,300,200,60);
    lock_button->setGeometry(0,400,200,60);
    controlButton_0->setGeometry(0,0,200,60);
    controlButton_1->setGeometry(0,70,200,60);
    label_0->setGeometry(200,0,200,60);
    label_1->setGeometry(200,70,200,60);

    QStateMachine machine;

    QState* car = new QState();

    QState* s1  = new QState(QState::ParallelStates,car);
    {
        QState* s11 = new QState(s1);
        {
            QState* s11_clean = new QState(s11);
            QState* s11_dirty = new QState(s11);

            s11_clean->assignProperty(label_0,"text","clean");
            s11_dirty->assignProperty(label_0,"text","dirty");

            s11_clean->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_dirty);
            s11_dirty->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_clean);

            s11->setInitialState(s11_clean);
        }


        QState* s12 = new QState(s1);
        {
            QState* s12_notMoving = new QState(s12);
            QState* s12_moving = new QState(s12);

            s12_notMoving->assignProperty(label_1,"text","not moving");
            s12_moving->assignProperty(label_1,"text","moving");

            s12_notMoving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_moving);
            s12_moving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_notMoving);

            s12->setInitialState(s12_notMoving);
        }
    }


    QState* s2 = new QState(car);
    {
        QState* s21 = new QState(s2);
        QFinalState* finalState = new QFinalState(s2);

        s21->addTransition(lock_button,SIGNAL(clicked(bool)),finalState);

        s2->setInitialState(s21);

    }

    s1->addTransition(fire_button,SIGNAL(clicked(bool)),s2);
    s2->assignProperty(label_0,"text",QStringLiteral("已熄火"));
    s2->assignProperty(label_1,"text",u8"已熄火");

    QState* s3 = new QState(car);
    s2->addTransition(s2, SIGNAL(finished()), s3);
    s3->assignProperty(label_0,"text",u8"已熄火锁车");
    s3->assignProperty(label_1,"text",u8"已熄火锁车");

    car->setInitialState(s1);

    machine.addState(car);
    machine.setInitialState(car);
    machine.start();


    w.show();
    return a.exec();
}

【运行效果】:
在这里插入图片描述

解释一下代码演示的流程:
程序启动,状态机启动,进入到默认的car.s1状态(点火状态),s1是一个并行的子状态机,包括雨刷和刹车这两个对象的状态控制,所以如图演示的,我们点击雨刷和刹车按钮,可以自由切换两个对象的状态,互补干扰;
当我们点击【点火/熄火】按钮,car的状态切换到car.s2(熄火状态),s2是一个串行的状态机,默认状态是[熄火/未锁车],当我们点击【锁车】按钮时,car的状态切换到car.s2.finalState s2状态结束,发送一个finished的信号,car切换到s3状态。

至此,一个相对复杂一点儿的状态机模型我们演示完成。当然实际的应用场景往往比这个要复杂的更多。但是我们只要保证一下几个步骤/原则,无论多复杂的状态转换模型,都可以轻而易举的完成。

* 1、状态模块划分(先父后子)
* 2、区分并行or串行
* 3、状态是否要中断
* 4、是否有结束状态
* 5、父状态需添加默认子状态
* 6、状态机需设置默认顶层状态

3、无目标状态的状态转移

有时候,我们会有这样的场景,当前状态(假设是s1)在接收到某个信号或者事件之后,转移动作被触发。我们可以捕获转移 对象(QAbstractTransition或者其子对象)发出的 triggered 信号,并在槽函数中做一些业务处理。当结束业务处理逻辑时,有趣的是,当前状态仍是s1。这样,一个动作就可以反复的react,就很适合重复性的状态转换了。

【codes】:

#include <QApplication>
#include <QState>
#include <QStateMachine>
#include <QSignalTransition>

#include <QSignalMapper>
#include <QPushButton>
#include <QMessageBox>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;

    QStateMachine machine;
    QState* s1 = new QState();
    machine.addState(s1);

    QPushButton* button = new QPushButton(QStringLiteral("触发"),&w);
    button->setGeometry(150,150,100,60);
    QSignalTransition* trans = new QSignalTransition(button,SIGNAL(clicked(bool)));
    s1->addTransition(trans);  // 无目标状态转移

    QMessageBox box;
    box.setText(QStringLiteral("The button was clicked, carry on."));
    QObject::connect(trans,SIGNAL(triggered()),&box,SLOT(exec()));


    machine.setInitialState(s1);
    machine.start();

     w.show();

    return a.exec();
}

【运行效果】:
在这里插入图片描述

4、事件、转换和防护

QStateMachine运行自己的事件循环。对于信号转换(QSignalTransition对象),QStateMachine在截获相应信号时会自动向自身发布QStateMachine::SignalEvent;类似地,对于QObject事件转换(QEventTransition对象),会发布一个QStateMachine::WrappedEvent
您可以使用QStateMachine::postEvent( )将自己的事件发布到状态机。
将自定义事件发布到状态机时,通常还会有一个或多个自定义转换,这些转换可以由该类型的事件触发。要创建这样的转换,您可以将QAbstractTransition子类化并重新实现QAbstract transition::eventTest( ),在其中检查事件是否与您的事件类型(以及可选的其他条件,例如事件对象的属性)匹配。

在下面的例子里,我们定义了自己的自定义事件类型StringEvent,用于将字符串发布到状态机:
在这里插入图片描述
【codes】:


```cpp
#include <QApplication>
#include <QState>
#include <QStateMachine>
#include <QSignalTransition>
#include <QFinalState>
#include <QDebug>

#include <QSignalMapper>
#include <QPushButton>
#include <QMessageBox>
#include <QLineEdit>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>

struct StringEvent : public QEvent
{
    StringEvent(const QString& val)
        : QEvent(QEvent::Type(QEvent::User+1)),value(val)
    {

    }
    QString value;
};

struct StringTransition : public QAbstractTransition
{
    Q_OBJECT
public:
    StringTransition(const QString& value) : m_value(value)
    {}

protected:
    bool eventTest(QEvent *e){
        if (e->type() != QEvent::Type(QEvent::User+1)) // StringEvent
                      return false;
                  StringEvent *se = static_cast<StringEvent*>(e);
                  return (m_value == se->value);
    }

    void onTransition(QEvent* e) override { Q_UNUSED(e);}
private:
    QString m_value;
};


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;

    QLineEdit* s1_ldt = new QLineEdit;
    QLineEdit* s2_ldt = new QLineEdit;
    QLabel* s_lab = new QLabel;

    QHBoxLayout* s1_hlayout = new QHBoxLayout;
    s1_hlayout->addWidget(new QLabel("s1:"));
    s1_hlayout->addWidget(s1_ldt);

    QHBoxLayout* s2_hlayout = new QHBoxLayout;
    s2_hlayout->addWidget(new QLabel("s2:"));
    s2_hlayout->addWidget(s2_ldt);


    QVBoxLayout* vlayout = new QVBoxLayout;
    vlayout->addLayout(s1_hlayout);
    vlayout->addLayout(s2_hlayout);
    vlayout->addWidget(s_lab);
    w.setLayout(vlayout);


    QStateMachine machine;
    QState* s1 = new QState();
    QState* s2 = new QState();
    QFinalState* done = new QFinalState();

    s1->assignProperty(s_lab,"text","state in s1");
    s2->assignProperty(s_lab,"text","state in s2");

    QObject::connect(s1_ldt,&QLineEdit::textChanged,[&](){
       machine.postEvent( new StringEvent(s1_ldt->text().trimmed()));
    });

    QObject::connect(s2_ldt,&QLineEdit::textChanged,[&](){
       machine.postEvent(new StringEvent(s2_ldt->text().trimmed()));
    });

    QObject::connect(done,&QFinalState::entered,[&](){
        s_lab->setText("state in final state.");
        qDebug() << "state in final state.";
    });

    QObject::connect(done,&QFinalState::exited,[&](){
        s_lab->setText("final state has exited.");
    });



    // s1->s2->done
    StringTransition* t1 = new StringTransition("hello");
    t1->setTargetState(s2);
    s1->addTransition(t1);

    StringTransition* t2 =new StringTransition("world");
    t2->setTargetState(done);
    s2->addTransition(t2);



    machine.addState(s1);
    machine.addState(s2);
    machine.addState(done);

    machine.setInitialState(s1);
    machine.start();

    w.show();
    return a.exec();
}

#include "main.moc"

【运行效果】:

在这里插入图片描述


[参考文档]:
1、https://blog.csdn.net/qq_35629971/article/details/125988152
2、https://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf
3、Qt Assistant 5.14.2

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

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

相关文章

Linux 系统之部署 h5ai 目录列表程序

一、h5ai 介绍 1.1&#xff09;h5ai 简介 h5ai 是用于 HTTP Web 服务器的现代文件索引器&#xff0c;专注于您的文件。目录以吸引人的方式显示&#xff0c;浏览它们通过不同的视图、面包屑和树概述得到增强。最初 h5ai 是 HTML5 Apache Index 的首字母缩写&#xff0c;但现在它…

异步Merkle Tree

1. 引言 前序博客&#xff1a; 利用多核的Rust快速Merkle tree Anoushk Kharangate 2023年论文《Asynchronous Merkle Trees》&#xff0c;其对Merkle tree数据结构进行修改&#xff0c;使得可跨多线程异步计算。 开源代码实现见&#xff1a; https://github.com/anoushk1…

2024华数杯国际数学建模B题思路+代码+模型+论文

2024华数杯国际数学建模B题思路代码模型论文&#xff1a;1.17上午第一时间更新&#xff0c;详细内容见文末名片 问题B&#xff1a;光伏电 背景 中国的电力构成包括传统的能源发电&#xff08;如煤炭、石油和天然气&#xff09;、可再生能源发电 &#xff08;如水力发电、风能…

gin+gorm增删改查目录框架

从网上找资料,发现,很多都是直接的结构 路由&#xff0c;后端的controller层&#xff0c;还有model层&#xff0c;都是放在了同一个main.go文件中&#xff0c;如果写项目的话&#xff0c;还得自己去拆文件&#xff0c;拆代码&#xff0c;经过查询和自己总结&#xff0c;下面放…

ssh免密登录 ssh公钥分发 ssh密钥生成

在连接服务器时&#xff0c;我们会被要求输入用户名对应的密码&#xff0c;如下&#x1f447;&#xff1a; 如果我们要登录的服务器是常用服务器&#xff0c;那么每次登录输入密码就会比较麻烦。那么如何免密登录呢&#xff1f;那就需要使用到rsa公私钥认证了。 生成rsa密钥…

vue 指定区域可拖拽的限定拖拽区域的div(如仅弹窗标题可拖拽的弹窗)

<template><div class"container" ref"container"><div class"drag-box" v-drag><div class"win_head">弹窗标题</div><div class"win_content">弹窗内容</div></div><…

vivado导出时序报告为excel文件的方法

1、打开implementation下的report timing summary 2、选择要看的时钟右键点击report_timing 3、在新打开的timing窗口中&#xff0c;选择setup或者hold&#xff0c;选中一条路径右键&#xff0c;点击export to spreadsheet&#xff0c;此时就可以存为table.xlsx文件

【MySQL】权限控制

DCL-权限控制 查询权限 show grants for 用户名主机名;授予权限 grant 权限列表 on 数据库名.表名 to 用户名主机名;grant all on test.* to user%; %是通配符&#xff0c;表示任意主机。撤销权限 revoke 权限列表 on 数据库名.表名 from 用户名主机名;revoke all on test.*…

旅游平台day02

1. 用户注册 概述&#xff1a; 常见的注册方式&#xff1a;邮箱注册、手机号注册、昵称注册、或者以上几种同时支持 本项目仅仅支持手机号注册 需求&#xff1a; 项目启动后&#xff0c;访问regist.html进入注册页面 手机号校验 前后台都需要对手机号进行校验 前端校验&am…

HashMap学习和线程安全的HashMap

HashMap的底层数据结构&#xff1f; HashMap在JDK1.8里面的Node数组加链表加红黑树&#xff0c;当链表长度大于8且数组长度大于64&#xff0c;链表转化为红黑树。当红黑树节点数小于6&#xff0c;红黑树转化为链表。在JDK1.7中是数组加链表。 为什么要用红黑树&#xff1f; 当…

react 第一个项目

sudo npx create-react-app reactdemo01 npx node.js工具 create-react-app 核心包&#xff08;固定写法&#xff09;用于创建react项目 后跟项目名层 启动一个新的 React 项目 – React 中文文档 //项目的根组件 //App -> index.js ->/Users/king/Documents/react…

芯片新闻-Global Semiconductor Sales Increase 5.3% Year-to-Year in November

11 月标志着一年多以来市场同比增长的第一个月&#xff1b;全球芯片销量环比增长2.9% 华盛顿——一月。 2024 年 12 月 9 日——半导体行业协会 (SIA) 今天宣布&#xff0c;2023 年 11 月全球半导体行业销售额总计 480 亿美元&#xff0c;比 2022 年 11 月的 456 亿美元总额增…

rust跟我学七:获取外网IP地址

图为RUST吉祥物 大家好,我是get_local_info作者带剑书生,这里用一篇文章讲解get_local_info是怎么获取到本机的外网IP地址。 首先,先要了解get_local_info是什么? get_local_info是一个获取linux系统信息的rust三方库,并提供一些常用功能,目前版本0.2.4。详细介绍地址:[…

FPGA 原理图引脚标识细节

BGA引脚表示 1.1 FPGA此引脚要正确和清晰&#xff0c;会在“Package Pin”中用到次物理接口 1.2, MCU 只用管对应的GPIO逻辑接口就可以了 1.3&#xff0c;引脚名标识出bank, PS/PL, signal/differential 标识Bank电平 标识出对应Bank的电平&#xff0c;在电路设计中可以清晰…

C#调用Newtonsoft.Json将bool序列化为int

使用Newtonsoft.Json将数据对象序列化为Json字符串时&#xff0c;如果有布尔类型的属性值时&#xff0c;一般会将bool类型序列化为字符串&#xff0c;true值序列化为true&#xff0c;false值序列化为false。如下面的类型序列化后的结果如下&#xff1a; public class UserInfo…

量化研究员!你应该如何写一手好代码

即使是Quant Researcher&#xff0c; 写一手高质量的代码也是非常重要的。再好的思路&#xff0c;如果不能正确地实现&#xff0c;都是没有意义的。 写一手高质量的代码的意义&#xff0c;对Quant developer来讲就更是自不待言了。这篇笔记就介绍一些python best practice。 始…

npm install 无反应 npm run serve 无反应

说明情况&#xff1a;其实最开始我就是发现我跟着黑马的苍穹外卖的前端day2的环境搭建做的时候&#xff0c;到这一步出现了问题&#xff0c;无论我怎么 npm install 和 npm run serve 都没有像黑马一样有很多东西进行加载&#xff0c;因此我换了一种方法 1.在这个文件夹下cmd …

CSS 设置背景图片

文章目录 设置背景颜色设置背景图片背景图片偏移量计算原点背景图片尺寸设置背景图片位置设置背景图片重复方式设置背景范围设置背景图片是否跟随元素移动测试背景图片 本文概念部分参考&#xff1a;CSS背景background设置 设置背景颜色 background-color 设置背景颜色 设置…

【面试合集】说说你对 linux 用户管理的理解?相关的命令有哪些?

面试官&#xff1a;说说你对 linux 用户管理的理解&#xff1f;相关的命令有哪些&#xff1f; 一、是什么 Linux是一个多用户的系统&#xff0c;允许使用者在系统上通过规划不同类型、不同层级的用户&#xff0c;并公平地分配系统资源与工作环境 而与 Windows 系统最大的不同…

表的增删改查 进阶(一)

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;MySql&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.数据库约束 约束类型 NOT NUll 约束 UNIQUE 约束 …