C语言状态机学习
来源:互联网 发布:note5网络锁 编辑:程序博客网 时间:2024/06/05 08:24
状态机的好处不用多说,自己百度去,但传统的编程模式,无论是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
探讨了一种非swtich-case结构的状态机写法,但是个人感觉写起来比较麻烦,如果增加一个状态,需要手动地在函数指针数组中添加相应的功能函数,而且状态函数的也必须写在函数指针数组前面导致代码结构较差,如果写在后面,又要在前面声明,就更麻烦了,总之,不易维护,想到Adam Dunkels在LwIP中逆天的宏定义用法,使得代码有自动更新的功能,于是也尝试着套用下其写法,现在这段代码更易维护,可做模板使用。
实现
还是上一篇博客的例子,只是多了些宏定义,这次先定义个状态列表:
#define STATE_LIST(_) \
_(init)\
_(count)\
_(done)\
_(dft)
可能你觉得奇怪,那么先别急,看下一段定义:
#define DEFINE_STATE(state) State##_##state,
enum States {
STATE_LIST(DEFINE_STATE)
State_Nums
};
#undef DEFINE_STATE
对于初学者可能已经晕了,其实你只需记住宏就是替换,就能理解,下面我先展开States中第一层(注意逗号不可少):
enum States {
DEFINE_STATE(init),
DEFINE_STATE(count),
DEFINE_STATE(done),
DEFINE_STATE(dft),
State_Nums
};
接下来根据DEFINE_STATE展开宏(##起连接Token的作用),则有:
enum States {
State_init,
State_count,
State_stop,
State_dft,
State_Nums
};
这样我们得到了一个完整的状态枚举,运用此方法可声明状态函数,并将声明的状态函数存入函数指针数组中,你的状态函数也不会有位置限制,完整代码如下:
#include<stdio.h>
typedef unsigned char State;
typedef State(*Procedure)(void *);
//状态列表,状态增减只在此处按格式写入
#define STATE_LIST(_) \
_(init)\
_(count)\
_(done)\
_(dft)
#define Step_NULL ((void *)0)
//状态执行函数声明,你的函数必须遵循如Step_init的格式
//并必须返回下一状态如State_init
#define STATEMENT_STEP(state) \
State Step##_##state(void * arg);
STATE_LIST(STATEMENT_STEP)
#undef STATEMENT_STEP
//状态枚举
#define DEFINE_STATE(state) State##_##state,
enum States {
STATE_LIST(DEFINE_STATE)
State_Nums
};
#undef DEFINE_STATE
//状态执行函数查找表
#define STATE_PROCEDURE(State) Step##_##State,
Procedure Steps[] = {
STATE_LIST(STATE_PROCEDURE)
Step_NULL
};
#undef STATE_PROCEDURE
typedef struct _SM_VAR //对状态机参数封装
{
int cnt;
}SM_VAR;
void BestStateMachine(void * invar)
{
static State NS = State_init; //定义下一状态
NS = Steps[NS](invar);
}
int main(void)
{
SM_VAR var;
int i;
for (i = 0; i <= 8; i++){
BestStateMachine(&var);
}
return 0;
}
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 State_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 State_count;
}
else{
printf("CS:count;cnt=%d;NS:done\n", p->cnt);
return State_done;
}
}
State Step_done(void * arg)//计数完成
{
SM_VAR *p = (SM_VAR *)arg;
printf("CS:done ;cnt=%d;NS:init\n", p->cnt);
return State_init;
}
State Step_dft(void * arg)//错误过程
{
SM_VAR *p = (SM_VAR *)arg;
printf("Wrong State\n");
return State_init;
}
在VS2013调试,输出结果和上一篇博客一样,你现在一定会发现代码修改变得异常简单,你只需在STATE_LIST定中增减状态,宏就会帮你完成后面一切,而且你的状态函数可以放在任意位置,唯一需要注意的是函数名必须遵循定义个格式,且状态引用名也必须遵循定义个格式,不过既然是模板就有相应格式,遵循即可。
- C语言状态机学习
- C语言状态机学习笔记一
- C语言状态机学习笔记二
- c语言状态机编程
- c语言注释---状态机
- c语言 状态机
- C语言实现状态机
- 状态机C语言
- C语言状态机的实现
- 状态机的c语言编程
- C语言最优状态机规范
- 状态机的C语言编程
- 状态机的c语言编程
- 状态机的C语言编程(转)
- 基于C语言的状态机实现技术
- 用C语言写一个状态机
- 普通青年的状态机,纯C语言
- C 语言状态机编程-去掉代码注释
- 《自控力》-- 精华摘抄
- 关于AndroidStudio如何导入新项目LibraryProject以及遇到的一些问题
- js中时间的比较
- Ubuntu常用命令
- 第一次使用Android Studio时你应该知道的一切配置
- C语言状态机学习
- Ajax处理下载文件response没有反应
- Android 动画详解之视图动画
- 【AngularJS】Repeat方法;
- 关于strncpy这个函数
- echarts 横向占比条状图
- CPU的频率
- Spring_SSH整合之_架构的历史(两层架构整合JSP+MySQL)_1
- MVVM模式中ViewModel和View、Model有什么区别