普通的年轻状态机,纯C语言
后。然后,我們聯(lián)系了狀態(tài)機(jī),它是在編譯原理課程。符串。
再后來(lái)。我們?cè)贕UI界面設(shè)計(jì)中,須要設(shè)置一些控件在某些條件下 禁用,某些條件下使能,某些條件下打個(gè)對(duì)號(hào)。這也能夠用狀態(tài)機(jī)模型來(lái)控制。
1. 不要寫成 消息響應(yīng)/事件處理
狀態(tài)機(jī)和消息響應(yīng)都是 雙層 switch-case 結(jié)構(gòu)。不同的是,狀態(tài)機(jī)的外層是狀態(tài),內(nèi)層是消息。消息響應(yīng)外層是消息,內(nèi)層是狀態(tài)。
有的同學(xué)會(huì)說(shuō)。那又有多大的差別呢?代碼僅僅是外在形式而非本質(zhì),它所反應(yīng)的是你對(duì)模型的理解,或者說(shuō)。對(duì)于問(wèn)題,你使用了哪種模型。
消息響應(yīng)適合于這種情形:有非常多種消息,對(duì)于同一種消息,你的程序總是給出同一種反應(yīng)。打個(gè)例如。你女朋友喜歡吃冰淇淋,不論什么時(shí)候你給她買,她都高興,或者轉(zhuǎn)怒為喜,或者轉(zhuǎn)悲為喜,總之,會(huì)置心情為"喜"。這種情形,適合用消息響應(yīng)解決。
而狀態(tài)機(jī)適合于還有一種情形,你的程序是"有狀態(tài)的",它在不同的情況 (狀態(tài))下,會(huì)對(duì)同一消息做出不同的反應(yīng)。狀態(tài),是一種數(shù)據(jù)。可是它影響流程的行為。
按面向?qū)ο蟮挠^點(diǎn)。數(shù)據(jù)與流程間的這樣的高內(nèi)聚關(guān)系,很適合用 類 來(lái)實(shí)現(xiàn)。
這是題外話。我們回到女朋友和冰淇淋間的關(guān)系。你女朋友可能并不是在不論什么情況下吃了冰淇淋都高興,比方剛剛吃完十個(gè)八個(gè)的時(shí)候...這與她當(dāng)前的狀態(tài)有關(guān)。
狀態(tài)機(jī)中,我們須要掌握的核心的數(shù)據(jù)是:當(dāng)前狀態(tài),當(dāng)前消息,將遷移到的狀態(tài),在遷移中發(fā)生的動(dòng)作。
在狀態(tài)機(jī)代碼之前,請(qǐng)先看一段消息響應(yīng)機(jī)制。VC生成的win32api代碼大抵如此。我們隨便找來(lái)一段片斷看看:
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行結(jié)束,對(duì)每一個(gè)消息給出一個(gè)響應(yīng)。沒錯(cuò),win32api也把這個(gè)傳進(jìn)來(lái)的東西稱為 message。
這是非常典型的適合消息響應(yīng)機(jī)制的情形。程序?qū)τ谕瑯拥南?#xff0c;處理的方法總是同樣的。
我們經(jīng)常錯(cuò)誤地把狀態(tài)機(jī)寫成了消息響應(yīng)。消息這部分處理得不錯(cuò),可是。因?yàn)闆]有非常好地記錄和遷移狀態(tài),寫起來(lái)easy把自己寫糊涂了。
無(wú)他,用錯(cuò)了工具。拿螺絲刀打孔,不是工具差,而是project師選錯(cuò)了工具。
2. 狀態(tài)機(jī)實(shí)例。錄音機(jī)
實(shí)例得是相對(duì)簡(jiǎn)單的,不然我們非常easy淹沒在細(xì)節(jié)之中,沒有足夠精力去關(guān)注狀態(tài)機(jī)本身的機(jī)制了。如果我們仿真一臺(tái)錄音機(jī)...
我們先如果你見過(guò)錄音機(jī)。錄音機(jī)是一種以前先進(jìn)的設(shè)備。有一個(gè)或兩個(gè)"卡",能夠放進(jìn)磁帶。
"卡"前面有幾個(gè)按鍵,這幾個(gè)按鍵上的標(biāo)識(shí)由于圖形簡(jiǎn)單且示意性強(qiáng),如今還在廣泛使用。它們各自是 播放 > 、暫停 || 、快進(jìn) >> 、快退<< 、錄音 O 、停止 []。
這幾個(gè)按鍵之間是有一定的"相互排斥關(guān)系"的。比方當(dāng)播放鍵按下時(shí),我們不應(yīng)該能把 快進(jìn)鍵按下。當(dāng)然,淘氣的同學(xué)可能這樣干過(guò),我們會(huì)聽到"咔咔"的聲音,然后是家長(zhǎng)罵敗家玩藝的聲音。能夠就"相互排斥關(guān)系"開始敲代碼。可是我認(rèn)為這樣有點(diǎn)麻煩。
我們覺得,這種"相互排斥關(guān)系"是由于錄音機(jī)是"有狀態(tài)的"。
所以。我們打算用狀態(tài)機(jī)來(lái)實(shí)現(xiàn)。狀態(tài)轉(zhuǎn)換圖是這種。請(qǐng)讀圖的時(shí)候關(guān)注這四點(diǎn):當(dāng)前狀態(tài)。當(dāng)前消息,將遷移到的狀態(tài),在遷移中發(fā)生的動(dòng)作 (本例中沒有) 。
備注:我實(shí)在想不起來(lái) 暫停 和 停止 之間的關(guān)系了。似乎是這種。又似乎不是。反正大概是那么個(gè)意思,不影響對(duì)狀態(tài)機(jī)的理解。就這么地吧。
接下來(lái)是C代碼實(shí)現(xiàn)。
3. 接口 及 測(cè)試
看到下面代碼,有的同學(xué)會(huì)說(shuō),你這不就是主程序么。為什么要把小標(biāo)題叫做接口。
由于,它規(guī)定了我們的狀態(tài)機(jī)函數(shù)將是什么樣子的。
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 }
上述代碼規(guī)定了。狀態(tài)機(jī)遷移函數(shù)的原型/簽名是 void state_change(enum
message m)。
測(cè)試的時(shí)候,我們這樣做:./state < test.in。
test.in的內(nèi)容是"psfsbspq"。測(cè)試時(shí)期待看到輸出的狀態(tài)遷移過(guò)程。之所以這樣做,而不是每次從控制臺(tái)手動(dòng)輸入。是由于每次測(cè)試的內(nèi)容都應(yīng)該是同樣的--同樣的輸入,程序有同樣的反應(yīng)--可重現(xiàn)性。或者說(shuō),DRY原則。
一個(gè)很值得我們注意的問(wèn)題。在上述接口中。我們看不到"狀態(tài)"。其實(shí),我們將會(huì)定義:
enum state { s_stop, s_play, s_forward, s_backward, s_pause, s_record };
可是,接口以外的代碼,是 *不應(yīng)該* (是不應(yīng)該。不是 不必要,是一定不要) 知道狀態(tài)的,既不應(yīng)該知道當(dāng)前狀態(tài),也不應(yīng)該知道將要遷移到哪個(gè)狀態(tài)。也不應(yīng)該知道在遷移過(guò)程中應(yīng)該做什么動(dòng)作。
假設(shè)接口以外的代碼知道了這些,就侵入了狀態(tài)機(jī)的隱私。子系統(tǒng)的邊界就模糊了。而契約的首要任務(wù)就是規(guī)定邊界。規(guī)定國(guó)家與個(gè)人、個(gè)人與個(gè)人、個(gè)人與集體的邊界。
這一原則,早在195X年,軟件project剛剛開始的時(shí)候就確立了,是最初確立的原則,即 信息隱藏。
后面的原則,都是它的兒子孫子。
有個(gè)比喻講過(guò)這個(gè)道理。當(dāng)你在超市出口付款的時(shí)候。你會(huì)自己把錢從錢夾里拿出來(lái)遞給售貨員,而不會(huì)轉(zhuǎn)過(guò)身去對(duì)她說(shuō),"在我屁股兜里,你自己掏吧。別忘了把零錢放回來(lái)。"這既添加了如果--你極端信任她。也添加了她的責(zé)任。
接口,最基本的任務(wù)就是為了明白責(zé)任,把責(zé)任分布在子系統(tǒng)邊界兩側(cè)。其次才是規(guī)定調(diào)用的方法,即邊界長(zhǎng)什么樣。
4. 狀態(tài)遷移
下面是狀態(tài)機(jī)的代碼片斷。
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;
我們還是要關(guān)注那四個(gè)關(guān)鍵點(diǎn): (1) 當(dāng)前狀態(tài), (2) 當(dāng)前消息, (3) 將遷移到哪個(gè)狀態(tài), (4) 遷移中會(huì)做哪些動(dòng)作。
(1) 當(dāng)前狀態(tài)必定是第1行的枚舉類型中的一個(gè)。我們初始化狀態(tài)為 停止,見第4行。
在第5行到第7行,我們的雙重 switch-case 的外層 按當(dāng)前狀態(tài)分類。例如以下。
5 ?switch (s)
6 ?{
7 case s_play:
以下還有非常多 case,第1行的枚舉類型中的每個(gè)狀態(tài),都有一個(gè) case。
(2) 當(dāng)前消息。
假設(shè)當(dāng)前狀態(tài)是第7行了,那么,當(dāng)前消息由雙層 switch-case的內(nèi)層,即第8行。第13行的 if...else if 來(lái)響應(yīng)。
(3) 將遷移到哪個(gè)狀態(tài)。
在 s_play狀態(tài) (第7行) 接收到 stop 消息 (第8行)的話,將遷移到 s_stop 狀態(tài),即第10行。
(4) 在遷移中會(huì)做哪些動(dòng)作,假設(shè)還是這個(gè)狀態(tài)這個(gè)消息,會(huì)做的動(dòng)作是 第11行。打印一段文字描寫敘述接下來(lái)的狀態(tài)。
在函數(shù) void state_change(enum message m) 中,維護(hù)了當(dāng)前狀態(tài)。規(guī)定了在某種狀態(tài)下-接收到某個(gè)消息,會(huì)遷移到哪個(gè)狀態(tài)。在狀態(tài)遷移中做哪些動(dòng)作。
主函數(shù)在調(diào)用state_change時(shí),是通過(guò)這一接口,向狀態(tài)機(jī)發(fā)送一個(gè)消息。由狀態(tài)機(jī)對(duì)這個(gè)消息做出適合自己當(dāng)前狀態(tài)的響應(yīng)--狀態(tài)遷移、動(dòng)作。主函數(shù)所示,是一個(gè)多彩或善變的女人。而她之所以對(duì)同一消息做出不同響應(yīng)的原因,在她的內(nèi)心深入保留著,那是她不會(huì)對(duì)你說(shuō)的狀態(tài)。以及狀態(tài)遷移中的波瀾壯闊。即使表面上善變的狀態(tài)機(jī),也是能夠理解和預(yù)測(cè)的,假設(shè)她對(duì)你倘開心扉。同意你一行一行把附錄A中的代碼讀完,了解全部的 switch-case,了解全部的狀態(tài)下她將會(huì)怎樣響應(yīng)每一種消息。
附錄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 狀態(tài)圖源碼 in graphviz
digraph state
{
graph [ nodesep=1.2];
rankdir = LR;
播放 -> 暫停 [label="按下 || "];
暫停 -> 播放 [label="按下 || "];
暫停 -> 停止 [label="按下 []"];
停止 -> 播放 [label="按下 >"];
播放 -> 停止 [label="按下 []"];
停止 -> 快退 [label="按下 <<"];
停止 -> 快進(jìn) [label="按下 >>"];
快進(jìn) -> 停止 [label="按下 []"];
快退 -> 停止 [label="按下 []"];
停止 -> 錄音 [label="按下 O"];
錄音 -> 停止 [label="按下 []"];
}
--------------------
博客會(huì)手工同步到下面地址:
[http://giftdotyoung.blogspot.com]
[http://blog.csdn.net/younggift]
=======================
版權(quán)聲明:本文博客原創(chuàng)文章。博客,未經(jīng)同意,不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的普通的年轻状态机,纯C语言的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【Android开发日记】第一个任务An
- 下一篇: hdu 5285 二分图黑白染色