C语言实现简单状态机

有限状态机(finite state machine)简称FSM,表示有限个状态及在这些状态之间的转移和动作等行为的数学模型。可以根据不同状态或者消息类型进行相应的处理逻辑,使得程序逻辑清晰易懂。
状态机实现的方式有多种,下面讲述三种.

1. 使用if/else语句实现的FSM

使用if/else if语句是实现的FSM最简单最易懂的方法,我们只需要通过大量的if /else if语句来判断状态值来执行相应的逻辑处理。
看看下面的例子:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

enum YEAR_STATE
{
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
};

static void SpringThing()
{
    printf("hello spring\n");
}

static void SummerThing()
{
    printf("hello summer\n");
}

static void AutumnThing()
{
    printf("hello autumn\n");
}

static void WinterThing()
{
    printf("hello winter\n");
}

int main()
{
    enum YEAR_STATE state = SPRING;

    while (1) {
        if (state == SPRING) {
            SpringThing(); // 相应的处理
            state = SUMMER; // 状态改变
        } else if (state == SUMMER) {
            SummerThing();
            state = AUTUMN;
        } else if (state == AUTUMN) {
            AutumnThing();
            state = WINTER;
        } else if (state == WINTER) {
            WinterThing();
            state = SPRING;
        }
        sleep(1);
    }

    return 0;
}

简单易懂,这里实现了四季的更替,因为只有四种状态,所以逻辑清楚,试想如果有个几十种状态,我们的if else将会变得十分之长,维护起来很麻烦,删减和添加状态变得不方便,但是通过这个例子我们认识到状态机的内涵。
如下图:



在状态1时,遇到一个事件,此刻发生状态转换,一般在状态转换前,先要进行事件的处理,然后改变状态位,然后进入状态2,以此类推。

2. 使用switch case

这种做法和if else类似,结构上更清楚一些,代码如下:

int main()
{
    enum YEAR_STATE state = SPRING;

    while (1) {
        switch (state) {
            case SPRING:
                SpringThing();
                state = SUMMER;
                break;
            case SUMMER:
                SummerThing();
                state = AUTUMN;
                break;
            case AUTUMN:
                AutumnThing();
                state = WINTER;
                break;
            case WINTER:
                WinterThing();
                state = SPRING;
                break;
            default:
                break;
        }

        sleep(1);
    }

    return 0;
}

3. 函数指针实现FSM,维护状态机表格

使用函数指针实现FSM的思路:建立状态机表,根据状态、事件定位相应的动作处理函数,执行完成后再进行状态的切换。

当然使用函数指针实现的FSM的过程还是比较费时费力,但是这一切都是值得的,因为当你的程序规模大时候,基于这种表结构的状态机,维护程序起来也是得心应手。

首先我们画出这个表



实现过程:


1.定义状态的枚举类型

typedef enum {
    SPRING = 1,
    SUMMER,
    AUTUMN,
    WINTER,
} YEAR_STATE;

2.定义事件的枚举类型

typedef enum {
    EVENT1 = 1,
    EVENT2,
    EVENT3,
    EVENT4,
} YEAR_EVENT;

3.定义状态表的数据类型

typedef struct {
    YEAR_EVENT event;           // 事件
    YEAR_STATE curState;        // 当前状态
    void (*eventActFun)();      // 执行的动作
    YEAR_STATE nextState;       // 下一个状态
} FsmTable;

4.定义处理函数和建立状态表

static void SprintThing()
{
    printf("this is spring\n");
}

static void SummerThing()
{
    printf("this is summer\n");
}

static void AutumnThing()
{
    printf("this is autumn\n");
}

static void WinterThing()
{
    printf("this is winter\n");
}

static FsmTable g_yeadSwitchTable[] = {
    /* 到来的事件,当前的状态,将要要执行的函数,下一个状态 */
    { EVENT1, SPRING, SummerThing, SUMMER },
    { EVENT2, SUMMER, AutumnThing, AUTUMN },
    { EVENT3, AUTUMN, WinterThing, WINTER },
    { EVENT4, WINTER, SprintThing, SPRING },
};

5.状态机工作运转

/* 具体状态机工作实现状态切换的数据类型 */
typedef struct {
    YEAR_STATE curState;   // 当前状态
    FsmTable *table;       // 状态表
    uint32_t tableSize;    // 表的项数
} FsmWork;

/* 事件处理 */
static void FsmEventProcess(FsmWork *fsm, YEAR_EVENT event)
{
    uint32_t i = 0;
    for (; i < fsm->tableSize; i++) {
        /* 当且仅当当前状态下来了指定的事件,我才执行它 */
        if ((event == fsm->table[i].event) &&
            (fsm->curState == fsm->table[i].curState)) {
            fsm->table[i].eventActFun();
            /* 然后修改当前状态为匹配到项的指定下一个状态  */
            fsm->curState = fsm->table[i].nextState; 
            break;
        }
    }

    if (i >= fsm->tableSize) {
        printf("there is no match\n");
    }
    return;
}

测试程序代码为:

/* state.c */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

typedef enum {
    SPRING = 1,
    SUMMER,
    AUTUMN,
    WINTER,
} YEAR_STATE;

typedef enum {
    EVENT1 = 1,
    EVENT2,
    EVENT3,
    EVENT4,
} YEAR_EVENT;

typedef struct {
    YEAR_EVENT event;           // 事件
    YEAR_STATE curState;        // 当前状态
    void (*eventActFun)();      // 执行的动作
    YEAR_STATE nextState;       // 下一个状态
} FsmTable;

static void SprintThing()
{
    printf("this is spring\n");
}

static void SummerThing()
{
    printf("this is summer\n");
}

static void AutumnThing()
{
    printf("this is autumn\n");
}

static void WinterThing()
{
    printf("this is winter\n");
}

static FsmTable g_yeadSwitchTable[] = {
    /* 到来的事件,当前的状态,将要要执行的函数,下一个状态 */
    { EVENT1, SPRING, SummerThing, SUMMER },
    { EVENT2, SUMMER, AutumnThing, AUTUMN },
    { EVENT3, AUTUMN, WinterThing, WINTER },
    { EVENT4, WINTER, SprintThing, SPRING },
};

/* 具体状态机工作实现状态切换的数据类型 */
typedef struct {
    YEAR_STATE curState;   // 当前状态
    FsmTable *table;       // 状态表
    uint32_t tableSize;    // 表的项数
} FsmWork;

/* 事件处理 */
static void FsmEventProcess(FsmWork *fsm, YEAR_EVENT event)
{
    uint32_t i = 0;
    for (; i < fsm->tableSize; i++) {
        /* 当且仅当当前状态下来了指定的事件,我才执行它 */
        if ((event == fsm->table[i].event) &&
            (fsm->curState == fsm->table[i].curState)) {
            fsm->table[i].eventActFun();
            /* 然后修改当前状态为匹配到项的指定下一个状态  */
            fsm->curState = fsm->table[i].nextState; 
            break;
        }
    }

    if (i >= fsm->tableSize) {
        printf("there is no match\n");
    }
    return;
}


int main()
{
    FsmWork myWorkfsm = {
        .curState = SPRING,
        .table = g_yeadSwitchTable,
        .tableSize = sizeof(g_yeadSwitchTable) / sizeof(g_yeadSwitchTable[0])
    };

    printf("\n-------1--init spring------\n");
    printf("state:%d\n", myWorkfsm.curState);

    printf("\n-------2--spring->summer------\n");
    FsmEventProcess(&myWorkfsm, EVENT1);
    printf("state:%d\n", myWorkfsm.curState);

    printf("\n-------3--summer->autumn------\n");
    FsmEventProcess(&myWorkfsm, EVENT2);
    printf("state:%d\n", myWorkfsm.curState);

    printf("\n-------4--autumn->winter------\n");
    FsmEventProcess(&myWorkfsm, EVENT3);
    printf("state:%d\n", myWorkfsm.curState);

    printf("\n-------5--winter->spring------\n");
    FsmEventProcess(&myWorkfsm, EVENT4);
    printf("state:%d\n", myWorkfsm.curState);

    printf("\n-------6--receive EVENT2 not EVENT1------\n");
    FsmEventProcess(&myWorkfsm, EVENT2);
    printf("state:%d\n", myWorkfsm.curState);

    return 0;
}

结果为:

-------1--init spring------
state:1

-------2--spring->summer------
this is summer
state:2

-------3--summer->autumn------
this is autumn
state:3

-------4--autumn->winter------
this is winter
state:4

-------5--winter->spring------
this is spring
state:1

-------6--receive EVENT2 not EVENT1------
there is no match
state:1
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 14,742评论 0 38
  • 『代码github地址』 标签: 有限状态机,Akka fsm,squirrel-foundation,java状...
    醉叁重阅读 30,317评论 3 24
  • C++是一门非常适合用来构建DSL(Domain Specific Language)的语言,它的多范式特点为它提...
    MagicBowen阅读 12,738评论 5 14
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,953评论 18 399
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 8,811评论 3 44