我们第一次接触到了状态机。在数字电路课程。计数器、串行奇偶校验、考了1连续报错电路 等待,两者都需要一个状态机模型。电路实现这些功能,与状态机的状态转移图、状态转移表是等价。



后。然后,我们联系了状态机,它是在编译原理课程。符串。

再后来。我们在GUI界面设计中,须要设置一些控件在某些条件下 禁用,某些条件下使能,某些条件下打个对号。这也能够用状态机模型来控制。



1. 不要写成 消息响应/事件处理



状态机和消息响应都是 双层 switch-case 结构。不同的是,状态机的外层是状态,内层是消息。消息响应外层是消息,内层是状态。



有的同学会说。那又有多大的差别呢?代码仅仅是外在形式而非本质,它所反应的是你对模型的理解,或者说。对于问题,你使用了哪种模型。



消息响应适合于这种情形:有非常多种消息,对于同一种消息,你的程序总是给出同一种反应。打个例如。你女朋友喜欢吃冰淇淋,不论什么时候你给她买,她都高兴,或者转怒为喜,或者转悲为喜,总之,会置心情为"喜"。这种情形,适合用消息响应解决。

而状态机适合于还有一种情形,你的程序是"有状态的",它在不同的情况 (状态)下,会对同一消息做出不同的反应。状态,是一种数据。可是它影响流程的行为。

按面向对象的观点。数据与流程间的这样的高内聚关系,很适合用 类 来实现。

这是题外话。我们回到女朋友和冰淇淋间的关系。你女朋友可能并不是在不论什么情况下吃了冰淇淋都高兴,比方刚刚吃完十个八个的时候...这与她当前的状态有关。



状态机中,我们须要掌握的核心的数据是:当前状态,当前消息,将迁移到的状态,在迁移中发生的动作。



在状态机代码之前,请先看一段消息响应机制。VC生成的win32api代码大抵如此。我们随便找来一段片断看看:



1 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

2 {

3 int wmId, wmEvent;

4 PAINTSTRUCT ps;

5 HDC hdc;

6 switch (message)

7 {

8 case WM_COMMAND:

9 wmId    = LOWORD(wParam);

10 wmEvent = HIWORD(wParam);

11 case ID_MENU_GO: .... break;

12 case IDM_ABOUT:  .... break;

13 case IDM_EXIT:   .... break;

14 default:

15 return DefWindowProc(hWnd, message, wParam, lParam);

16 }

17 break;

18 case WM_PAINT: .... break;

19 case WM_DESTROY: ... break;

20 case WM_KEYDOWN: ... break; 

21 default: return DefWindowProc(hWnd, message, wParam, lParam);

22 }

23 return 0;

24 }



第6行開始到第22行结束,对每一个消息给出一个响应。没错,win32api也把这个传进来的东西称为 message。

这是非常典型的适合消息响应机制的情形。程序对于同样的消息,处理的方法总是同样的。



我们经常错误地把状态机写成了消息响应。消息这部分处理得不错,可是。因为没有非常好地记录和迁移状态,写起来easy把自己写糊涂了。

无他,用错了工具。拿螺丝刀打孔,不是工具差,而是project师选错了工具。

2. 状态机实例。录音机



实例得是相对简单的,不然我们非常easy淹没在细节之中,没有足够精力去关注状态机本身的机制了。如果我们仿真一台录音机...



我们先如果你见过录音机。录音机是一种以前先进的设备。有一个或两个"卡",能够放进磁带。

"卡"前面有几个按键,这几个按键上的标识由于图形简单且示意性强,如今还在广泛使用。它们各自是 播放 > 、暂停 || 、快进 >> 、快退<< 、录音 O 、停止 []。

这几个按键之间是有一定的"相互排斥关系"的。比方当播放键按下时,我们不应该能把 快进键按下。当然,淘气的同学可能这样干过,我们会听到"咔咔"的声音,然后是家长骂败家玩艺的声音。能够就"相互排斥关系"開始敲代码。可是我认为这样有点麻烦。



我们觉得,这种"相互排斥关系"是由于录音机是"有状态的"。

所以。我们打算用状态机来实现。状态转换图是这种。请读图的时候关注这四点:当前状态。当前消息,将迁移到的状态,在迁移中发生的动作 (本例中没有) 。







备注:我实在想不起来 暂停 和 停止 之间的关系了。似乎是这种。又似乎不是。反正大概是那么个意思,不影响对状态机的理解。就这么地吧。

接下来是C代码实现。

3. 接口 及 測试



看到下面代码,有的同学会说,你这不就是主程序么。为什么要把小标题叫做接口。

由于,它规定了我们的状态机函数将是什么样子的。



1 enum message { play, stop, forward, backward, record, pause };



3 int main(int argc, char *argv[])

4 {

5     char c=0x00;

6     while(1)

7     {

8         c = getchar();

9         switch(c)

10         {

11             case ' ': state_change(pause); break;

12             case 'p':  state_change(play); break;

13             case 'r': state_change(record); break;

14             case 's': state_change(stop); break;

15             case 'f': state_change(forward); break;

16             case 'b': state_change(backward); break;

17             case 'q':     return EXIT_SUCCESS;

18         }

19     }

20     return EXIT_SUCCESS;

21 }



上述代码规定了。状态机迁移函数的原型/签名是 void state_change(enum

message m)。

測试的时候,我们这样做:./state < test.in。

test.in的内容是"psfsbspq"。測试时期待看到输出的状态迁移过程。之所以这样做,而不是每次从控制台手动输入。是由于每次測试的内容都应该是同样的--同样的输入,程序有同样的反应--可重现性。或者说,DRY原则。



一个很值得我们注意的问题。在上述接口中。我们看不到"状态"。其实,我们将会定义:



enum state { s_stop, s_play, s_forward, s_backward, s_pause, s_record };



可是,接口以外的代码,是 *不应该* (是不应该。不是 不必要,是一定不要) 知道状态的,既不应该知道当前状态,也不应该知道将要迁移到哪个状态。也不应该知道在迁移过程中应该做什么动作。

假设接口以外的代码知道了这些,就侵入了状态机的隐私。子系统的边界就模糊了。而契约的首要任务就是规定边界。规定国家与个人、个人与个人、个人与集体的边界。

这一原则,早在195X年,软件project刚刚開始的时候就确立了,是最初确立的原则,即 信息隐藏。

后面的原则,都是它的儿子孙子。

有个比喻讲过这个道理。当你在超市出口付款的时候。你会自己把钱从钱夹里拿出来递给售货员,而不会转过身去对她说,"在我屁股兜里,你自己掏吧。别忘了把零钱放回来。"这既添加了如果--你极端信任她。也添加了她的责任。



接口,最基本的任务就是为了明白责任,把责任分布在子系统边界两側。其次才是规定调用的方法,即边界长什么样。



4. 状态迁移



下面是状态机的代码片断。



1 enum state { s_stop, s_play, s_forward, s_backward, s_pause, s_record};

2 void state_change(enum message m)

3 {

4  static enum state s=s_stop;

5  switch (s)

6  {

7 case s_play:

8     if(m==stop)

9        {

10 s = s_stop;

11 printf("stop.\n");        

12        }

13        else if (m==pause)

14        {

15              s = s_pause;

16 printf("pause");

17        }

18        break;



我们还是要关注那四个关键点: (1) 当前状态, (2) 当前消息, (3) 将迁移到哪个状态, (4) 迁移中会做哪些动作。

(1) 当前状态必定是第1行的枚举类型中的一个。我们初始化状态为 停止,见第4行。

在第5行到第7行,我们的双重 switch-case 的外层 按当前状态分类。例如以下。

5  switch (s)

6  {

7 case s_play:

以下还有非常多 case,第1行的枚举类型中的每个状态,都有一个 case。

(2) 当前消息。

假设当前状态是第7行了,那么,当前消息由双层 switch-case的内层,即第8行。第13行的 if...else if 来响应。



(3) 将迁移到哪个状态。

在 s_play状态 (第7行) 接收到 stop 消息 (第8行)的话,将迁移到 s_stop 状态,即第10行。



(4) 在迁移中会做哪些动作,假设还是这个状态这个消息,会做的动作是 第11行。打印一段文字描写叙述接下来的状态。



在函数 void state_change(enum message m) 中,维护了当前状态。规定了在某种状态下-接收到某个消息,会迁移到哪个状态。在状态迁移中做哪些动作。



主函数在调用state_change时,是通过这一接口,向状态机发送一个消息。由状态机对这个消息做出适合自己当前状态的响应--状态迁移、动作。主函数所示,是一个多彩或善变的女人。而她之所以对同一消息做出不同响应的原因,在她的内心深入保留着,那是她不会对你说的状态。以及状态迁移中的波澜壮阔。即使表面上善变的状态机,也是能够理解和预測的,假设她对你倘开心扉。同意你一行一行把附录A中的代码读完,了解全部的 switch-case,了解全部的状态下她将会怎样响应每一种消息。



附录A 完整代码





1 #include <stdlib.h>

2 #include <stdio.h>





5 //recorder 



7 enum state { s_stop, s_play, s_forward, s_backward, s_pause, s_record  };

8 enum message { play, stop, forward, backward, record, pause };



10 

11 void state_change(enum message m)

12 {

13  static enum state s=s_stop;

14  switch (s)

15  {

16 case s_play:

17     if(m==stop)

18        {

19 s = s_stop;

20    printf("stop.\n");        

21        }

22        else if (m==pause)

23        {

24             s = s_pause;

25 printf("pause");

26        }

27        break;

28 case s_pause:

29 if(m==pause)

30 {

31 s = s_play;

32 printf("play.\n");        

33 }

34 else if(m==stop)

35 {

36 s = s_stop;

37 printf("stop.\n");        

38 }

39 break;

40     case s_stop:

41 if(m==play)

42 {

43 s = s_play;

44 printf("play.\n");        

45 }

46 if(m==backward)

47 {

48 s = s_backward;

49 printf("backward.\n");        

50 }

51 if(m==forward)

52 {

53 s = s_forward;

54 printf("forward.\n");        

55 }

56 if(m==record)

57 {

58 s = s_record;

59 printf("record.\n");        

60 }

61 break;

62 case s_forward:

63 if(m==stop)

64 {

65 s = s_stop;

66 printf("stop.\n"); 

67 }

68 break;

69 case s_backward:

70 if(m==stop)

71 {

72 s = s_stop;

73 printf("stop.\n"); 

74 }

75 break;

76 case s_record:

77 if(m==stop)

78 {

79 s = s_stop;

80 printf("stop.\n"); 

81 }

82 break;

83

84         

85  }

86      

87 }

88 

89 

90 int main(int argc, char *argv[])

91 {

92     char c=0x00;

93     while(1)

94     {

95         c = getchar();

96         switch(c)

97         {

98             case ' ': state_change(pause); break;

99             case 'p':  state_change(play); break;

100             case 'r': state_change(record); break;

101             case 's': state_change(stop); break;

102             case 'f': state_change(forward); break;

103             case 'b': state_change(backward); break;

104             case 'q':     return EXIT_SUCCESS;

105         }

106 

107         

108     }

109     

110     return EXIT_SUCCESS;

111 }





附录B 状态图源码 in graphviz





digraph state

{

graph [ nodesep=1.2];

rankdir = LR;



播放 -> 暂停 [label="按下 || "];

暂停 -> 播放 [label="按下 || "];

暂停 -> 停止 [label="按下 []"];

停止 -> 播放 [label="按下 >"];

播放 -> 停止 [label="按下 []"];

停止 -> 快退 [label="按下 <<"];

停止 -> 快进 [label="按下 >>"];

快进 -> 停止 [label="按下 []"];

快退 -> 停止 [label="按下 []"];

停止 -> 录音 [label="按下 O"];

录音 -> 停止 [label="按下 []"];





}













--------------------





博客会手工同步到下面地址:





[http://giftdotyoung.blogspot.com]





[http://blog.csdn.net/younggift]

=======================

版权声明:本文博客原创文章。博客,未经同意,不得转载。

普通的年轻状态机,纯C语言的更多相关文章

  1. geek青年的状态机,查表,纯C语言实现

    geek青年的状态机,查表,纯C语言实现 1. 问题的提出.抽象 建一,不止是他,不少人跟我讨论过这种问题:怎样才干保证在需求变更.扩充的情况下.程序的主体部分不动呢? 这是一个很深刻和艰难的问题.在 ...

  2. 纯C语言INI文件解析

    原地址:http://blog.csdn.net/foruok/article/details/17715969 在一个跨平台( Android .Windows.Linux )项目中配置文件用 IN ...

  3. 经典数独游戏+数独求解器—纯C语言实现

    "心常乐数独小游戏"(下面简称"本软件")是一款windows平台下的数独游戏软件. 本软件是开源.免费软件. 本软件使用纯C语言编写,MinGW编译,NSIS ...

  4. 不好意思啊,我上周到今天不到10天时间,用纯C语言写了一个小站!想拍砖的就赶紧拿出来拍啊

    花10天时间用C语言做了个小站 http://tieba.yunxunmi.com/index.html 简称: 云贴吧 不好意思啊,我上周到今天不到10天时间,用纯C语言写了一个小站!想拍砖的就赶紧 ...

  5. FastDFS是纯C语言实现,只支持Linux,适合以中小文件为载体的在线服务,还可以冗余备份和负载均衡

    一.理论基础 FastDFS比较适合以中小文件为载体的在线服务,比如跟NGINX(APACHE)配合搭建图片服务器. 分布式文件系统FastDFS FastDFS是纯C语言实现,只支持Linux.Fr ...

  6. 异想家纯C语言矩阵运算库

    Sandeepin最近做的项目中需要在嵌入式芯片里跑一些算法,而这些单片机性能不上不下,它能跑些简单的程序,但又还没到上Linux系统的地步.所以只好用C语言写一些在高级语言里一个函数就解决的算法了, ...

  7. 状态机的c语言编程

    http://blog.csdn.net/shandongdaya/article/details/7282547 一 有限状态机的实现方式 有限状态机(Finite State Machine或者F ...

  8. 关于linux内核用纯c语言编写的思考

    在阅读linux2.6 版本内核的虚拟文件系统和驱动子系统的时候,我发现内核纯用c语言编写其实也是有一点不方便,特别是内核中大量存在了对象的概念,比如说文件对象,描述起来使用对象描述,但是对象在c语言 ...

  9. <Win32_20>纯c语言版的打飞机游戏出炉了^_^

    经过昨天的苦战,终于完成了纯C版的打飞机游戏——使用微信打飞机游戏的素材,不过玩法有些不同,下面会有详述 一.概述游戏的玩法.实现效果 1. 游戏第一步,简单判断一下,给你一个准备的时间: 2.选择& ...

随机推荐

  1. jquery ajax验证用户名是否存在(后台spring mvc)

    controller层 @ResponseBody @RequestMapping(value = "/user/isExist", produces = "applic ...

  2. Linux I/O 重定向详解及应用实例

    Linux I/O 重定向详解及应用实例 简解 > 输出 < 输入 >> 追加 & [> | < | >>]之前:输入输出; ls /dev & ...

  3. Swift - 使用表格组件(UITableView)实现单列表

    1,样例说明: (1)列表内容从Controls.plist文件中读取,类型为Array. (2)点击列表项会弹出消息框显示该项信息. (3)按住列表项向左滑动,会出现删除按钮.点击删除即可删除该项. ...

  4. 14.3.2.1 Transaction Isolation Levels 事务隔离级别

    14.3.2 InnoDB Transaction Model InnoDB 事务模型 14.3.2.1 Transaction Isolation Levels 事务隔离级别 14.3.2.2 au ...

  5. C++异常中的堆栈跟踪

    C++语言的运行时环境是基于栈的环境,堆栈跟踪(trace stack)就是程序运行时能够跟踪并打印所调用的函数.变量及返回地址等,C++异常中的堆栈跟踪就是当程序抛出异常时,能够把导致抛出异常的语句 ...

  6. 使用SetUnhandledExceptionFilter转储程序崩溃时内存DMP注意事项

    使用代码手工生成dmp文件 SetUnhandledExceptionFilter 为每个线程设置SetUnhandledExceptionFilter(MyCallBack),(必须在每个线程中启动 ...

  7. ACM-最小生成树之畅通project——hdu1863

    ***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...

  8. [Android学习笔记]继承自ViewGroup的控件的过程学习

    ViewGroup文档 http://developer.android.com/training/index.html 继承自ViewGroup需要重写onLayout方法用来为子View设定位置信 ...

  9. Note:This element neither has attached source nor attached Javadoc

    在用Eclipse编写程序时,发现把鼠标放到类名上时出现标题的提示 解决方法: 右击项目,选择 properties –> Java Build Path –> Libraries,如图 ...

  10. 如何解决ORA-12547: TNS:lost contact错

    执行环境:ubuntu+oracle 11.2.0 为了启动oracle时间,出现ORA-12547: TNS:lost contact错误. 中午好好的纳,下午就无论了.以为是链接失效,关机重新启动 ...