• C专家编程 第8章 为什么程序员无法分清万圣节和圣诞节 8.7 用C语言实现有限状态机


    用C语言实现有限状态机 
    有限状态机(Finite State Machine, FSM)是一个数学概念。它是一种协议,用于有限数量的子程序(状态)的发展变化。每个子程序进行一些处理并选择下一种状态。(通常取决于下一段输入)

    FSM可以用作程序的控制结构。FSM对于那些以输入为基础,在几个不同的可选动作中进行循环的程序尤其合适。投币售货机就是一个FSM的好例子。

    它的基本思路是用一张表保存所有可能的状态,并列出进入每个状态时可能执行的所有动作,其中最后一个动作就是计算(通常在当前状态和下一次输入字符的基础上,另外再经过一次表查询)下一个应该进入的状态。你从一个“初始状态”开始。在这一过程中,翻译表可能会告诉你进入了一个错误的状态,表示一个预期之外的或错误的输入。你不停地在各种状态间进行切换,直到到达结束状态。 

    在C语言中,有好几种方法可以用来表达FSM,但它们绝大多数都是基于函数指针数组。一个函数指针数组可以像下面这样声明:
    void (*state[MAX_STATES])();
    如果知道了函数名,就可以像下面这样对数组进行初始化:
    extern int a(), b(), c(), d();
    int (*state[])() = {a, b, c, d};
    可以通过数组中的指针来调用函数:
    (*state[i])();
    所有的函数必须接受同样的参数,并返回同种类型的返回值(除非把数组元素做成一个联合)。函数指针是很有趣的。注意,我们甚至可以去掉指针形式,把上面的调用写成:
    state[i]();
    甚至是:
    (******state[i]) ();
    这是一个在ANSI C中流行的不良方法:调用函数和通过指针调用函数(或任意层次的指针间接引用)可以使用同一种语法。

    /*用FSM实现cdecl*/
    #include
    #include
    #include
    #include
    #define MAXTOKENS 100
    #define MAXTOKENLEN 64
    enum type_tag {
        IDENTIFIER, QUALIFIER, TYPE
    }; 

    struct token {
        char type;
        char string[MAXTOKENLEN];
    };

    int top = -1;
    /*在第一个标识符(identifier)前保存所有的标记(token)*/
    struct token stack[MAXTOKENS];
    /*保存刚读入的标记*/
    struct token this_2;
    #define pop stack[top--]
    #define push(s) stack[++top] =  s
    enum type_tag classify_string(void) /*标识标识符的类型*/
    {
        char *s = this_2.string;
        if (!strcmp(s, "const")) {
            strcpy(s, "read_only ");
            return QUALIFIER;
        }
        if (!strcmp(s, "volatile")) {
            return QUALIFIER;
        }
        if (!strcmp(s, "void")) {
            return TYPE;
        }
        if (!strcmp(s, "char")) {
            return TYPE;
        }
        if (!strcmp(s, "signed")) {
            return TYPE;
        }
        if (!strcmp(s, "unsigned")) {
            return TYPE;
        }
        if (!strcmp(s, "short")) {
            return TYPE;
        }
        if (!strcmp(s, "int")) {
            return TYPE;
        }
        if (!strcmp(s, "long")) {
            return TYPE;
        }
        if (!strcmp(s, "float")) {
            return TYPE;
        }
        if (!strcmp(s, "double")) {
            return TYPE;
        }
        if (!strcmp(s, "struct")) {
            return TYPE;
        }
        if (!strcmp(s, "union")) {
            return TYPE;
        }
        if (!strcmp(s, "enum")) {
            return TYPE;
        }
        return IDENTIFIER;    

    void gettoken(void) {
        /*读入下一个标记,保存在“this”中*/
        char *p = this_2.string;
        /*略过所有空白符*/
        while ((*p = getchar()) == ' ');
        if (isalnum(*p)) {
            /*在标识符中读取A-Z, 1-9字符*/
            while (isalnum(*++p = getchar()));
            ungetc(*p, stdin);
            *p = '\0';
            this_2.type = classify_string();
            return; 
        }
        this_2.string[1] = '\0';
        this_2.type =*p;
        return;
    }

    void initialize(), get_array(), get_params(), get_lparen(), get_ptr_part(), get_type();
    void (*nextstate)(void) = initialize;

    /*用有限机实现的cdecl */ 
    int main() {
        /*在不同的状态间转换,直到指针为null*/
        while (nextstate != NULL) {
            (*nextstate)();
        } 
        return 0;

    void initialize() {      
        gettoken();
        while (this_2.type != IDENTIFIER) {
            push(this_2);
            gettoken(); 
        } 
        printf("%s is ", this_2.string);
        gettoken();
        nextstate = get_array;  
    }

    void get_array() {      
        nextstate = get_params;   
        while (this_2.type == '[') {
            printf("array ");
            gettoken(); /*一个数字或者']'*/
            if (isdigit(this_2.string[0])) {
                printf("0..%d ", atoi(this_2.string) - 1);
                gettoken(); /*读取']'*/ 
            }
            gettoken(); /*在']'继续读取*/
            printf("of ");
            nextstate = get_lparen;
        }
    }

    void get_params() {   
        nextstate = get_lparen;
        if (this_2.type == '(') {
            while (this_2.type != ')') {
                gettoken();
            }
            gettoken();
            printf("function returning ");
        }
    }

    void get_lparen() {   
        nextstate = get_ptr_part;
        if (top >= 0) {
            if (stack[top].type == '(') {
                pop;
                gettoken(); /*在')'之后读取*/
                nextstate = get_array; 
            }
        }
    }

    void get_ptr_part() {  
        nextstate = get_type;
        if (stack[top].type == '*') {
            printf("pointer to ");
            pop;
            nextstate = get_lparen;
        } else if (stack[top].type == QUALIFIER) {
            printf("%s", pop.string);
            nextstate = get_lparen;
        }
    }

    void get_type() {    
        nextstate = NULL;
        /*处理在读入标识符之前被放在堆栈里的所有标记*/
        while (top >= 0) {
            printf("%s ", pop.string);
        } 
        printf("\n");
    }

    /* 输出:

    */ 

    如果想干得漂亮一些,可以让状态机函数返回一个指向通用后续函数的指针,并把它转换为适当的类型。这样,就不需要全局变量了。如果不想搞得太花哨,可以使用一个switch语句作为一个简朴的状态机,方法是赋值给控制变量并把switch语句放在循环内部。关于FSM还有一个最后一点需要说明:如果你的状态函数看上去需要多个不同的参数,可以考虑使用一个参数计数器和一个字符串指针数组,就像main函数的参数一样。我们熟悉的int argc、char *argv[]机制是非常普遍的,可以成功应用在你所定义的函数中。 

  • 相关阅读:
    TypeReference使用笔记
    web前端期末大作业 HTML+CSS+JavaScript仿京东
    83.(cesium之家)cesium示例如何运行
    大数据必学Java基础(七十七):线程的生命周期和常见方法
    Git与SSH
    Tomcat
    Feature and Instance Joint Selection: A Reinforcement Learning Perspective
    从头开始进行CUDA编程:线程间协作的常见技术
    Leetcode.213 打家劫舍 II
    单链表的简单使用
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126087848