具有功能指针的状态机

函数指针可用于各种应用程序,包括状态机的实现。就像任何工具一样,在状态机实现中使用函数指针可能并不总是合适的。实现状态机的几种常见方法是使用if / else语句或使用switch / case语句。通常,如果状态机具有很多状态或者每个状态有很多与状态相关的代码,这会导致状态机功能的复杂性很高,那么使用函数指针实现可能是一个好主意。

 

图1包含将在本文中实现的示例状态机。它仅包含四个状态,每个状态仅具有转换为另一个状态的能力。这将允许简单检查如何实现状态机的功能指针版本。但是,此过程可用于具有更复杂的过渡和状态的任何状态机。

 

实现状态机的第一步是创建状态机中每个状态的枚举。清单1显示了使用typedef定义并给出StateType标签的示例枚举。除了提供状态机中状态数的NUM_STATES外,它还包含所有四个状态。记住枚举从0开始!

 

图1

图1–状态机示例

 

清单1

清单1–状态机类型定义

 

定义状态机的下一步将是定义状态机的结构。除了能够存储指向该状态的函数指针之外,该结构还应该能够存储状态(STATE_A至STATE_D)。结构的结果是可以创建一个数组,该数组既包含状态又包含状态机处于该状态时应执行的功能。现在可以跳过结构的StateType元素,但是在创建状态机表时,除了使用几个额外的Flash字节外,它使该表更易于阅读且不影响系统性能。清单2显示了StateMachineType结构的定义。

 

 清单2

清单2–状态机状态列表

 

应当注意,在StateMachineType的此定义中,函数指针既不接收也不返回任何东西。对于虚拟机来说,这很好,但是在现实世界中,每个状态都可以使用某种类型的变量或返回一个变量。将修改结构以满足状态机的要求。

 

定义结构后,将定义一个StateMachineType类型的数组,该数组不仅包含每个状态,还包含应为该状态调用的实际函数。在代码清单3中可以看到这一点。重要的是要记住,至少应在此表定义之前对诸如Sm_StateA之类的函数进行原型设计。如果不是,则编译器很可能会给出警告或完全错误,表明找不到该定义。除了功能原型外,还需要定义一个状态变量来存储机器的当前状态。清单4显示了变量定义和函数原型。

清单3

清单3– State Machine Table

清单4

清单4–状态变量和功能

 

根据需求,状态函数的实际代码有几种不同的显示方式。首先,状态函数可以运行状态代码,然后将状态变量(SmState)更新为下一个状态。或者,状态代码可能会一遍又一遍地运行,直到发生某些外部事件,然后再导致其执行状态转换并更新状态变量。这在很大程度上取决于应用程序和定义的过渡。为简单起见,此示例将简单地运行每个状态代码,然后执行强制转换。函数定义可以在代码清单5中找到。

 

清单5

清单5–状态机功能

 

最后,在为状态机准备好整个框架之后,需要定义一个功能来实际运行状态机。通常,作者喜欢使用明显的命名约定,因此在这种情况下该函数将是Sm_Run,如代码清单6所示。在Sm_Run函数中,执行了一个简单的检查以确保状态变量的当前状态尚未超过州数。如果存在,则可以引发异常,否则将调用指向当前状态函数的函数指针,从而运行该状态。

 

清单6

清单6–状态机运行功能

 

请注意,用于调用状态函数的表示法与以前的文章中所使用的表示法相同,并且状态机代码是如此简单。每个状态都可以很好地分解为各个功能,这将使复杂性降到最低,从而使将来的维护更加容易。可以使用if / else语句将这些代码全部实现为一个函数,但是代码的复杂性将非常困难,尤其是在任何一个州都有与之相关联的页面代码的情况下,这种情况并不罕见。另外,添加加法状态不需要更改Sm_Run函数!所有需要做的就是将状态添加到枚举中,定义一个函数并更新状态机表。

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *

该网站使用Akismet减少垃圾邮件。 了解如何处理您的评论数据.