函數指針方法實現簡單狀態機(附代碼)

2021-12-23 21ic電子網

之前寫過一篇狀態機的實用文章,很多朋友說有幾個地方有點難度不易理解,今天給大家換種簡單寫法,使用函數指針的方法實現狀態機。

狀態機簡介

有限狀態機FSM是有限個狀態及在這些狀態之間的轉移和動作等行為的數學模型,是一種邏輯單元內部的高效編程方法,可以根據不同狀態或者消息類型進行相應的處理邏輯,使得程序邏輯清晰易懂。

函數指針實現FSM

使用函數指針實現FSM可以分為3個步驟

代碼實現步驟
typedef enum {
  state_1=1,
  state_2,
  state_3,
  state_4
}State;

typedef enum{
  event_1=1,
  event_2,
  event_3,
  event_4,
  event_5
}EventID;

typedef struct
{
    int event;   //事件
    int CurState;  //當前狀態
    void (*eventActFun)();  //函數指針
    int NextState;  //下一個狀態
}StateTable;

void f121()
{
    printf("this is f121\n");
}
void f221()
{
    printf("this is f221\n");
}
void f321()
{
    printf("this is f321\n");
}

void f122()
{
    printf("this is f122\n");
}

StateTable fTable[] =
{
    //{到來的事件,當前的狀態,將要要執行的函數,下一個狀態}
    { event_1,  state_1,    f121,  event_2 },
    { event_2,  state_2,    f221,  event_3 },
    { event_3,  state_3,    f321,  event_4 },
    { event_4,  state_4,    f122,  event_1 },
    //add your code here
};

/*狀態機類型*/
typedef struct {
    int curState;//當前狀態
    StateTable * stateTable;//狀態表
    int size;//表的項數
}fsmType;

/*狀態機註冊,給它一個狀態表*/
void fsmRegist(fsmType* pFsm, StateTable* pTable)
{
    pFsm->stateTable = pTable;
}

/*狀態遷移*/
void fsmStateTransfer(fsmType* pFsm, int state)
{
    pFsm->curState = state;
}

/*事件處理*/
void fsmEventHandle(fsmType* pFsm, int event)
{
    StateTable* pActTable = pFsm->stateTable;
    void (*eventActFun)() = NULL;  //函數指針初始化為空
    int NextState;
    int CurState = pFsm->curState;
    int maxNum = pFsm->size;
    int flag = 0; //標識是否滿足條件

    /*獲取當前動作函數*/
    for (int i = 0; i<maxNum; i++)
    {
        //若且唯若當前狀態下來個指定的事件,我才執行它
        if (event == pActTable[i].event && CurState == pActTable[i].CurState)
        {
            flag = 1;
            eventActFun = pActTable[i].eventActFun;
            NextState = pActTable[i].NextState;
            break;
        }
    }


    if (flag) //如果滿足條件了
    {
        /*動作執行*/
        if (eventActFun)
        {
            eventActFun();
        }

        //跳轉到下一個狀態
        fsmStateTransfer(pFsm, NextState);
    }
    else
    {
        printf("there is no match\n");
    }
}

附代碼

代碼直接複製過去就行啦,本想打包的,太麻煩了。

測試程序
//編譯器:http://www.dooccn.com/cpp/
//來源:技術讓夢想更偉大
//作者:李肖遙
#include <stdio.h>

typedef enum {
  state_1=1,
  state_2,
  state_3,
  state_4
}State;

typedef enum{
  event_1=1,
  event_2,
  event_3,
  event_4,
  event_5
}EventID;

typedef struct {
    int event;   //事件
    int CurState;  //當前狀態
    void (*eventActFun)();  //函數指針
    int NextState;  //下一個狀態
}StateTable;

void f121()
{
    printf("this is f121\n");
}
void f221()
{
    printf("this is f221\n");
}
void f321()
{
    printf("this is f321\n");
}

void f122()
{
    printf("this is f122\n");
}

StateTable fTable[] =
{
    //{到來的事件,當前的狀態,將要要執行的函數,下一個狀態}
    { event_1,  state_1,    f121,  event_2 },
    { event_2,  state_2,    f221,  event_3 },
    { event_3,  state_3,    f321,  event_4 },
    { event_4,  state_4,    f122,  event_1 },
    //add your code here
};

/*狀態機類型*/
typedef struct {
    int curState;//當前狀態
    StateTable * stateTable;//狀態表
    int size;//表的項數
}fsmType;

/*狀態機註冊,給它一個狀態表*/
void fsmRegist(fsmType* pFsm, StateTable* pTable)
{
    pFsm->stateTable = pTable;
}

/*狀態遷移*/
void fsmStateTransfer(fsmType* pFsm, int state)
{
    pFsm->curState = state;
}

/*事件處理*/
void fsmEventHandle(fsmType* pFsm, int event)
{
    StateTable* pActTable = pFsm->stateTable;
    void (*eventActFun)() = NULL;  //函數指針初始化為空
    int NextState;
    int CurState = pFsm->curState;
    int maxNum = pFsm->size;
    int flag = 0; //標識是否滿足條件

    /*獲取當前動作函數*/
    for (int i = 0; i<maxNum; i++)
    {
        //若且唯若當前狀態下來個指定的事件,我才執行它
        if (event == pActTable[i].event && CurState == pActTable[i].CurState)
        {
            flag = 1;
            eventActFun = pActTable[i].eventActFun;
            NextState = pActTable[i].NextState;
            break;
        }
    }


    if (flag) //如果滿足條件了
    {
        /*動作執行*/
        if (eventActFun)
        {
            eventActFun();
        }

        //跳轉到下一個狀態
        fsmStateTransfer(pFsm, NextState);
    }
    else
    {
        printf("there is no match\n");
    }
}

int main()
{
    fsmType pType;
    fsmRegist(&pType,fTable);
    pType.curState = state_1;
    pType.size = sizeof(fTable)/sizeof(StateTable);


    printf("init state:%d\n\n",pType.curState);

    fsmEventHandle(&pType,event_1);
    printf("state:%d\n\n",pType.curState);

    fsmEventHandle(&pType,event_2);
    printf("state:%d\n\n",pType.curState);

    fsmEventHandle(&pType,event_3);
    printf("state:%d\n\n",pType.curState);

    fsmEventHandle(&pType,event_4);
    printf("state:%d\n\n",pType.curState);

    fsmEventHandle(&pType,event_2);
    printf("state:%d\n\n",pType.curState);

    return 0;
}

編譯結果總結

使用函數指針實現的FSM的過程還是比較費時費力的,但是這一切相對一大堆的if/else、switch/case來說都是值得的,當你的程序規模變得越來越大的時候,基於這種表結構的狀態機,維護程序起來會清晰很多。

相關焦點

  • C語言函數指針之回調函數
    我的理解是:把一段可執行的代碼像參數傳遞那樣傳給其他代碼,而這段代碼會在某個時刻被調用執行,這就叫做回調如果代碼立即被執行就稱為同步回調,如果過後再執行,則稱之為異步回調回調函數就是一個通過調用的函數。
  • 狀態機的三種騷操作,值得你了解
    狀態機的實現無非就是 3 個要素:狀態、事件、響應。轉換成具體的行為就 3 句話。用 C 語言實現狀態機主要有 3 種方法:switch—case 法、表格驅動法、函數指針法。前面說過,表格驅動法可以把狀態機調度的部分做成標準統一的框架代碼,這個框架適用性極強, 不管用狀態機來實現什麼樣的應用, 框架代碼都不需要做改動, 我們只需要根據實際應用場合規劃好狀態轉換圖,然後將圖中的各個要素(狀態、事件、動作、遷移,有關「條件」要素一會兒再說)用代碼實現就行了,我把這部分代碼稱作應用代碼
  • 如何正確的理解指針和結構體指針、指針函數、函數指針這些東西?
    這裡簡單說了類型主要是為後面引出指針這個特殊性,在計算機中,將要運行的程序都保存在內存中,所有的程序中的變量其實就是對內存的操作。計算機的內存結構較為簡單,這裡不詳細談論內存的物理結構,只談論內存模型。
  • 帶參數的無返回值(void)函數如何返回處理結果?(附代碼)
    在軟體開發過程中有一種很常見的編程方法,就是即使將參數傳給無返回值的void函數也能實現對原始參數值的修改,這句話到底說的什麼意思,且看示例。(附代碼)這是一個帶參數的普通函數,作用是統計字符串中的有效字符數('\0'不算有效字符),返回值即為字符串中的字符總數,類型為int,具體代碼入下:
  • 函數指針和指針函數還分不清?還不懂來找我
    函數指針和指針函數,在C語言算是比較重要和基礎,但是還是很多人分不清。現在分享下我的理解。它們之間的區分代碼案例為什麼要用函數指針函數指針的應用    指針函數和函數指針,說白了,一個是函數一個是指針。
  • 如何快速理解函數指針與回調函數?
    摘要:前面分享了關於指針和結構體使用過程,今天是同系列的函數指針和回調函數。函數指針是指向函數的指針變量。通過函數指針C語言可以實現各種強大的功能與設計方法。指向函數的指針被稱作是函數指針。通過函數指針,我們可以靈活的調用各種形式相同,但是功能不同的函數這樣做大大的增加了代碼的靈活程度。
  • JavaScript狀態模式及狀態機模型
    :先來看看最簡單的一個動作的簡單實現:class Contra {  constructor () {    //存儲當前待執行的動作    this.lastAct = {};  }  //執行動作  contraGo (act){    if(act === '
  • 一種基於指針函數的狀態控制策略
    今天來聊一下狀態機。最早我在AT指令集裡面推薦過一種方式,採用的方式是switch加共同體的方式來進行。在這個時候,你需要採用指針函數的狀態機來完成這個事情了。用他相對而言更加的輕鬆。自己只需要專注於各個業務動作驗證,然後通過指針函數將各個業務串聯起來即可。指針函數結構體我想到的如下圖:
  • ...一文弄懂「函數指針數組」,為什麼不直接調用函數,而是通過指針...
    可能一些初學者會認為輸出 val=0x3,但是將這段代碼實際編譯運行,得到的輸出卻是:# gcc t.c# ./a.out val = 0x1怎麼回事呢?原因當然是簡單的,這個問題仍然可以用函數形參和實參的關係解釋,很基礎。但是有些初學者還會迷惑,「這可是指針!」其實,這就是將C語言中的指針「特殊化」了。
  • C語言編程:以實例教你學指向函數的指針
    指針是C語言的精髓,對於初學者來講,指針是C語言語法學習中比較難的知識點,而這裡面指向函數的指針更是不太容易理解。下面給大家講下怎樣學習理解C語言中指向函數的指針及編程方法和使用例子。這樣對比著理解,指向函數的指針似乎與普通指針區別也不是太大。指向函數的指針編程例子下面通過一個例子演示指向函數的指針的使用方法。
  • 淺談狀態機
    簡單來說,狀態機主要是用來描述事物或者事物間的狀態以及轉換的,舉個例子,現實的事物燈泡,一般來說只有兩個狀態:亮起和熄滅,燈泡的亮起和熄滅取決於開關這個事件,所以這個燈泡抽象成狀態機應該是如下圖所示:這就引出了狀態機中幾個比較重要的概念:狀態、事件、動作、轉換。
  • 單片機之狀態機淺談
    狀態機的表示狀態機的表示要領有許多種,我們可以用文字、圖形或表格的形式來表示一個狀態機。舉個簡單的例子:就按鍵處理來說,擊鍵動作本身也可以看做一個狀態機。一個細小的擊鍵動作包含了:釋放、抖動、閉合、抖動和重新釋放等狀態。當我們打開思路,把狀態機作為一種思想導入到程序中去時,就會找到處理疑問的一條有效的捷徑。
  • 深入淺出剖析C語言函數指針與回調函數(一)
    百度的權威解釋如下:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
  • 成員函數指針的一些奇怪行為
    如果你只是使用單繼承,則指向成員函數的指針實際上是指向了這個函數的起始地址,因為在單繼承中,所有基類都共享了同一個this指針。我們看看下面的代碼:因為它們都使用了同一個this指針,一個指向基類成員函數的指針可以被當作是指向Derived2的成員函數指針來使用,不需要進行任何的轉換操作。
  • 狀態機設計原則:清晰!清晰!還是清晰!
    比如,當我們說要設計一個輸出字符串的狀態機,對很多人來說,首先出現在大腦中的不是一張狀態圖,而是類似如下函數的一個參考代碼://!原因主要有二:如果使用躍遷到第一個狀態的方法,則每一個躍遷都可能要重複去做類似初始化的工作——每多一條躍遷就多了一個重複的內容——這裡如果不是簡單複製粘貼的話,可能還會出現在漫長的代碼維護過程中出現「某些躍遷的動作與其它不一致」從而給自己挖坑的情況;使用reset可以確保狀態機復位,從而安全的從唯一的start點進入,完成統一的初始化動作。
  • 【基本無害】C/C++函數指針與金融衍生品定價
    所有信息的存儲,都在內存,計算機的內存主要是三大塊:棧空間、堆空間和全局空間,原則上還有第四塊空間,是專門用來存代碼信息的空間,也因為有第四塊空間的存在,才會有「函數指針」這種東西,也就是說,我們可以利用函數指針,找到存放那段函數代碼的地址,在找到存放那段函數代碼的地址後,知道了地址就可以操縱那個「函數」去幹活....其實最讓初學者難以理解的事情,為啥要如此「繞路」去找「那個函數
  • 小技巧:一種優雅的 C 語言函數指針的寫法
    上一篇文章中代碼很多,大家也許沒有注意到一個很巧妙的小技巧: 那就是 C 語言函數指針的寫法.在一次的代碼中的這樣的一行注釋 "很多同學不會寫函數指針聲明,函數指針的寫法是,先寫正常的函數聲明,然後將函數名加上括號,然後在函數名前再加上*號即可!!!
  • 認識一下Qt狀態機QStateMachine
    關於Qt狀態機的介紹就懶得說了,網絡上一搜一大堆,反正也看不懂。我關心的就是怎麼使用,畢竟我只是一個編寫應用程式的程式設計師。簡單粗暴地理解一下狀態機就是一個管理很多狀態的機器。組成一個最簡單的狀態機應該包括狀態機(QStateMachine)、狀態(QState)和過渡(QAbstractTransition子類)。狀態機就相當於一個容器,過渡就是將某一個狀態切換到另一個狀態(當然也可以不切換)。
  • C語言的那些小秘密之函數指針
    本文引用地址:http://www.eepw.com.cn/article/270442.htm  函數是由執行語句組成的指令序列或者代碼,這些代碼的有序集合根據其大小被分配到一定的內存空間中,這一片內存空間的起始地址就成為函數的地址,不同的函數有不同的函數地址,編譯器通過函數名來索引函數的入口地址,為了方便操作類型屬性相同的函數,c/c++引入了函數指針,函數指針就是指向代碼入口地址的指針
  • 深入淺出剖析C語言函數指針與回調函數
    回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。