首页 > 编程知识 正文

有限状态机c语言实现,c语言状态机框架

时间:2023-05-05 23:09:43 阅读:190434 作者:4129

动机

状态机模式(状态机)是嵌入式开发中最重要、最核心的设计模式之一,是否熟悉状态机模式在很大程度上直接决定着嵌入式工程师的代码控制能力,这一点也不过分。 在嵌入式开发中,几乎80%以上的程序都有状态模式(状态机)的影子。 在思路清晰且高效的程序中,一定会浮现出状态模式(状态机)的样子。 但是很多嵌入式开发人员只是掌握了基础的状态机编程,状态机编程提高程序的可维护性和可扩展性无法得到深入的理解。

在这里,通过简单易懂的MP3播放器的案例,向大家展示自己独家总结的状态机的6个阶段的做法,帮助正在啃状态机这块硬骨头的大家。 相信在您深入掌握状态机编程之后,您优雅优美的代码会让同事朋友们眼前一亮,啧啧称赞。

生活中的状态模式(状态机),几乎在所有复杂的项目中,都充满着各种事物状态的变化。 这是因为我们所处的物理世界本来就是一个动态多变的环境,自然我们开发的程序也要根据事物不同时刻不同场景的状态,不断调整自己的行为属性。

例如在电影《分裂》中,xhdy饰演的主人公jzdm患有精神分裂,具有多重人格疾病。 他在精神科医生Dr. Fletcher身上有23个人格,被诊断出随时可以转换境遇,成为聪明的律师,懦弱结实的毛衣总是自杀,遭遇触发愤怒杀人的世界,这种人格的转换速度

想象一下,如果我们想通过程序实现这样的作用,就需要良好的状态变化设计,以便在主人公快速切换状态的情况下,能够保证与之匹配的精神状态和行为。

序列场景:设计了简单的MP3播放器,要求两个按钮(播放/暂停、停止)分别控制MP3的播放/停止功能。

下表所示的:

按钮功能[play/pause]播放/暂停[stop]停止状态转移图状态模式的设计开发中,通常使用状态转移图进行多个状态的分析。 本案例的MP3播放器,状态转移图如下图所示为:

图示虽然简单,但非常方便。 按下各按钮,MP3播放器的状态一目了然,可以根据状态转移图着手编写程序。

首先,我们来看一下简单状态机,它是状态机(状态机)的入门级实现。 其实通过大量的switch/case和if/else,可以在很多项目中看到很相似的代码:

#include stdio.hvoid stopPlayer (; void pausePlayer (; void resumePlayer (; void startPlayer (; //键的动作类型typedef enum { EV_STOP,EV_PLAY_PAUSE}EventCode; //MP3的状态enum{ ST_IDLE,ST_PLAY,ST_PAUSE}; //MP3当前状态char state; //MP3状态初始化void init () { state=ST_IDLE; //状态机处理MP3的过程的变化voidonevent(eventcodeEC ) switch (state ) caseST_idle3360if ) ev_play_pause==EC ) state caseST_play:if(ev_stop==EC ) stopPlayer ); elseif(ev_play_pause==EC ) pausePlayer ); 黑; casest _ pause : if (ev _ stop==EC ) stopPlayer ); elseif(ev_play_pause==EC ) resumePlayer ); 黑; default: break; }}void stopPlayer () { state=ST_IDLE; printf (停止播放音乐(n ); }void pausePlayer () { state=ST_PAUSE; printf (音乐暂停(n ); }void resumePlayer () { state=ST_PLAY; printf (重新开始播放音乐(n ) ); }void startPlayer () { state=ST_PLAY; printf ()开始播放音乐(n ); //主程序是MP3的再生控制void main () { init ); ovent(ev_play_pause; 播放//onevent(ev_play_pause )//暂停//onevent(ev_play_pause ); 继续再生//onevent(ev_stop ); //停止}代码已经在C在线工具|专用工具上运行了验证,读者也可以自己验证。 执行结果如下。

在开始播放音乐、暂停播放音乐、重新开始播放音乐、停止播放音乐的代码安装中,主要在onEvent函数中,以MP3的当前状态为判断条件进行适当的分支变更,根据状态迁移图简单地实现了功能。

/p>

但是我们观察onEvent函数,不难发现其中有大量的swith...case这样的判断(if...else也是一样).对于MP3播放器这样简单的例子,这样的代码还是不难阅读和维护的。但是当状态和事件增加后,onEvent函数就会变得非常庞大,这是因为该函数的代码行数与状态和事件数量的乘积成正比,直接导致代码行数爆炸增长,代码会越发变得难以阅读和维护。

其次,程序的扩展性非常差,无论是我们新增一种状态,还是新增一种按键动作,onEvent函数都要大改特改,极难保障程序的稳定性。

解决方案

核心思路:我们可以利用C语言的多态特性来分解复杂的条件分支(关于c语言多态的实现,请查看c语言面向对象基础)。这样一来可以就避免大量的swith...case和 if...else等条件分支语句,提高程序的可维护性和可扩展性。

下面我将使用独家总结的六步法,帮助大家轻松掌握状态模式(状态机)的编程诀窍。

#include <stdio.h>/***********************************************1、定义状态接口,以MP3的状态接口为例,每种状态下都可能发生两种按键动作。************************************************/typedef struct State{ void (* stop)(); void (* palyOrPause)();}State;/***********************************************2、定义系统当前状态指针,保存系统的当前状态************************************************/State * pCurrentState;/***********************************************3、定义具体状态,根据状态迁移图来实现具体功能和状态切换。************************************************/void ignore();void startPlay();void stopPlay();void pausePlay();void resumePlay();//空闲状态时,stop键操作无效,play/pause会开始播放音乐State IDLE = { ignore, startPlay};//播放状态时,stop键会停止播放音乐,play/pause会暂停播放音乐State PLAY = { stopPlay, pausePlay};//暂停状态时,stop键会停止播放音乐,play/pause会恢复播放音乐State PAUSE = { stopPlay, resumePlay};void ignore(){ //空函数,不进行操作}void startPlay(){ //实现具体功能 printf("开始播放音乐n"); //进入播放状态 pCurrentState = &PLAY;}void stopPlay(){ //实现具体功能 printf("停止播放音乐n"); //进入空闲状态 pCurrentState = &IDLE;}void pausePlay(){ //实现具体功能 printf("暂停播放音乐n"); //进入暂停状态 pCurrentState = &PAUSE;}void resumePlay(){ //实现具体功能 printf("恢复播放音乐n"); //进入播放状态 pCurrentState = &PLAY;}/***********************************************4、定义主程序上下文操作接口,主程序只关心当前状态,不关心状态之间是怎么变化的。************************************************/void onStop();void onPlayOrPause();State context = { onStop, onPlayOrPause};void onStop(State *pThis){ pCurrentState->stop(pThis);}void onPlayOrPause(State *pThis){ pCurrentState->palyOrPause(pThis);}/***********************************************5、初始化系统当前状态指针,其实就是指定系统的起始状态************************************************/void init(){ pCurrentState = &IDLE;}/***********************************************6、主程序通过上下文操作接口来控制系统当前状态的变化************************************************/void main(){ init(); context.palyOrPause();//播放 context.palyOrPause();//暂停 context.palyOrPause();//播放 context.stop();//停止}

代码已经在c在线工具|菜鸟工具中运行验证,读者也可以自行验证。运行结果如下:

开始播放音乐暂停播放音乐恢复播放音乐停止播放音乐

对比前后两份代码,六步法实现的状态机比简单状态机明显有以下几方面的优点:

代码结构要更加清晰,避免了过多的switch...case或者if...else语句   的使用。

很好地体现了开闭原则和单一职责原则,每个状态都是一个子结构体,你要增加状态就要增加子结构体,你要修改状态,你只修改一个子结构体就可以了。

封装性非常好,状态变换放置到子结构体的内部来实现,外部的调用不用知道子结构体的内部如何实现状态和行为的变换。

最后跟大家总计一下状态机六步法:

(1)、定义状态接口。
(2)、定义系统当前状态指针。
(3)、定义具体状态,根据状态迁移图来实现具体功能和状态切换。
(4)、定义主程序上下文操作接口。
(5)、初始化系统当前状态指针。
(6)、主程序通过上下文操作接口来控制系统当前状态的变化。

一般来说,熟练使用状态机六步法的嵌入式开发者,大都是两年软件开发经验以上的cqdys了。所以,如果你还是个嵌入式新手,请在实际开发中多多运用它,以后你的代码才能越来越优雅美观。而且掌握状态机编程对理解其他更复杂的设计模式也是大有裨益的。

    如果你认为文章不错,欢迎分享给身边的同事好友

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。