普通的年轻状态机,纯C语言
后。然后,我们联系了状态机,它是在编译原理课程。符串。
再后来。我们在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 };
2
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>
3
4
5 //recorder
6
7 enum state { s_stop, s_play, s_forward, s_backward, s_pause, s_record };
8 enum message { play, stop, forward, backward, record, pause };
9
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语言相关推荐
- linux下c语言聊天室程序,纯C语言Socket实现聊天室
最近在学习嵌入式开发,练习C语言小项目,基本是参考别人的代码,做了些修改实现了聊天室,纯C语言编写. 想直接看源码的跳到最后. 一.练习内容 socket通信流程 管道的使用 epoll的使用 首先要 ...
- 使用纯C语言开始win32 sdk编程
使用纯C语言开始win32 sdk编程 今天开始加强用c语言进行win32 sdk编程的训练,不为别的,只为进一步加强自己对代码的感觉,加强快速写出正确代码的能力.因为c是如些地具有挑战性而灵活的语言 ...
- java的开源项目哪里找,我想参加开源项目的开发,请问在网上去哪找这样的项目? 纯C语言的(非C++或JAVA)...
我想参加开源项目的开发,请问在网上去哪找这样的项目? 纯C语言的(非C++或JAVA)以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起 ...
- 用ram实现寄存器堆_纯C语言实现bootloader
之前有用很少一部分汇编实现过bootloader,但经过后来慢慢改进,发现是可以用纯C语言实现嵌入式操作系统的引导. 下面是之前对不能完全使用C语言引导系统的几点说明: 1.C程序中所有的代码都是以函 ...
- 河南理工大学c语言报告封面,河南理工大学图书信息管理系统设计_纯c语言课程设计.doc...
河南理工大学图书信息管理系统设计_纯c语言课程设计 C语言课程设计报告 题 目:图书信息管理系统设计 河南理工大学计算机学院 目 录 第一章 题目与要求 1.1 问题提出1 1.2 本系统涉及的知识点 ...
- linux c语言 电子相册,纯C语言开发(电子相册).doc
纯C语言开发(电子相册) PAGE PAGE 4 毕 业 论 文 课 题:(C语言)电子相册 摘要:对于java,.net,C++等一些面向对象的语言来说,结合相应的开发工具做出一个电子相册并非难事, ...
- c语言实现协议层层消息,纯C语言实现面向对象分析与示例分享.pdf
纯C语言实现面向对象分析与示例分享 采用 语言实现的关键是如何运用 语言本身的特性来实现多态.继承面.封装的面向对 C C 象的特征最近给出了例子,大家可以参考使用 , C语言的对象化模型 面向对象的 ...
- Visual Studio 编写纯C语言程序
本篇文章讲述如何用微软的 Visual Studio 编写纯C语言程序,这里的纯C语言,指的是 ANSI C 语言. 要在 Visual Studio 里创建一个 ANSI C语言程序,请按以下步骤执 ...
- c语言坦克大战程序设计,用纯C语言实现坦克大战
好久没给大家看有意思的C语言实现的代码了,今天给大家分享一个C语言实现坦克大战的游戏源码,依旧是纯C语言,点c文件,但是是在TC的环境下,运行效果截图如下: 上下左右控制方向,空格为发射炮弹,还带声音 ...
最新文章
- 《软件工艺师:专业、务实、自豪》一3.7.2 软件工艺概念走向全球
- 51nod1307(暴力树剖/二分dfs/并查集)
- 那些年我在CSDN追过的安全白帽师傅,respect
- Linux内核中读写文件数据的方法
- 2、python机器学习基础教程——K近邻算法鸢尾花分类
- 埃罗芒阿老师计算机谱,[B型]ヒトリゴト-埃罗芒阿老师OP 完整版
- WebAPI(part2)--获取元素
- python中函数定义可以不包括什么_Python 中,函数定义可以不包括以下_______。
- 【Python】Matplotlib利用楔形绘制圆环形饼图
- 《CLR via C#》读书笔记 之 基元类型、引用类型和值类型
- php+mysql+like+通配符+变量
- Java安全模型与简单示例
- oracle的merge into 的用法
- 关于maven下载依赖失败问题
- SpingMVC简易学习笔记二(springMVC的请求和响应)
- 软考中级网络工程师真题资料
- win7美化_Windows桌面美化记(二)这可能是你看过最详细的美化教程
- 方案分享丨基于海思Hi3519智能 IP 摄像机解决方案
- HDU 5857 Median (推导)
- mASK调制在AWGN信道下的可达信息速率的Monte Carlo仿真计算法
热门文章
- python拆分列表元素_Python将列表拆分为一个元素 - python
- python如何播放视频_如何用python做一个视频搜索+播放器
- mysql 导出中文乱码_sqoop导出到mysql中文乱码问题总结、utf8、gbk
- 用python玩转数据作业答案_大学mooc2020用Python玩转数据课后答案
- 保存oracle数据之后乱码,Oracle保存中文数据和读取数据页面显示乱码解决方案
- mysql创建表格1warning_MySQLMySQL创建表及相关约束
- ajax 更新模型数据_PyTorch视觉工具包torchvision重大更新!支持各种检测模型、分割模型,还有许多数据集...
- Spring 详解(三):AOP 面向切面的编程
- Java编程思想 第十五章:泛型
- mysql按照datetime精确查询_MySQL datetime字段查询按小时:分钟排序