1. 程式人生 > >C語言入門--狀態機程式設計

C語言入門--狀態機程式設計

狀態機的好處不用多說,自己百度去,但傳統的程式設計模式,無論是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
請按任意鍵繼續. . .