geek青年的狀態機

2021-07-14 19:03:15 字數 4675 閱讀 7989



geek青年的狀態機,查表,純c語言實現

1. 問題的提出,抽象

建一,不止是他,不少人跟我討論過這樣的問題:如何才能保證在需求變更、擴充的情況下,程式的主體部分不動呢?

這是乙個非常深刻和艱難的問題。在進入實質討論之前,我們還得先明確什麼是"主體",就是我們不希望動的那一部分是什麼。事實上,沒有什麼"主體",這是被我們主觀劃分的,**中有一部分是不動的,另一部分是動的。而追求永恆(一勞永逸?) ,是我們的天性吧。

我們希望實現一段程式,換一些東西,遊戲就由 雙截龍 變成了 超級瑪麗,再換一點東西,就變成了 魂斗羅。只要招些美工,再招些指令碼作者,所有的程式設計師就可以--解雇了。

這看起來不太現實,那麼我們來看一段類似的,但是更現實一點的。我們希望實現一段程式,在每輪迭代/迴圈中,這段**都能完成我們需要做的任務,雖然這些任務可能在每輪迭代中有所不同。在數學歸納法,在 sigma 符號的的周圍,甚至在積分符號的周圍,都在發生這樣的事情。

這些夢想或者已經實現的技術,都基於"抽象"。我們試圖找到在不同的情境 (動作、需求) 下那些相同的部分。我們對具體事件做抽象,並且期待抽象的結果適用於所有的具體的事例。這樣,原來的很多任務作就成為 應用抽象的理論 的過程,不再需要創造力,因此也不再能吸引我們。那麼,我們再對抽象的結果繼續抽象,直到形而上。

2. 狀態機的引擎

引擎,就是上文中提到的開發出乙個遊戲,然後能衍生出很多遊戲的技術。**的核心部分、流程部分不會改變,只有資料 (甚至可以在外部檔案中) 才隨需求的變化而變化。

狀態機,也可以用引擎實現。實現這一目標的技術也存在已久,就是查表。查表的經典案例是 求三角函式 (在一定精度下),常量時間複雜度的解決方案 就是查表。事先把三角函式在不同度數下的值都求出來,放在hash表 (?) 裡。你要查哪個度數,我就去查哪個度數對應的函式值。

在這個案例裡,查表的那段**,不隨三角函式由sin變成cos或tan而發生任何變化。這就是引擎,被查的表就是資料。

3. 介面

我們期待的介面跟前一篇普通青年中的完全一樣。在主函式中呼叫 void state_change(enum message m) 向狀態機傳遞訊息,用 test.in 作為測試用例。主函式還知道,一共就這樣幾種訊息:

enum message ;
4. 狀態遷移表

在講如何查表前,我們先設計 表 本身。我們期待**能夠描述 狀態遷移 中的要素。記得麼,一共4個。 (1) 當前狀態, (2)當前訊息, (3)將遷移到的狀態,(4)在狀態遷移中的動作。我們期待能用**,而不是如普通青年一文中用**(switch-case)的方式描述。因為我們相信,改**比改**容易。

狀態遷移表與狀態遷移圖完全等價。

**看起來像下面這樣,如果想像劃上豎線效果更佳。

1 struct transition fsm[transition_num] = ,

4     ,

5     ,

6     ,

7     ,

8     ,

9     ,

10     ,

11     ,

12     ,

13     };

每一行,都是一組狀態遷移的匹配。第一列是當前狀態,第二列是接收到的訊息,第三列是在此種情況下將遷移到的狀態。每增加乙個遷移的匹配,我們就按這樣的規則增加一行。這規定了狀態機遷移中4要素裡的3條,剩下的那條是在遷移中的動作,後面再介紹。

當然,為了遵循c語言的語法,我們需要在此前就定義 (1) 狀態列舉、 (2) 訊息列舉,還有 (3) 遷移的結構體。如下。

1 enum state ;

2 enum message ;

3 4 struct transition ;

我們還需要定義一共多少條遷移規則,是為了我們還沒有寫出來的**準備的,不過此處已經用到,所以定義如下。

1 #define transition_num  11
5. 遷移時的動作

我們希望把遷移時的動作放在每個狀態到達之處。即,每個狀態都可以有一些"***"。這與遷移時的動作是等價的,證明略去。如果僅想在遷移時寫**,也可以利用這種方法實現。

狀態機的動作 **如下:

1 struct state_action state_action_map[state_num] = ,

3     ,

4     ,

5     ,

6     ,

7     };

每一行,是乙個狀態對應的動作。第一列是狀態,第二列是對應的動作。這樣,每增加乙個狀態 (如果它有對應動作),就在這裡加入一行;動作對應的函式需要實現,後面會介紹。

類似於狀態遷移圖,為了遵循c語言語法,我們需要在此前宣告如下。

1 #define state_num 6

2 typedef void (*action_foo)() ;

3 4 enum state ;

5 6 /* action starts */

7 void do_stop()

8 void do_play()

9 void do_forward()

10 void do_backward()

11 void do_pause()

12 void do_record()

13 14 struct state_action ;

第1行,是狀態的數量。第2行和第7行到第12行,以及第16行,使用了函式指標(指向函式的指標,乙個指標,它的基型別是乙個函式),用於表示要執行的動作。第4行,是狀態列舉。第14行到第17行,是 狀態-動作 對應關係的結構體。

第7行至第12行,是動作的執行部分。當增加的狀態需要動作時,程式設計師要在此處加入乙個函式,它遵守第2行的簽名約定。

6. 引擎

如果**的資料結構已定,**就好寫了。我們的引擎**的核心部分是查表,遍歷**,找到與當前狀態、當前訊息匹配的將遷移到的狀態。

我們還是自頂向下,假設 查表部分已經完成,為主函式提供與 普通青年一文相同的介面--而內部實現是不同的。

1 void state_change(enum message m)

2 13     return;

14 }

如第3行如示,初始狀態是 停止。在第7行,我們引用了乙個尚未寫好的函式,lookup_transition。雖然函式還不存在,不過我們能猜出來它的作用,查表,找到 當前狀態是 state,當前訊息是 m 時所對應的表項的下標 index。fsm引數是為了可能有多個狀態遷移表設計的,此處可以略過。

當查詢到 index 以後,且 index 不是 err (沒找到),就可以令 下乙個狀態為state = fsm[index].next,見第10行。

以上,完成了狀態遷移4要素中的3個:當前狀態、當前訊息、將遷移到的狀態。

第11行,完成的功能是執行與狀態對應的動作。這裡又用到函式指標。在** lookup_action(state, state_action_map)() 中,lookup_action(state, state_action_map) 用於找到狀態 state 對應的動作,後面的 "()",是因為這個動作是乙個函式指標,可以使用這樣的方式執行這個指標指向的函式。與上文中的 fsm 引數類似,state_action_map是為了應對有多個狀態-動作表的情況,這裡可以略過。

無論資料 (狀態遷移、狀態-動作)如何變化,引擎**都不會變化。所以,甚至可以把引擎放在靜態或動態鏈結庫裡,或者把資料放在外部檔案裡,執行時再載入,從而提高部署時的靈活性。

7. 查表

剛剛用到的兩個未定義的函式 lookup_transition(state, m, fsm) 和 lookup_action(state, state_action_map) 都使用了查表的方法。

**如下。可以看出,二者的結構非常類似,遍歷陣列 (for迴圈) ,找到符合條件的元素 (if判斷),然後把該元素的索引或者該元素結構體的某個成員返回。

err 和 action_not_found 是用來容錯的,萬一**有誤,沒有查到匹配的項。

1 int const err = -1;

2 int lookup_transition (enum state s, enum message m, struct transition * t)

3 12     }

13     return ret;

14 }

15 16 action_foo action_not_found = null;

17 action_foo lookup_action(enum state s, struct state_action* a)

18 27     }

28     return ret;

29 }

8. 總結

geek青年,從介面上看,與普通青年並無不同。甚至在情況相對簡單 (狀態少、狀態遷移種類少) 的時候,**量比普通青年還有不如。那麼,geek青年的長處在**呢?

古人云:滄海橫流方顯英雄本色。古人又雲:大丈夫山崩於前不變色,海嘯於後不動容。

geek青年的長處在於,他始終如一,無論遇到的情形是多麼糟糕多麼惡劣,他始終沒有變化。這個世界上,總需要一些因素,一些承諾,不隨任何易變的感情、任何旁人不能承受的痛苦或**而變化,穩定地堅持。這才能讓我們對這個世界保留一絲希望,未來才能夠和值得期待。

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

我們第一次接觸到狀態機,是在數位電路課程裡。計數器 序列奇偶檢校 檢驗三個1連續出現的報錯電路 等,都需要狀態機作為模型。實現這些功能的電路,與狀態機的狀態轉換圖 狀態轉換表都是等價的。後來,我們再接觸狀態機,是在編譯原理課程裡。狀態機用於描述與正規表示式匹配的字串。再後來,我們在gui介面設計中,...

狀態機 狀態機0

近半年都忙於做專案,沒有太多的時間去整理和總結在專案中用過的技術 個人還是覺得技術需要總結提煉和沉澱的,忙到沒時間去總結提公升其實不 是什麼好事,這次講下狀態機,在戰鬥型別的遊戲中角色有多種不同的狀態,而狀態的切換錯綜複雜,23種設計模式中有一種模式叫做狀態模式,不過 這種模式是把狀態切換條件放到各...

python 狀態機 Python 狀態機

class statemachine def init self self.handlers 狀態轉移函式字典 self.startstate none 初始狀態 self.endstate 最終狀態集合 引數name為狀態名,handler為狀態轉移函式,end state表明是否為最終狀態 de...