众所周知,C++是由C语言编写而成,因此,C语言也可以实现一定程度的面对对象编程,接下来为引入状态机的程序,我们先来介绍几个不常用的C语言用法。
众所周知,指针是C语言的灵魂,它不仅可以指向各种变量和自己,并且可以指向函数。
先来看正常的C函数
void function_name(void *arg);
这个函数,我们将参数设置为void形式方便后续添加参数。
我们可以根据上方函数声明
创建函数指针变量
由于优先级问题,将名称括起来
并加上星号
void (*P_function)(void *arg);
/* 注意:前后参数类型必须保持一直 */
这样,在调用时,就可以
P_function = function_name;
/* 函数指针指向对应地址 */
(*P_function)( 填入对应参数 );
/* 调用函数 */
当然,函数的返回值类型可以任意指定,但是上下必须保持一致。
扩展
在上面为什么要使用void*
作为参数呐?
因为如果使用了void*
,我们就可以在函数中将它转化为任意类型的变量指针
只需要使用强制类型转换
,就可以轻而易举的达到我们的目的。
void function_name(void *arg)
{
/* int可替换成任意需要的类型 */
int *need_arg= (int *)arg;
...
}
前面说了函数指针,如果我们能在结构体中添加函数指针
项,就可以实现类似C++一样的面对对象的形式。
/* 定义 */
struct func_struct{
int n;
void (*P_func)(int n);
};
/* 使用 */
struct func_struct func;
func->P_func( 100 );
宏定义也可以使用一些变量替换,这样方便我们编程,而实现的方法。
#define NUM(n) (num_##n)
/* n作为变量,##后的n将被替换成参数 */
例如:
NUM(1) 替换为 num_1
NUM(2) 替换为 num_2
NUM(3) 替换为 num_3
要说状态机的概念就得从实例来讲。
比如说一个控制一个发光二极管。
由图可以知道,二极管有两种状态
,亮和灭,分别对应IO口的两种动作
,高电平和低电平。从这里出发,可以得出状态机的4个要素
我个人有个不太恰当的理解,变换
作为一个整体的系统(传函)
,状态
就是我们的输出
,事件
就是输入与触发
,动作
自然就是输出状态后的响应
。
构建一个简单的三层状态机系统
有三个状态,四个事件,并且动作设计为可以现实当前所处状态。
对状态机的变换结构体,事件和状态进行编写。
#ifndef _STATE_H
#define _STATE_H
typedef enum _sta_{
state1,state2,state3
}State;
typedef enum _evt_{
evt_1t2,evt_2t3,evt_3t1,evt_1t3
}Event;
typedef struct {
State curState; //当前状态
Event eventId; //事件ID
State nextState;//下个状态
void (*action)(void *arg);//回调函数(动作函数),事件发生后,调用对应的回调函数
}StateTransform;
#endif /* _STATE_H */
再写出动作函数
void action_callback(void *arg)
{
/* 将输入参数转化为自己定义的结构体指针类型 */
StateTransform *statTran = (StateTransform *)arg;
/* 将转化的内容打印到屏幕上 */
printf("状态由%d由%d",statTran->curState+1,statTran->nextState+1);
}
构建状态迁移矩阵
此处每个结构体对应数组代表迁移列表中的列。
/* 由于地址上的连续性
结构体可以直接通过数组进行赋值 */
StateTransform stateTran_1[] = {
{state1,evt_1t2,state2,action_callback},//每一行代表一个结构体变量
{state1,evt_2t3,state1,NULL},
{state1,evt_3t1,state1,NULL},
{state1,evt_1t3,state3,action_callback},
};
StateTransform stateTran_2[] = {
{state2,evt_1t2,state2,NULL},
{state2,evt_2t3,state3,action_callback},
{state2,evt_3t1,state2,NULL},
{state2,evt_1t3,state2,NULL},
};
StateTransform stateTran_3[] = {
{state3,evt_1t2,state3,NULL},
{state3,evt_2t3,state3,NULL},
{state3,evt_3t1,state1,action_callback},
{state3,evt_1t3,state3,NULL},
};
构建完整状态迁移表后,创建一个全局的变量作为当前状态
即 State GlobleCurState;
之后,进行简单的查表即可。
也就是说,根据当前状态
和输入的事件
,在状态迁移表中查询下一个状态和动作响应并执行。
void event_happen(Event event) //输入事件
{
switch (GlobleCurState) {
case state1:
do_action(&STATETRANS(1)[event]); //执行对应动作
break;
case state2:
do_action(&STATETRANS(2)[event]);
break;
case state3:
do_action(&STATETRANS(3)[event]);
break;
}
}
void do_action(StateTransform *stateTran)
{
//状态迁移
GlobleCurState = stateTran->nextState;
//调用回调
if(stateTran->action == NULL)
{
printf("Without Action!\n\r");
}
else
{
stateTran->action((void *)stateTran);
}
}
最后进行简单测试
#define ENTRY_STATE state1
int main() {
GlobleCurState = ENTRY_STATE;
State lastState = GlobleCurState;
Event User_Event;
printf("当前状态为state%d\n",GlobleCurState);
while(1)
{
scanf("%d",&User_Event);
if(User_Event == -1)
break;
event_happen(User_Event);
printf("当前状态为state%d\n",GlobleCurState+1);
}
return 0;
}
参考
什么是状态机?用C语言实现进程5状态模型 作者:一口Linux.
谈谈单片机编程思想——状态机 作者:轻松学C语言.
不得不说,在家太舒服了。。。由于疫情不开学,天天摸鱼,啥也没干,我知道摸鱼不对可是就是停不下来哼哼啊啊啊啊啊啊啊。
但是快开学了,压力在前,就真不能摸了。。。 Orz