# U4 K9 [( q) f' u# R5 g |
状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果联系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
2 J g! U/ F& ~! o
①现态:是指当前所处的状态。
. s# {/ z: N0 K
②条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
- E- w9 ~5 [; D
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
5 s0 _) Q+ G2 e! m: K) U) a4 n0 V# A
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
5 L- M; I) R4 W* E% l) J$ R
如果我们进一步归纳,把“现态”和“次态”统一起来,而把“动作”忽略(降格处理),则只剩下两个最关键的要素,即:状态、迁移条件。
" m( q8 a. k+ _
状态机的表示要领有许多种,我们可以用文字、图形或表格的形式来表示一个状态机。
^! }! c/ q: ]( f& `1 Z: F
举个简单的例子:就按键处理来说,击键动作本身也可以看做一个状态机。一个细小的击键动作包含了:释放、抖动、闭合、抖动和重新释放等状态。 当我们打开思路,把状态机作为一种思想导入到程序中去时,就会找到处理疑问的一条有效的捷径。有时候用状态机的思维去思考程序该干什么,比用控制流程的思维去思考,可能会更有效。这样一来状态机便有了更实际的功用。废话不多说,实践才是检验真理的唯一标准。
' X* h: r5 j; D" p% [6 m
也许有人觉得状态机把问题复杂化了,其实做过软件设计的人无形之中已经在用状态机,下面就总结介绍几种状态机。
4 j0 C' S0 x' p h+ I
第一种:switch case结构状态机
5 g6 ]6 a$ k7 m7 ~' z
switch()。
* o6 b% \5 K4 @, q( j1 e
case1:。
* M0 X1 R6 A& ^$ A
if(not反复执行状态1)。
! }& a. h: Y3 [0 _, r
进入1状态前要做的准备。
1 @4 |9 W* ~ I0 ]
进入1状态的过程。
/ ]* @1 P) p/ @5 x; g6 B
if(not反复执行状态1)。
/ M) E M. V+ ~/ b
离开状态1的过程。
, L- U; V+ T7 G
case2:。
6 u5 ^: D7 \* v* y' c! R. s
...。
1 C. d# @4 U1 F
但这种方式不能很有效预定义所有的状态,也不能把这些状态之间的切换过程合理的定义出来,“状态”本身没有一个合理的定义,几乎是一种面向过程的方式,只过这种方式足够简单,也最容易让人接受,缺点就没有“状态”的定义和指派功能,导致状态的混乱,出现状态处理重复代码,甚至处理不一致的问题,按照OO的观念,状态描述本来就应该是一种实体。
( z! a; m, f$ u
第二种状态机:ifelse语句结构状态机
# r V7 z$ {7 C
这种状态机相对灵活一点,但对于一些大的项目,系统软件设计会相对复杂。
7 a) a- e$ s9 S0 h. i# G% ]
以上2种状态机是是大家接触最多的,也是经常用到的,这里不多说了。下面重点谈谈第三种状态机。
. B$ i1 {9 `, e& q4 u) L5 l7 s
第三种状态机:消息触发状态机
/ S& E* s- [$ [$ P- [
该类型的状态机实现方式也是很多的,形态多样,但万变不离其宗的就是状态机的4要素及现态、条件、动作、次态。
, B' t1 l5 U1 y, l" A. U- `
下面介绍一种消息触发类型的状态机。
: d" S! E6 G: g7 y& | K) @
基于消息驱动的状态机机制
$ v* }0 y- Z7 P) J
原理:一旦有消息触发,系统服务函数立即寻找所在状态的消息与消息处理函数对,找到后执行消息处理函数
* K7 m& N3 @7 F& @( _( f% v5 F4 b+ ~
步骤:
6 |. E8 }# d: D
1.添加消息与消息映射
7 S. A& `1 @& _- j" U( l
…
$ l+ P& Z, ^ s2 J
BEGIN_MESSAGE_ MAP(Name,Count) :状态机名,消息数
3 L: @5 }7 u* C2 D2 t
ADD_NEW_MSG_ITEM (Msg,OnMsg) :消息与消息处理函数
5 @& ?7 m' @. p8 E9 i9 ]* g( S
END_MESSAGE_MAP:结束
. ]7 E- ~& o0 @9 `" a0 z1 w
…
) R- e$ R% M* o' M
2.在这里注册
6 `, _5 [! j( K/ k# W( h
BEGIN_Register_Task:头
. ~! n: U9 u w& |" D
...
; T( x# u' w* \/ H v1 w
ADD_Register_Task(Name,Count):状态机名,消息数
" P- s0 q* n4 V# u/ O
...
/ A1 A6 i) C: d7 x2 ~
END_Register_Task:尾
+ ^! W. F5 Y0 O `# O# l
1.划分电子秤状态,完成以上步骤后,完成OnMsg消息处理函数
8 N. j: a/ o5 j" t7 S
Void OnMsg(void)
" U6 \( r3 d9 Q9 [* }/ ?9 K; R/ A
{
. _6 W( r: E7 h) ^+ [/ _
…
6 V3 H8 l% H: ~2 Y) h) K
}
2 I; m# e5 K$ P5 \, R
说明:以上用宏完成,具体宏是如下定义:
( m5 c8 I, w, E5 W! ~$ {7 N
#defineBEGIN_MESSAGE_MAP(Name,Count) constMSG_NODE_TYP MSG_node_Array_##Name[(Count)]={
3 K' ^# Y/ ], J- R$ M! M
#define ADD_NEW_MSG_ITEM(Msg,OnMsg) {Msg,OnMsg},
* S8 p5 c; Z& K y. m
#define END_MESSAGE_MAP };
* W# l; s4 I1 d8 G% J; [5 \
#define BEGIN_Register_Task const MSG_MAP TaskMap[TotalTask]={
* t# B# k$ |0 O# ^8 U! v
#define ADD_Register_Task(Name,Count) {(MSG_NODE_TYP*)MSG_node_Array_##Name,Count},
# u& B+ C, R' H3 y) ?
#define END_Register_Task };
! n$ O. [! }: d' `: ]
从以上代码可知:
2 @ K& V. r; r* x& M+ n- `
1. 添加消息与消息映射实际上是定义消息与消息处理函数对的数组,以形成一个表
) C) v( V, q4 k
2. 注册状态机实际上是把所有消息对数组的入口定义成一个数组,以形成一个表
- n/ \' V, R' x3 T5 m4 |& O
消息是如何被执行的?
2 L5 f1 x ]7 P
分发消息
1 o/ }( f3 O. J ]2 H
void Default_DisposeMessage(unsigned char *pMsg)
{
unsigned chari;
unsigned charcount=TaskMap[g_Status].cItemCount;//定位到状态表
for(i=0;i<count;i++)
{
if(*pMsg==TaskMap[g_Status].pMsgItems.msg)//看能否匹配消息
{
TaskMap[g_Status].pMsgItems.pMsgFunc();//找到就执行消息处理函数
return;
}
}
}
void DispatchMessage(unsigned char*pMsg)
{
if(*pMsg)
{
Default_DisposeMessage(pMsg);
}
}
核心函数:消息处理中心
void Message_Dispose_Central(void)
{
BYTE Msg;
while(GetMessage(&Msg)) //获取消息
{
TranslateMessage(&Msg); //解释消息
DispatchMessage(&Msg); //分发消息
}
}