状态机教程

ant*_*009 68 c c99 state-machine

我只是想知道是否有人知道在互联网上开发状态机的一些很好的教程.还是电子书?

我开始在状态机上工作,只需要一些通用的东西让我开始.

qrd*_*rdl 125

如果使用函数指针,状态机在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;
}
Run Code Online (Sandbox Code Playgroud)

我没有放lookup_transitions()功能,因为它是微不足道的.

这就是我多年来一直使用机器的方式.

  • 很好,谢谢你.这里唯一可能的缺陷是lookup_transitions可能与这个转换表数据结构是线性的(O(n)).使用多维数组可以使其更好 - 保证O(1).例如.该表可以被表示为多维阵列,其中关键是状态和所述值是一个阵列,其中关键是返回代码和值是下一个状态:`诠释state_transitions [] [3] = {[条目] = {FOO,结束,foo},...}/*ok,失败,重复*/` (13认同)
  • 为什么您的状态函数不为查找函数返回枚举而不是整数?你要返回一个 ret 代码,不是吗? (2认同)
  • @Multisync通常来说,状态!=函数,通常有多个不同的状态,实际上使用的是同一函数。例如,您可以有几个准备消息的功能,一个发送消息的功能和一个接收响应的功能,但是这两个I / O功能将在不同的状态下使用。 (2认同)

Chr*_*oph 28

我更喜欢在巨大的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);
}
Run Code Online (Sandbox Code Playgroud)

  • @Multisync 上一行中的结构体初始化器将 `state.next`(一个函数指针)设置为 `foo` 的地址。所以`state.next(&amp;state)` 与`foo(&amp;state)` 相同(循环的第一次迭代,它稍后指向其他地方)。为了比较,如果这是 C++,`foo()` 将是 `State` 类 (`State::foo()`) 的成员并且不会接受任何参数。它的主体将使用 `this-&gt;next = bar` 而不是 `state-&gt;next = bar`。在 C 中,您必须显式传递“this”指针的等效项,因为没有有状态的类作用域。 (2认同)

X-I*_*nce 9

状态机本身不需要解释甚至使用教程.我建议您查看数据以及如何解析数据.

例如,我不得不解析近太空气球飞行计算机的数据协议,它以特定格式(二进制)将数据存储在SD卡上,需要将其解析为逗号分隔文件.使用状态机是最有意义的,因为根据下一部分信息,我们需要更改我们正在解析的内容.

代码使用C++编写,可用作ParseFCU.如您所见,它首先检测我们正在解析的版本,并从那里进入两个不同的状态机.

它以一个已知良好的状态进入状态机,此时我们开始解析,并根据我们遇到的字符,我们要么继续前进到下一个状态,要么返回到先前的状态.这基本上允许代码自我适应数据的存储方式以及是否存在某些数据.

在我的示例中,GPS字符串不是飞行计算机记录的要求,因此如果找到该单个日志写入的结束字节,则可以跳过GPS字符串的处理.

状态机很容易编写,一般来说我遵循它应该流动的规则.通过系统的输入应该从一个状态流到一个状态.

  • 这听起来像一个非常低效,荒谬的昂贵,也许有点浪费,而且是非常棒的爱好. (4认同)
  • @Chris:近空间被定义为任何高于 60,000 英尺的地方,我们的气球达到了 92,999 英尺。在某个时候,由于减压(气体不断膨胀气球),乳胶气球开始变得如此大,以至于气球爆裂,此时指向近太空飞行器自由落体返回地球(我们在偏离航线的情况下附加降落伞),请参阅链接的 Wiki 以获取有关我们的近太空努力和谷歌周围的更多信息,这绝对是一个很棒的爱好! (2认同)

Mic*_*urr 9

不幸的是,状态机上的大多数文章都是为C++或其他直接支持多态的语言编写的,因为很好地将FSM实现中的状态建模为派生自抽象状态类的类.

但是,使用switch语句将事件分配到状态(对于简单的FSM,它们几乎是代码)或使用表将事件映射到状态转换,在C中实现状态机非常容易.

关于C中状态机的基本框架,有一些简单但不错的文章:

编辑:网站"正在维护",网站存档链接:

switch基于语句的状态机通常使用一组宏来"隐藏" switch语句的机制(或者使用一组if/ then/ else语句而不是a switch),并使用"FSM语言"来描述C语言中的状态机资源.我个人更喜欢基于表格的方法,但这些方法肯定具有优点,被广泛使用,并且对于更简单的FSM尤其有效.

Steve Rabin在"Game Programming Gems"第3.0章(设计通用的强大AI引擎)中概述了一个这样的框架.

这里讨论一组类似的宏:

如果您对C++状态机实现也感兴趣,那么可以找到更多内容.如果你有兴趣,我会发布指针.


Cha*_*ion 6

这就是您需要知道的全部内容。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为明确设置状态是更好的做法,但这只是我的看法。 (9认同)
  • 你遗漏了很多,例如:子状态;事件以及事件/状态组合;状态转换图;守卫(“如果”则不改变状态);状态进入和状态退出方法(“退出此状态时执行以下方法”)。 (2认同)
  • 这是过于简化的状态机,并不是一个很好的例子。 (2认同)
  • 不要让非常简单的事情变得过于复杂。 (2认同)