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要素裡的3條,剩下的那條是在遷移中的動作,後面再介紹。4 ,
5 ,
6 ,
7 ,
8 ,
9 ,
10 ,
11 ,
12 ,
13 };
當然,為了遵循c語言的語法,我們需要在此前就定義 (1) 狀態列舉、 (2) 訊息列舉,還有 (3) 遷移的結構體。如下。
1 enum state ;我們還需要定義一共多少條遷移規則,是為了我們還沒有寫出來的**準備的,不過此處已經用到,所以定義如下。2 enum message ;
3 4 struct transition ;
1 #define transition_num 115. 遷移時的動作
我們希望把遷移時的動作放在每個狀態到達之處。即,每個狀態都可以有一些"***"。這與遷移時的動作是等價的,證明略去。如果僅想在遷移時寫**,也可以利用這種方法實現。
狀態機的動作 **如下:
1 struct state_action state_action_map[state_num] = ,每一行,是乙個狀態對應的動作。第一列是狀態,第二列是對應的動作。這樣,每增加乙個狀態 (如果它有對應動作),就在這裡加入一行;動作對應的函式需要實現,後面會介紹。3 ,
4 ,
5 ,
6 ,
7 };
類似於狀態遷移圖,為了遵循c語言語法,我們需要在此前宣告如下。
1 #define state_num 6第1行,是狀態的數量。第2行和第7行到第12行,以及第16行,使用了函式指標(指向函式的指標,乙個指標,它的基型別是乙個函式),用於表示要執行的動作。第4行,是狀態列舉。第14行到第17行,是 狀態-動作 對應關係的結構體。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 ;
第7行至第12行,是動作的執行部分。當增加的狀態需要動作時,程式設計師要在此處加入乙個函式,它遵守第2行的簽名約定。
6. 引擎
如果**的資料結構已定,**就好寫了。我們的引擎**的核心部分是查表,遍歷**,找到與當前狀態、當前訊息匹配的將遷移到的狀態。
我們還是自頂向下,假設 查表部分已經完成,為主函式提供與 普通青年一文相同的介面--而內部實現是不同的。
1 void state_change(enum message m)如第3行如示,初始狀態是 停止。在第7行,我們引用了乙個尚未寫好的函式,lookup_transition。雖然函式還不存在,不過我們能猜出來它的作用,查表,找到 當前狀態是 state,當前訊息是 m 時所對應的表項的下標 index。fsm引數是為了可能有多個狀態遷移表設計的,此處可以略過。2 13 return;
14 }
當查詢到 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;8. 總結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 }
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...