轉載聲明 如果轉載本博客內容,請聯系[email protected],獲得作者書面授權
近來思緒有點停不下來,構思了一個GUI的框架(用在Cotex-M平台上,很小),期待以後有時間去實現,裡面有一個對觸摸屏的檢測,自然想到使用狀態機進行消息的生成和分發,於是想著實現一個狀態機實現的模型,以後再其他項目上應用也方便。
狀態機的好處不用多說,自己百度去,但傳統的編程模式,無論是C語言,或是硬件FPGA的Verilog都是采用switch-case結構,硬件的還好說,是並行的,但如果是C語言實現狀態機則可能需要對每個case進行判斷,狀態少比如幾個可能沒什麼效率之類的問題,但狀態多幾十個上百個呢,那麼就需要進行上百次的判斷是否匹配,毫無疑問效率很低,切每次的狀態切換時間也不確定。那麼有沒有一種好的實現模式不用switch-case結構呢?下面我來為C語言狀態機的實現建立一個最優規范。
前面說了一堆裝bi的廢話,下面進入正題,前段時間研究了下函數式編程,發現C語言的循環結構完全可以用尾遞歸(不懂百度)實現,最後C就只剩下唯有if和switch還不能用函數式編程實現,而今天要講的狀態機模式就是用函數式編程實現switch-case,那麼switch-case其實可以看做一種查找的跳轉,我們知道C語言中goto可以實現跳轉,那麼還有什麼可以實現跳轉呢,答案是函數調用,但是又需要如何實現指定的函數調用呢,難道要用if判斷,顯然沒什麼意義,那麼又沒有比if判斷(如果上百次判斷)更高效的東西呢?有人已經想到是查表,如果將查表和函數調用結合,那麼就是函數指針數組的應用了!
上代碼!首先定義一個函數指針類型,為什麼要帶void *的參數後面會說:
typedef unsigned char State;
typedef State(*Procedure)(void *);
這樣就可以方便地定義一個函數指針數組:
Procedure Steps[] = { step_init, step_count, step_done, step_default };
step_init,step_count等是函數名,再定義狀態:
enum states{ s_init, s_count, s_done, s_default };
枚舉定義對應著{0,1,2,3},有了這些再狀態機聯系那麼可以想到,數組的索引就是狀態定義,上核心代碼,兩行(簡單吧!關鍵是想到):
void BestStateMachine(void * invar)
{
static State NS = s_init; //定義下一狀態
NS = Steps[NS](invar);
}
static的變量NS在每次BestStateMachine調用會得到維護,我們只需再每Steps返回下一個狀態並保存到NS中可以實現狀態的保存和切換。再說說為什麼要加個void*的參數,狀態機一般有很多自身變量的維護,而且對於mealy狀態機還需根據輸入判斷,因為函數調用返回是不保留局部變量的,那麼就需要將變量傳遞來實現更改和保存,之所以只用了一個void*參數是因為,如果需要保存和傳遞的變量很多,直接傳遞會在調用函數是浪費大量的棧空間,且效率低下,采用這種模式,你可以將變量用一個結構體封裝,然後將結構體指針傳遞給void *的形參,再函數內部再強制轉換即可使用結構體內部的變量。現在你已經在嘀咕這作者真啰嗦,好上實例代碼,就是一個簡單的計數器(以前學狀態機都從計數器開始)
,在計數完成打印信息:
#include<stdio.h>
typedef unsigned char State;
typedef State(*Procedure)(void *);
enum states{ s_init, s_count, s_done, s_default };//狀態定義
typedef struct _SM_VAR //對狀態機參數封裝
{
int cnt;
}SM_VAR;
State step_init(void * arg)//初始化
{
SM_VAR *p = (SM_VAR *)arg;
p->cnt = 0;
printf("CS:init ;cnt=%d;NS:count\n", p->cnt);
return s_count;
}
State step_count(void * arg)//計數
{
SM_VAR *p = (SM_VAR *)arg;
if (p->cnt < 3){
p->cnt+=1;
printf("CS:count;cnt=%d;NS:count\n", p->cnt);
return s_count;
}
else{
printf("CS:count;cnt=%d;NS:done\n", p->cnt);
return s_done;
}
}
State step_done(void * arg)//計數完成
{
SM_VAR *p = (SM_VAR *)arg;
printf("CS:done ;cnt=%d;NS:init\n", p->cnt);
return s_init;
}
State step_default(void * arg)//錯誤過程
{
SM_VAR *p = (SM_VAR *)arg;
printf("Wrong State\n");
return s_init;
}
Procedure Steps[] = { step_init, step_count, step_done, step_default };
void BestStateMachine(void * invar)
{
static State NS = s_init; //定義下一狀態
NS = Steps[NS](invar);
}
int main(void)
{
SM_VAR var;
int i;
for (i = 0; i <8; i++){//給狀態機8個周期的時鐘驅動
BestStateMachine(&var);
}
return 0;
}
最後在VS2013上調試如下:
CS:init ;cnt=0;NS:count
CS:count;cnt=1;NS:count
CS:count;cnt=2;NS:count
CS:count;cnt=3;NS:count
CS:count;cnt=3;NS:done
CS:done ;cnt=3;NS:init
CS:init ;cnt=0;NS:count
CS:count;cnt=1;NS:count
請按任意鍵繼續. . .
以這種模式不僅可以實現上面的Moore型狀態機,還可根據實際實現Mealy型狀態機,結構清晰易懂,在這裡我敢說最優,是因為這是C函數式編程的極限了,如果你有更好的想法聯系我,郵箱:[email protected],唐童鞋。如果你對嵌入式GUI感興趣,我們可以一起探討寫出我們自己的GUI。