状态机
状态机一般指有限状态机,简称fsm,意思就是一项任务只有有限个状态。比如嵌入式经典点灯问题,灯只有开灯和关灯两个状态。
举例
假设我们有一项任务是三个灯先后点亮,也就是先点亮第一个灯,过了一秒后点亮第一、第二个灯,再过一秒点亮第一、第二、第三个灯,再过一秒点亮第一个灯,如此循环。
标志位写法
我们讲每个灯是否点亮做一个标志位那么程序实现如下
if (time1S)// 1s时间到了
{
if(light1Flag == 1 && light2Flag == 0 && light3Flag == 0) // 如果当前是第一个灯亮
{
light1Flag = 1; // 第一个灯亮
light2Flag = 1; // 第二个灯亮
light3Flag = 0; // 第三个灯灭
}
else if(light1Flag == 1 && light2Flag == 1 && light3Flag == 0) // 如果当前是第一、第二个灯亮
{
light1Flag = 1; // 第一个灯亮
light2Flag = 1; // 第二个灯亮
light3Flag = 1; // 第三个灯亮
}
else if(light1Flag == 1 && light2Flag == 1 && light3Flag == 1) // 如果当前是第一、第二、第三个灯亮
{
light1Flag = 1; // 第一个灯亮
light2Flag = 0; // 第二个灯灭
light3Flag = 0; // 第三个灯灭
}
light1_open(light1Flag);
light2_open(light2Flag);
light3_open(light3Flag);
}
虽然我们通过上述代码实现了功能,但是如果遇到灯非常多的情况,程序中就会出现大量标志位Flag,极易因为某个标志位写错而出现bug,而且对于程序的可读性也会非常差。
下面我们试着使用状态机来实现这部分代码。
状态机写法
首先我们将需求分为3个状态
enum{
STATE_OPEN_1, // 开第一个灯状态
STATE_OPEN_1_2,// 开第一、第二个灯状态
STATE_OPEN_1_2_3,// 开第一、第二、第三个灯状态
};
然后我们编写相应代码
if (time1S)// 1s时间到了
{
switch(stateNow)
{
case STATE_OPEN_1:
stateNow = STATE_OPEN_1_2;
light1_open(1);
light2_open(1);
light3_open(0);
break;
case STATE_OPEN_1_2:
stateNow = STATE_OPEN_1_2_3;
light1_open(1);
light2_open(1);
light3_open(1);
break;
case STATE_OPEN_1_2_3:
stateNow = STATE_OPEN_1;
light1_open(1);
light2_open(0);
light3_open(0);
break;
}
}
这段代码里,time1S
是状态发生改变的条件,称为转移条件。
stateNow = STATE_OPEN_1_2;
这里状态发生了改变,称为状态转移。
light1_open(1);
light2_open(1);
light3_open(0);
这是状态发生改变后的动作,通常动作发生在退出状态、进入状态、状态运行时。
因此我们可以看到状态机的4个重要的组成部分:
- 状态
- 状态转移
- 转移条件
- 动作
状态机的优点
逻辑的完备性
使用状态机你可以清晰的把整个程序的所有逻辑罗列,不会因为编写代码没有想到这种状况而出现莫名其妙的bug。
每次状态发生改变时都必然对应一个状态转移条件,转移之后都有对应的动作,我们自然而然的就会思考当前状态下的所有可能发生的状况,这将在编程阶段就能减少大量的程序bug。
程序结构清晰
使用状态机的程序没有标志位相互参杂,每一个状态对应一段子代码,该段代码下的所有程序都能对应上当前状态,程序阅读就会非常清晰,再配上简单的文字说明,状态机的4个要素清晰明了,程序中有哪些状态,会发生哪些事件,状态机如何响应,响应之后跳转到哪个状态,这些都十分明朗。
程序易于扩展
当产生新需求时,对于使用状态机的程序来说只需要增减几个状态并编写对应的程序即可,而使用标志位的程序则可能要更改多个文件,并在文件所有对标志位进行操作的地方进行更改,如果遗漏某些地方则有可能出现bug,且这些bug是难以寻找的。