此篇文章是有關有限狀態機的筆記,有限狀態機,顧名思義是由有限個狀態、有限個觸發的事件組成,經過觸發的條件來切換不一樣的狀態。因爲其特性是具備有限性,所以咱們在設計的時候須要考慮好全部的狀態以及觸發狀態的事件,這是設計有限狀態機的前提。api
如下是建立一個有限狀態機的通常流程:數組
-
確認用戶的需求信息(凍結需求)框架
-
分析需求並繪製狀態轉換流程圖ide
-
建立狀態機的代碼框架函數
-
確保轉換(狀態改變)能正確運行測試
-
測試已實現的狀態機url
有限狀態機在咱們平常生活中使用的一些電器裏面有不少應用,好比說洗衣機、咖啡機、銀行自助存款機、電飯煲等等,本篇筆記就以銀行自助存款機爲例,由於這個和money有關,money這個東西固然是越多越好咯,也但願你們今年存越多越好咯~下面來和你們介紹下,有限狀態機用C語言怎麼實現。idea
首先咱們梳理下銀行自助存款機的一些狀態以及觸發這些狀態的事件:spa
一、ATM Machine State.net
-
Idle State
-
Card inserted State
-
Pin entered State
-
Option selected State
-
Amount entered State
二、ATM Machine Event
-
Insert card Event
-
Enter pin Event
-
Select option Event
-
Enter amount Event
-
Money Dispatch Event
下圖就是根據上圖狀態和事件,畫的一個狀態機跳轉圖:
最初時,ATM機將處於空閒狀態。當用戶插入卡時,它會更改其狀態並處理卡。卡處理後,ATM再次更改其狀態,並要求用戶輸入密碼。當用戶輸入密碼時,它會要求您進行選擇(餘額查詢,提款,存款),而後更改狀態並要求輸入金額或者其餘查詢,最後交易完成後點擊退出,就又從新回到了空閒狀態。
通常在C語言中,實現狀態機經常使用的方法有兩種:
-
使用條件語句(switch-case或if-else)
-
使用查詢表(二維數組或者結構體數組)
基於條件語句的狀態機
咱們使用if-else或switch case來檢查狀態和觸發了的事件。若是狀態組合和觸發的事件匹配,請執行事件處理程序以服務該服務並更新下一個狀態,這取決於檢查第一狀態或事件的要求。在下面的示例代碼中,我首先驗證狀態,而後檢查觸發的事件。若是有須要,能夠撤消該過程,能夠先檢查事件,而後再檢查狀態。
#include <stdio.h>typedef enum{ Idle_State, CardInsert_State, PinEntered_State, OptionSelected_State, AmountEntered_State,}eSystemState;typedef enum{ CardInsert_Event, PinEnter_Event, OptionSelection_Event, AmountEnter_Event, AmountDispatch_Event, }eSystemEvent;//deal with Amount Dispatch Event,return Idle_StateeSystemState AmountDispatch_Handler(void){ return Idle_State;}//deal with Card Insert Event,return CardInsert_StateeSystemState CardInsert_Handler(void){ return CardInsert_State;}//deal with Pin Enter Event,return PinEntered_StateeSystemState PinEnter_Handler(void){ return PinEntered_State;}//deal with Option Selection Event,return OptionSelected_StateeSystemState OptionSelection_Handler(void){ return OptionSelected_State;}//deal with Amount Enter Event,return AmountEntered_StateeSystemState AmountEnter_Handler(void){ return AmountEntered_State;}int main(void){ eSystemState eNextState = Idle_State; eSystemEvent eNewEvent; while(1) { eSystemEvent eNewEvent; //Read system Events,assume read from API code eNewEvent = ReadEvent(); switch(eNextState) { case Idle_State: { if(CardInsert_Event == eNewEvent) { eNextState = CardInsert_Handler(); } break; } case CardInsert_State: { if(PinEnter_Event == eNewEvent) { eNextState = PinEnter_Handler(); } break; } case PinEntered_State: { if(OptionSelection_Event == eNewEvent) { eNextState = OptionSelection_Handler(); } break; } case OptionSelected_State: { if(AmountEnter_Event == eNewEvent) { eNextState = AmountEnter_Handler(); } break; } case AmountEntered_State: { if(AmountDispatch_Event == eNewEvent) { eNextState = AmountDispatch_Handler(); } break; } default: { break; } } } return 0;}
基於查詢表的狀態機
這裏會建立一個二維數組,其中行下標和列下標分別表明狀態和事件,這個二維數組的初始化是由指定的初始化程序來初始化,這個初始化也是個騷操做,反正小編以前看數組初始化的代碼是沒有見過的,這裏先來個小例程,來給你們介紹下這個騷操做吧~通常初始化數組,常見的幾種以下:
#define Max_Size 3//method one,common init the all elem of array with zeroint Array[Max_Size] = {0};//method two,common init the all elem of array with zeroint Array[Max_Size];void Init_Array(void){ for(int i = 0; i < Max_Size; i++) { Array[i] = 0; }}//method three,common init arrayint Array[Max_Size] = {1, 2,};int Array[Max_Size] = {1, 2, 0};int Array[] = {1, 2, 0};
這些初始化的結果,只要學了C語言的,應該都會容易的看出來,下面來點騷操做吧~怎麼直接初始化數組某個特定元素的值?
#define Max_Size 3#define Value 3//method oneint a[3] = {[0] = Value};//method twoint test_func(void){ return Value;}int a[3] = {[0] = test_func()};int main(void){ for(int i = 0; i < Max_Size; i++) { printf("a[%d] = %d\n", i, a[i]); } return 0;}
猜猜打印到輸出的結果是什麼?
a[0] = 3
a[1] = 0
a[2] = 0
--------------------------------
Process exited after 0.152 seconds with return value 0
請按任意鍵繼續. . .
以上就是怎麼初始化數組指定元素的值,下面用查詢表設計的狀態機中的二維數組也會用到該種方法來初始化,來直接上代碼。
#include <stdio.h>//Different state of ATM machinetypedef enum{ Idle_State, Card_Inserted_State, Pin_Eentered_State, Option_Selected_State, Amount_Entered_State, last_State} eSystemState; //Different type eventstypedef enum{ Card_Insert_Event, Pin_Enter_Event, Option_Selection_Event, Amount_Enter_Event, Amount_Dispatch_Event, last_Event} eSystemEvent; //typedef of 2d arraytypedef eSystemState (*const afEventHandler[last_State][last_Event])(void); //typedef of function pointertypedef eSystemState (*pfEventHandler)(void); //function call to dispatch the amount and return the ideal stateeSystemState AmountDispatchHandler(void){ return Idle_State;} //function call to Enter amount and return amount enetered stateeSystemState EnterAmountHandler(void){ return Amount_Entered_State;} //function call to option select and return the option selected stateeSystemState OptionSelectionHandler(void){ return Option_Selected_State;} //function call to enter the pin and return pin entered stateeSystemState EnterPinHandler(void){ return Pin_Eentered_State;} //function call to processing track data and return card inserted stateeSystemState InsertCardHandler(void){ return Card_Inserted_State;} int main(void){ eSystemState eNextState = Idle_State; eSystemEvent eNewEvent; // Table to define valid states and event of finite state machine static afEventHandler StateMachine = { [Idle_State] ={[Card_Insert_Event]= InsertCardHandler()}, [Card_Inserted_State] ={[Pin_Enter_Event] = EnterPinHandler() }, [Pin_Eentered_State] ={[Option_Selection_Event] = OptionSelectionHandler()}, [Option_Selected_State] ={[Amount_Enter_Event] = EnterAmountHandler()}, [Amount_Entered_State] ={[Amount_Dispatch_Event] = AmountDispatchHandler()}, }; while(1) { // assume api to read the next event eSystemEvent eNewEvent = ReadEvent(); //Check NULL pointer and array boundary if( ( eNextState < last_State) && (eNewEvent < last_Event) && StateMachine[eNextState][eNewEvent]!= NULL) { // function call as per the state and event and return the next state of the finite state machine eNextState = (*StateMachine[eNextState][eNewEvent])(); } else { //Invalid } } return 0;}
這裏的查詢表是稀疏的,從初始化的代碼裏面也能夠看出,若是狀態和事件在增長,這種建立方法會增長內存的浪費。所以,在建立狀態機以前,咱們須要在設計開始時就很是準確地說明全部事情。
狀態機之結構體數組設計
這種方法的設計,是能夠將狀態和事件封裝在結構體裏面(這個部分的處理能夠看看我以前的推文數據處理技巧及結構體數組的巧用),並在合適的狀態和事件條件下調用函數指針。
#include <stdio.h>//Different state of ATM machinetypedef enum{ Idle_State, Card_Inserted_State, Pin_Eentered_State, Option_Selected_State, Amount_Entered_State, last_State} eSystemState; //Different type eventstypedef enum{ Card_Insert_Event, Pin_Enter_Event, Option_Selection_Event, Amount_Enter_Event, Amount_Dispatch_Event, last_Event} eSystemEvent; //typedef of function pointertypedef eSystemState (*pfEventHandler)(void); //structure of state and event with event handlertypedef struct{ eSystemState eStateMachine; eSystemEvent eStateMachineEvent; pfEventHandler pfStateMachineEvnentHandler;} sStateMachine; //function call to dispatch the amount and return the ideal stateeSystemState AmountDispatchHandler(void){ return Idle_State;} //function call to Enter amount and return amount entered stateeSystemState EnterAmountHandler(void){ return Amount_Entered_State;} //function call to option select and return the option selected stateeSystemState OptionSelectionHandler(void){ return Option_Selected_State;} //function call to enter the pin and return pin entered stateeSystemState EnterPinHandler(void){ return Pin_Eentered_State;} //function call to processing track data and return card inserted stateeSystemState InsertCardHandler(void){ return Card_Inserted_State;} //Initialize array of structure with states and event with proper handlersStateMachine asStateMachine [] ={ {Idle_State,Card_Insert_Event,InsertCardHandler()}, {Card_Inserted_State,Pin_Enter_Event,EnterPinHandler()}, {Pin_Eentered_State,Option_Selection_Event,OptionSelectionHandler()}, {Option_Selected_State,Amount_Enter_Event,EnterAmountHandler()}, {Amount_Entered_State,Amount_Dispatch_Event,AmountDispatchHandler()}}; //main functionint main(void){ eSystemState eNextState = Idle_State; while(1) { //Api read the event eSystemEvent eNewEvent = read_event(); if((eNextState < last_State) && (eNewEvent < last_Event)&& (asStateMachine[eNextState].eStateMachineEvent == eNewEvent) && (asStateMachine[eNextState].pfStateMachineEvnentHandler != NULL)) { // function call as per the state and event and return the next state of the finite state machine eNextState = (*asStateMachine[eNextState].pfStateMachineEvnentHandler)(); } else { //Invalid } } return 0;}