我只是想知道是否有人知道Internet上关于开发状态机的一些很好的教程。还是电子书?
我开始在状态机上工作,只需要一些一般性的知识即可入门。
Answers:
如果使用函数指针,状态机在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
作为函数指针会不会容易得多?我不了解使用枚举类型的它们的好处,当您使用它们时,只是在数组中查找一些函数指针。
我喜欢使用函数指针而不是硕大的switch
语句,但是与qrdl的答案相反,我通常不使用显式的返回码或转换表。
另外,在大多数情况下,您需要一种机制来传递其他数据。这是一个示例状态机:
#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)
(循环的第一次迭代,稍后将其指向其他地方)相同。为了进行比较,如果是C ++,foo()
它将是State
类(State::foo()
)的成员,并且不接受任何参数。它的身体将this->next = bar
代替state->next = bar
。在C语言中this
,由于没有状态类作用域,因此必须显式传递指针的等效项。
不幸的是,状态机上的大多数文章都是用C ++或其他直接支持多态的语言编写的,因为将FSM实现中的状态建模为派生自抽象状态类的类很不错。
然而,使用switch语句将事件分派给状态(对于简单的FSM,它们几乎是直接编写代码)或使用表将事件映射到状态转换,在C中实现状态机非常容易。
这里有一些关于C状态机基本框架的简单但不错的文章:
编辑:网站“维护中”,网络存档链接:
switch
基于语句的状态机经常使用的一组宏“隐藏”的力学的switch
语句(或使用一组if
/ then
/else
语句来代替switch
),并相当于一个“FSM语言”描述在C状态机资源。我个人更喜欢基于表的方法,但是这些方法肯定有优点,已被广泛使用,并且尤其对于简单的FSM而言可能是有效的。
Steve Rabin在“ Game Programming Gems”第3.0章(设计通用的健壮AI引擎)中概述了这样一种框架。
这里讨论了一组相似的宏:
如果您还对C ++状态机实现感兴趣,那么可以找到更多的信息。如果您有兴趣,我会发布指针。
状态机并不是本质上需要解释甚至使用教程的东西。我建议您查看一下数据以及如何对其进行解析。
例如,我必须为近太空气球飞行计算机解析数据协议,它以特定格式(二进制)将数据存储在SD卡上,需要将其解析为逗号分隔文件。为此,使用状态机是最有意义的,因为根据下一信息的内容,我们需要更改解析的内容。
该代码使用C ++编写,可以作为ParseFCU使用。如您所见,它首先检测我们正在解析的版本,然后从那里进入两个不同的状态机。
它以已知良好的状态进入状态机,这时我们开始解析,并根据遇到的字符前进到下一个状态,或返回到上一个状态。基本上,这使代码可以自动适应数据的存储方式,甚至可以完全适应某些数据。
在我的示例中,GPS字符串不是飞行计算机记录日志的必要条件,因此,如果找到了该单个日志写入的结尾字节,则可以跳过GPS字符串的处理。
状态机很容易编写,一般来说,我遵循应遵循的规则。通过系统的输入应该在状态之间轻松流动。
这就是您需要知道的全部。
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;
}
}
学习用C语言手工制作状态机有很多课程,但让我也建议Ragel状态机编译器:
http://www.complang.org/ragel/
它具有定义状态机的非常简单的方法,然后您可以生成图,生成不同样式的代码(表驱动,goto驱动),分析代码(如果需要)等等。它功能强大,可以在生产中使用各种协议的代码。
对于复杂的问题,状态机可能非常复杂。它们还会遭受意外的错误。如果有人遇到错误或将来需要更改逻辑,它们可能会变成噩梦。如果没有状态图,它们也很难遵循和调试。结构化编程要好得多(例如,您可能不会在主线级别使用状态机)。您甚至可以在中断上下文(通常使用状态机)中使用结构化编程。请参阅位于codeproject.com上的本文“在中断级别模拟多任务/阻止代码的宏”。