提问人:ant2009 提问时间:9/3/2009 最后编辑:paradigmaticant2009 更新时间:9/6/2019 访问量:159419
状态机教程 [已结束]
state machines tutorials [closed]
问:
我们不允许向读者、工具、软件库等寻求推荐的问题。您可以编辑问题,以便用事实和引文来回答。
7年前关闭。
我只是想知道是否有人知道互联网上一些关于开发状态机的好教程。还是电子书?
我开始研究状态机,只需要一些通用的东西来让我入门。
答:
这就是您需要知道的全部内容。
int state = 0;
while (state < 3)
{
switch (state)
{
case 0:
// Do State 0 Stuff
if (should_go_to_next_state)
{
state++;
}
break;
case 1:
// Do State 1 Stuff
if (should_go_back)
{
state--;
}
else if (should_go_to_next_state)
{
state++;
}
break;
case 2:
// Do State 2 Stuff
if (should_go_back_two)
{
state -= 2;
}
else if (should_go_to_next_state)
{
state++;
}
break;
default:
break;
}
}
评论
Real-Time Object-Oriented Modeling 非常棒(1994 年出版,现在售价低至 81 美分,外加 3.99 美元的运费)。
评论
状态机本质上不需要教程来解释甚至使用。我的建议是,你看看数据以及如何解析它。
例如,我必须解析近太空气球飞行计算机的数据协议,它以特定格式(二进制)将数据存储在SD卡上,需要将其解析为逗号分隔的文件。为此使用状态机是最有意义的,因为根据下一点信息是什么,我们需要更改我们正在解析的内容。
该代码使用 C++ 编写,可用作 ParseFCU。正如你所看到的,它首先检测我们正在解析的版本,然后从那里进入两个不同的状态机。
它以已知良好的状态进入状态机,此时我们开始解析,根据我们遇到的字符,我们要么进入下一个状态,要么回到以前的状态。这基本上允许代码自我适应数据的存储方式,甚至无论某些数据是否存在。
在我的示例中,GPS 字符串不是飞行计算机记录的必要条件,因此,如果找到该单个日志写入的结束字节,则可能会跳过 GPS 字符串的处理。
状态机编写起来很简单,一般来说,我遵循它应该流动的规则。通过系统的输入应该在一定程度上轻松地从一个状态流向另一个状态。
评论
如果使用函数指针,状态机在 C 中非常简单。
基本上你需要 2 个数组 - 一个用于状态函数指针,一个用于状态转换规则。每个状态函数都返回代码,您可以逐个状态查找状态转换表并返回代码以查找下一个状态,然后执行它。
int entry_state(void);
int foo_state(void);
int bar_state(void);
int exit_state(void);
/* array and enum below must be in sync! */
int (* state[])(void) = { entry_state, foo_state, bar_state, exit_state};
enum state_codes { entry, foo, bar, end};
enum ret_codes { ok, fail, repeat};
struct transition {
enum state_codes src_state;
enum ret_codes ret_code;
enum state_codes dst_state;
};
/* transitions from end state aren't needed */
struct transition state_transitions[] = {
{entry, ok, foo},
{entry, fail, end},
{foo, ok, bar},
{foo, fail, end},
{foo, repeat, foo},
{bar, ok, end},
{bar, fail, end},
{bar, repeat, foo}};
#define EXIT_STATE end
#define ENTRY_STATE entry
int main(int argc, char *argv[]) {
enum state_codes cur_state = ENTRY_STATE;
enum ret_codes rc;
int (* state_fun)(void);
for (;;) {
state_fun = state[cur_state];
rc = state_fun();
if (EXIT_STATE == cur_state)
break;
cur_state = lookup_transitions(cur_state, rc);
}
return EXIT_SUCCESS;
}
我不把函数,因为它是微不足道的。lookup_transitions()
这就是我多年来做状态机的方式。
评论
int state_transitions[][3] = { [entry] = { foo, end, foo }, ... } /* ok, fail, repeat */
src_state
dst_state
不幸的是,大多数关于状态机的文章都是为 C++ 或其他直接支持多态性的语言编写的,因为将 FSM 实现中的状态建模为派生自抽象状态类的类是很好的。
但是,在 C 语言中实现状态机非常容易,要么使用 switch 语句将事件调度到状态(对于简单的 FSM,它们几乎是直接编写代码),要么使用表将事件映射到状态转换。
这里有几篇关于C语言状态机基本框架的简单但不错的文章:
- http://www.gedan.net/2008/09/08/finite-state-machine-matrix-style-c-implementation/
- http://www.gedan.net/2009/03/18/finite-state-machine-matrix-style-c-implementation-function-pointers-addon/
编辑:网站“正在维护中”,网络存档链接:
- http://web.archive.org/web/20160517005245/http://www.gedan.net/2008/09/08/finite-state-machine-matrix-style-c-implementation
- http://web.archive.org/web/20160808120758/http://www.gedan.net/2009/03/18/finite-state-machine-matrix-style-c-implementation-function-pointers-addon/
switch
基于语句的状态机通常使用一组宏来“隐藏”语句的机制(或使用一组 // 语句而不是 语句),并制作相当于“FSM 语言”的东西来描述 C 源代码中的状态机。我个人更喜欢基于表的方法,但这些方法肯定有优点,被广泛使用,并且特别对更简单的 FSM 有效。switch
if
then
else
switch
Steve Rabin 在“Game Programming Gems”第 3.0 章(设计一个通用的健壮 AI 引擎)中概述了一个这样的框架。
下面讨论了一组类似的宏:
如果您也对 C++ 状态机实现感兴趣,可以找到更多内容。如果你有兴趣,我会发布指针。
评论
用 C 语言手工制作状态机有很多经验教训,但我也建议使用 Ragel 状态机编译器:
http://www.complang.org/ragel/
它有非常简单的方法来定义状态机,然后你可以生成图形,生成不同样式的代码(表驱动,goto驱动),如果需要,可以分析该代码,等等。而且它功能强大,可用于各种协议的生产代码中。
我更喜欢使用函数指针而不是巨大的语句,但与 qrdl 的答案相比,我通常不使用显式返回代码或转换表。switch
此外,在大多数情况下,您需要一种机制来传递其他数据。下面是一个状态机示例:
#include <stdio.h>
struct state;
typedef void state_fn(struct state *);
struct state
{
state_fn * next;
int i; // data
};
state_fn foo, bar;
void foo(struct state * state)
{
printf("%s %i\n", __func__, ++state->i);
state->next = bar;
}
void bar(struct state * state)
{
printf("%s %i\n", __func__, ++state->i);
state->next = state->i < 10 ? foo : 0;
}
int main(void)
{
struct state state = { foo, 0 };
while(state.next) state.next(&state);
}
评论
main
main()
0
while
foo()
state.next
foo
state.next(&state)
foo(&state)
foo()
State
State::foo()
this->next = bar
state->next = bar
this
对于一个复杂的问题,状态机可能非常复杂。它们也容易受到意外错误的影响。如果有人遇到错误或将来需要更改逻辑,它们可能会变成一场噩梦。如果没有状态图,它们也很难跟踪和调试。结构化编程要好得多(例如,您可能不会在主线级别使用状态机)。即使在中断上下文(通常使用状态机)中,也可以使用结构化编程。请参阅 codeproject.com 中的这篇文章“用于在中断级别模拟多任务/阻塞代码的宏”。
评论