普通青年的狀態機,純C語言

2021-06-22 13:41:48 字數 3955 閱讀 2567

我們第一次接觸到狀態機,是在數位電路課程裡。計數器、序列奇偶檢校、檢驗三個1連續出現的報錯電路 等,都需要狀態機作為模型。實現這些功能的電路,與狀態機的狀態轉換圖、狀態轉換表都是等價的。

後來,我們再接觸狀態機,是在編譯原理課程裡。狀態機用於描述與正規表示式匹配的字串。

再後來,我們在gui介面設計中,需要設定一些控制項在某些條件下 禁用,某些條件下使能,某些條件下打個對號。這也可以用狀態機模型來控制。

1. 不要寫成 訊息響應/事件處理

狀態機和訊息響應都是 雙層 switch-case 結構。不同的是,狀態機的外層是狀態,內層是訊息;訊息響應外層是訊息,內層是狀態。

有的同學會說,那又有多大的區別呢?**只是外在形式而非本質,它所反應的是你對模型的理解,或者說,對於問題,你使用了哪種模型。

訊息響應適合於這樣的情形:有很多種訊息,對於同一種訊息,你的程式總是給出同一種反應。打個比方,你女朋友喜歡吃冰淇淋,任何時候你給她買,她都高興,或者轉怒為喜,或者轉悲為喜,總之,會置心情為"喜"。這種情形,適合用訊息響應解決。

而狀態機適合於另一種情形,你的程式是"有狀態的",它在不同的情況 (狀態)下,會對同一訊息做出不同的反應。狀態,是一種資料,但是它影響流程的行為。按物件導向的觀點,資料與流程間的這種高內聚關係,非常適合用 類 來實現。這是題外話,我們回到女朋友和冰淇淋間的關係。你女朋友可能並非在任何情況下吃了冰淇淋都高興,比如剛剛吃完十個八個的時候...這與她當前的狀態有關。

狀態機中,我們需要掌握的核心的資料是:當前狀態,當前訊息,將遷移到的狀態,在遷移中發生的動作。

在狀態機**之前,請先看一段訊息響應機制,vc生成的win32api**大抵如此。我們隨便找來一段片斷看看:

1 lresult callback wndproc(hwnd hwnd, uint message, wparam wparam, lparam lparam)

2 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。這是很典型的適合訊息響應機制的情形,程式對於相同的訊息,處理的方法總是相同的。

我們常常錯誤地把狀態機寫成了訊息響應,訊息這部分處理得不錯,但是,由於沒有很好地記錄和遷移狀態,寫起來容易把自己寫糊塗了。無他,用錯了工具。拿螺絲刀打孔,不是工具差,而是工程師選錯了工具。

2. 狀態機例項,錄音機

例項得是相對簡單的,不然我們很容易淹沒在細節之中,沒有足夠精力去關注狀態機本身的機制了。假設我們**一台錄音機...

備註:我實在想不起來 暫停 和 停止 之間的關係了,似乎是這樣的,又似乎不是。反正大概是那麼個意思,不影響對狀態機的理解,就這麼地吧。

接下來是c**實現。

3. 介面 及 測試

看到以下**,有的同學會說,你這不就是主程式麼,為什麼要把小標題叫做介面。因為,它規定了我們的狀態機函式將是什麼樣子的。

1 enum message ;

2 3 int main(int argc, char *argv)

4 19     }

20     return exit_success;

21 }

上述**規定了,狀態機遷移函式的原型/簽名是 void state_change(enum

message m)。

測試的時候,我們這樣做:./state < test.in。test.in的內容是"psfsbspq",測試時期待看到輸出的狀態遷移過程。之所以這樣做,而不是每次從控制台手動輸入,是因為每次測試的內容都應該是相同的--相同的輸入,程式有相同的反應--可重現性。或者說,dry原則。

乙個非常值得我們注意的問題。在上述介面中,我們看不到"狀態"。事實上,我們將會定義:

enum state ;

但是,介面以外的**,是 *不應該* (是不應該,不是 不必要,是一定不要) 知道狀態的,既不應該知道當前狀態,也不應該知道將要遷移到哪個狀態,也不應該知道在遷移過程中應該做什麼動作。如果介面以外的**知道了這些,就侵入了狀態機的隱私,子系統的邊界就模糊了。而契約的首要任務就是規定邊界,規定國家與個人、個人與個人、個人與集體的邊界。

這一原則,早在195x年,軟體工程剛剛開始的時候就確立了,是最初確立的原則,即 資訊隱藏。後面的原則,都是它的兒子孫子。有個比喻講過這個道理。當你在超市出口付款的時候,你會自己把錢從錢夾裡拿出來遞給售貨員,而不會轉過身去對她說,"在我屁股兜裡,你自己掏吧,別忘了把零錢放回來。"這既增加了假設--你極端信任她,也增加了她的責任。

介面,最主要的任務就是為了明確責任,把責任分布在子系統邊界兩側。其次才是規定呼叫的方法,即邊界長什麼樣。

4. 狀態遷移

以下是狀態機的**片斷。

1 enum state ;

2 void state_change(enum message m)

3 13        else if (m==pause)

14        

18        break;

我們還是要關注那四個關鍵點: (1) 當前狀態, (2) 當前訊息, (3) 將遷移到哪個狀態, (4) 遷移中會做哪些動作。

(1) 當前狀態必然是第1行的列舉型別中的乙個。我們初始化狀態為 停止,見第4行。

在第5行到第7行,我們的雙重 switch-case 的外層 按當前狀態分類,如下。

5  switch (s)

6  ;

8 enum message ;

9 10 

11 void state_change(enum message m)

12 22        else if (m==pause)

23        

27        break;

28 case s_pause:

29 if(m==pause)

30 34

else if(m==stop)

35 39

break;

40     case s_stop:

41 if(m==play)

42 46

if(m==backward)

47 51

if(m==forward)

52 56

if(m==record)

57 61

break;

62 case s_forward:

63 if(m==stop)

64 68

break;

69 case s_backward:

70 if(m==stop)

71 75

break;

76 case s_record:

77 if(m==stop)

78 82

break;

83 84         

85  }

86      

87 }

88 89 

90 int main(int argc, char *argv)

91 106 

107         

108     }

109     

110     return exit_success;

111 }

附錄b 狀態圖源** in graphviz

digraph state

--------------------[[

********************===

geek青年的狀態機

geek青年的狀態機,查表,純c語言實現 1.問題的提出,抽象 建一,不止是他,不少人跟我討論過這樣的問題 如何才能保證在需求變更 擴充的情況下,程式的主體部分不動呢?這是乙個非常深刻和艱難的問題。在進入實質討論之前,我們還得先明確什麼是 主體 就是我們不希望動的那一部分是什麼。事實上,沒有什麼 主...

C語言 狀態機程式設計

狀態機原理 有限狀態機的工作原理如圖1所示,發生事件 event 後,根據當前狀態 cur state 決定執行的動作 action 並設定下乙個狀態號 nxt state fsm的實現方式 1 switch case或者if else 這無意是最直觀的方式,使用一堆條件判斷,會程式設計的人都可以做...

C語言狀態機程式設計

輸入一段字串,字串由字母和分隔符 空格,逗號,句號等 組成,寫乙個函式統計該字串中的單詞數。狀態 typedef enum count state t count state t 判斷函式 static intis end char ch static intis division char ch ...