前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >这个编程技巧别说我没告诉你

这个编程技巧别说我没告诉你

作者头像
编程珠玑
发布2019-11-25 16:26:00
4120
发布2019-11-25 16:26:00
举报
文章被收录于专栏:编程珠玑编程珠玑

来源:公众号【编程珠玑】

作者:守望先生

ID:shouwangxiansheng

前言

有读者在后台留言说用c写一篇有限状态机的推文,正好之前也用过,就分享一下吧。

背景

先举一个简单的例子,假设是这样的,一个小孩有两种状态,睡眠,清醒。睡的时候可能会撒尿,微笑,撒尿之后会转为清醒状态,而清醒的时候可能会笑,会吃,吃完之后会转会睡眠状态 用C语言实现,一般写法可能是这样的:

代码语言:javascript
复制
//来源:公众号【编程珠玑】
#include <stdio.h>
enum KID_STATUS
{
    SLEEP = 0,
    WAKE = 1,
    INVALID = 2
};
enum KID_ACTION
{
    SMILE = 0,
    EAT = 1,
    PEE = 2
};
/*孩子当前状态*/
static int nowStatus = WAKE;

/*孩子行为*/
int smile(int status)
{
    printf("kid smile\n");
    return status;
}
int eat(int status)
{
    printf("kid eat\n");
    return SLEEP;
}
int pee(int status)
{
    printf("kid pee\n");
    return WAKE;
}
/*孩子产生某个行为*/
void execute(int action)
{
    int nextStatus;
    /*醒着时的行为*/
    if(WAKE == nowStatus)
    {
        switch(action)
        {
            case SMILE:
            {
                nextStatus = smile(nowStatus);
                break;
            }
            case EAT:
            {
                nextStatus = eat(nowStatus);
                break;
            }
            case PEE:
            {
                nextStatus = pee(nowStatus);
                break;
            }
            default:
                printf("unknow action when wake %d\n",action);
                nextStatus = nowStatus;
                break;
        }
    }
    /*睡着时的行为*/
    else if(SLEEP == nowStatus)
    {
        switch(action)
        {
            case SMILE:
            {
                nextStatus = smile(nowStatus);
                break;
            }
            casePEE:
            {
                nextStatus = pee(nowStatus);
                break;
            }
            default:
                printf("unknow action when sleep %d\n",action);
                nextStatus = nowStatus;
                break;
        }
    }
    else
    {
        printf("unknow action\n");
    }
    nowStatus = nextStatus;
    printf("next status is %d\n",nowStatus);
}
int main(void)
{
    nowStatus = SLEEP;
    execute(EAT);
    return 0;
}

这段代码的意图就非常明显了,首先判断当前所在状态, 然后找到其中的行为,最后执行。 但是这段代码有以下几个特点:

  • 新加一种行为需要修改execute函数
  • 新加一种行为需要增加更多分支代码
  • 新加一种状态,需要新增一个大的分支
  • 哪些状态有哪些行为不是很明显

换一种写法

在《高级指针话题-函数指针》中介绍了函数指针,以及在《编程技巧-跳转表》介绍了函数跳转表。这里我们把代码调整一下,看看结合跳转表和状态机,能写出什么样的代码。

代码语言:javascript
复制
//来源:公众号【编程珠玑】地址:https://www.yanbinghu.com
#include <stdio.h>
/*省略枚举定义和函数定义,与前面相同*/
typedef int (*Handler)(int);//函数指针
/*用于处理某种状态的行为*/
typedef struct Act_Handler
{
    int action;
    Handler handler;
}Act_Handler;
/*某种状态的处理集*/
typedef struct Stat_Handler
{
    int status;
    int actNum;
    Act_Handler *actHandler;
}Stat_Handler;
/*sleep状态行为处理*/
Act_Handler sleepHandler[] = 
{
    {SMILE,smile},
    {PEE,pee}
};
/*wake状态行为处理*/
Act_Handler weakHandler[] = 
{
    {SMILE,smile},
    {PEE,pee},
    {EAT,eat}
};
/*状态处理*/
static Stat_Handler statHandler[] = 
{
    {SLEEP,sizeof(sleepHandler)/sizeof(Act_Handler),sleepHandler},
    {WAKE,sizeof(weakHandler)/sizeof(Act_Handler),weakHandler},
};
static int statSize = sizeof(statHandler)/ sizeof(Stat_Handler);
void execute(int action)
{
    if(nowStatus >= statSize)
    {
        printf("unknow status\n");
        return;
    }
    printf("now status is %d,action %d\n",nowStatus,action);
    int nextStatus = nowStatus;
    Act_Handler *actHandler = statHandler[nowStatus].actHandler;
    int actNum = statHandler[nowStatus].actNum;
    int actIdx = 0;
    /*遍历指定状态下的行为集,找到对应的行为*/
    for(actIdx = 0;actIdx < actNum;actIdx++)
    {
        if(actHandler[actIdx].action == action)
        {
            nextStatus = (actHandler[actIdx].handler)(action);
            break;
        }
    }
    if(actIdx == actNum)
    {
        printf("did find action %d in status %d\n",action,nowStatus);
    }
    nowStatus = nextStatus;
    printf("next status is %d\n",nowStatus);
}
int main(void)
{
    nowStatus = WAKE;
    execute(EAT);
    return 0;
}

这里简单说明一下execute函数中的执行流程。

  • 判断当前状态合法性
  • 在数组中找到对应状态的行为处理集
  • 在处理集中找到对应的行为
  • 处理结束

这种方式有什么特点呢?可以看到,在需要新加一个动作的时候,只需要在sleepHandler或者weakHandler中添加,完全不影响execute函数的改动。你甚至可以将不同状态分在不同的文件中管理,使得结构更加清晰明朗。

另外一方面,某种状态下,能执行哪些动作,非常清晰。

不过这样的写法对于初学者来说不太友好,但是不影响你添加新的内容。

有的读者可能会堪虑,在寻找行为的时候,for循环会不会很慢?首先这和case差别不大,通常不会有性能问题,其次除了使用数组,还可以考虑其他数据结构,例如哈希,我在《工作中用不到算法,为什么还要学算法?》中也提到了,数据结构和算法能更好地帮我们解决问题。

总结

本文代码较多,建议在实际环境中运行调试。完整可运行代码也可以阅读原文。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 背景
  • 换一种写法
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档