前言
功能業務代碼寫多了,回看代碼會發現用if-else if-else用的越來越多,因為有很多場景需要區分,不同場景下的功能不一樣,因此需要通過if做判斷,場景一旦多了起來起來,用if-else if-else就會越來越多。
這里的if-else if-else通常是用來區分不同場景下的功能實現,和上篇提到的表驅動編程還不太一樣。
為了避免這種情況,本編介紹一種開發方法--狀態機編程。
為什么需要狀態機編程?相信很多人在實現if判斷的時候,偶爾會出現不小心漏掉或者寫錯一些觸發條件,導致功能出現異常的問題,特別是一些復雜的邏輯條件,一大堆的&&/||/()等眼花繚亂的復雜邏輯,寫完之后恐怕自己都得檢查好一會,擔心是不是哪個條件不能正常觸發。狀態機通常采用?switch-case實現。
那么狀態機的引入解決了哪些問題呢?
當程序有多個狀態時,規范了程序的狀態轉換,避免了一些引入一些復雜的判斷邏輯。
規范了程序在不同狀態下的實現和所能提供的能力。
在能力上可以進行橫向擴展,提供新的狀態來完善現有邏輯
邏輯清楚,實現過程會多考慮一些情況,方便定位問題所在
介紹
什么是狀態機?
狀態機是有限狀態自動機(FSM)的簡稱,是現實事物運行規則抽象而成的一個數學模型。
簡單理解就是:現實事物是有不同狀態的,比如燈,就有“亮”和“滅”兩種狀態,再復雜抽象一點,增加一個“損壞”狀態,那這屬于異常情況了。
概念
現態:當前所處的狀態,一個狀態機至少要包含兩個狀態,某一時刻只能是一種狀態,比如剛才說到的燈,就有“亮”和“滅”兩種狀態
條件:又稱事件,執行某個操作的觸發條件或者口令,比如燈通過開關控制,操作開關就是一個事件
動作:事件發生以后要執行動作,比如開關按下開關,燈亮,松開則滅
次態:條件滿足后要遷往的新狀態,比如開關按下后燈從當前滅的狀態變為亮的狀態

用過"RTOS"的朋友應該也知道任務的幾種狀態,任務的四種狀態分別是:就緒態、運行態、阻塞態、掛起態,不會同時出現兩種及兩種以上的狀態存在,OS 根據當前的狀態,和任務優先級、滴答時鐘、主動睡眠等條件進行任務的狀態切換。
狀態機的動作類型
進入動作:在進入狀態時進行
退出動作:在退出狀態時進行
輸入動作:依賴于當前狀態 和 輸入條件進行
轉移動作:在進行特定轉移時進行
實現
首先,看一個簡單的例子,在不同場景下實現控制電機的功能:設備開機啟動三次電機、開關按下一次啟動一次、關機啟動三次電機。采用非狀態機的寫法,通過各種標志位去判斷設備是否需要控制電機,什么條件下退出等。
只是簡單的實現一下,可能其中也有一些狀態機的思想吧(畢竟狀態機編程思想已經在腦海里,不可避免吧),不過我還是盡量還原我初次編程期間的實現這個功能的邏輯思想吧,勿怪。
?
?
/*?控制電機函數?*/
void?MotorCtrlTask(void)
{
????if?(ctrlCnt)
????{
????????MotorCtrl(ON);
????????delay(1);
????????MotorCtrl(OFF);
????}
????else
????{
????????MotorCtrl(OFF);
????}
}
int?isPowerOn?=?true;
int?isPowerOff?=?false;
int?ctrlCnt?=?0;
void?main(void)
{???
????while?(1)
????{
????????if?(isPowerOn)
????????{
????????????isPowerOn?=?false;
????????????ctrlCnt?=?3;
????????}
????????if?(keyPress)
????????{
????????????keyPress?=?false;
????????????ctrlCnt?=?1;
????????}
????????if?(...)??//?關機條件
????????{
????????????if?(ctrlCnt?==?0?&&?!isPowerOff?&&?!isPowerOn)
????????????{
????????????????isPowerOff?=?true;
????????????????ctrlCnt?=?3;
????????????}
????????}
????????MotorCtrlTask();
????????if?(ctrlCnt?>?0)
????????????ctrlCnt--;
????????else
????????{
????????????if?(ctrlCnt?==?0?&&?isPowerOff?&&?!isPowerOn)
????????????{
????????????????return;
????????????}
????????}
????}
}
?
?
通過采用狀態機編程的方式,首先考慮的就是有三種狀態:開機、關機和工作狀態,先理清楚三種狀態之間轉換的條件和當前狀態需要執行的相關功能,然而在實現過程中就會意識到還需要增加一種過渡狀態:關機準備中(關機過程中需要執行的一系列操作)。
只有邏輯清晰了,才會下意識的察覺少了一些東西了,特別是一些臨界處理等。
?
?
int?sysState?=?POWER_OFF;?//?默認關機狀態
int?ctrlCnt?=?0;
/*?控制電機函數?*/
void?MotorCtrlTask(void)
{
????if?(ctrlCnt)
????{
????????MotorCtrl(ON);
????????delay(1);
????????MotorCtrl(OFF);
????}
????else
????{
????????MotorCtrl(OFF);
????}
}
void?main(void)
{
????while?(1)
????{
????????switch?(sysState)
????????{
????????????case?POWER_OFF:?//?關機狀態
????????????????sysState?=?POWER_ON;?//?自動切換成開機狀態
????????????????ctrlCnt?=?3;
????????????????break;
????????????case?POWER_ON:??//?開機過程狀態
????????????????...?//?開機過程中的其他功能
????????????????if?(ctrlCnt?==?0)?//?控制結束自動切換工作狀態
????????????????{
????????????????????sysState?=?WORKING;
????????????????????break;
????????????????}
????????????????break;
????????????case?WORKING:??//?工作狀態
????????????????if?(...)?//?關機條件
????????????????{
????????????????????sysState?=?POWER_OFF_READY;
????????????????????ctrlCnt?=?3;
????????????????????break;
????????????????}
????????????????if?(keyPress)
????????????????{
????????????????????keyPress?=?false;
????????????????????ctrlCnt?=?1;
????????????????}
????????????????break;
????????????case?POWER_OFF_READY:??//?關機準備中
????????????????...?//?關機準備中的其他功能
????????????????if?(ctrlCnt?==?0)?//?控制結束自動退出
????????????????{
????????????????????sysState?=?POWER_OFF;
????????????????????return;?//?退出程序
????????????????}
????????????????break;
????????????default:
????????????????break;
????????}
????????MotorCtrlTask();
????????if?(ctrlCnt?>?0)
????????????ctrlCnt--;
????}
}
總結:從上述兩份代碼看,你覺得哪一個邏輯更清晰呢?非狀態機實現方式還有一些異常處理沒有實現,比如開機過程中在啟動三次調節電機過程中按下會有什么現象呢,為了避免這種情況又需要加入多少if條件判斷呢?
適用場景
狀態機應用范圍挺廣的,不止是在C語言中,其他都能使用,準確來說這個屬于一種編程思想。特別是業務功能,狀態機是常用的。
比如常常使用的模塊也有狀態機的身影:比如按鍵的按下和松開,按下又包括按下瞬間、多次按下、持續按下、松開瞬間和持續松開等
通常狀態機和表驅動可以結合使用,狀態機的現態、條件、動作和次態作為數據,執行這些狀態切換的作為邏輯。完全可以根據實際情況靈活使用。
代碼參考:
菜單:菜單控制,可以這樣理解:當前菜單界面為現態、菜單進入和退出為條件、菜單切換時的函數執行為動作、上下級菜單理解為次態,其中菜單選項表就靈活使用了狀態機和表驅動的方式。
https://gitee.com/const-zpc/menu.git
ESP8266:AT指令數據表,包含指令、期望響應、時間和函數指針【后續的動作】等,可以這樣理解:數組索引當前值為現態、收到響應和超時為條件、收到響應或超時執行的函數為動作、數組索引的未來值為次態
https://gitee.com/const-zpc/esp8266
ESP8266:AT指令數據表,包含指令、期望響應、時間和函數指針【后續的動作】等,可以這樣理解:數組索引當前值為現態、收到響應和超時為條件、收到響應或超時執行.
編輯:黃飛
?
電子發燒友App



















評論