有没有典型的状态机实施模式?
我们需要在C中实现一个简单的状态机。
标准的switch语句是最好的方法吗?
我们有一个当前状态(状态)和一个转换触发器。
switch(state) { case STATE_1: state = DoState1(transition); break; case STATE_2: state = DoState2(transition); break; } ... DoState2(int transition) { // Do State Work ... if(transition == FROM_STATE_2) { // New state when doing STATE 2 -> STATE 2 } if(transition == FROM_STATE_1) { // New State when moving STATE 1 -> STATE 2 } return new_state; }
对于简单的状态机,是否有更好的方法
编辑:
对于C ++,我认为Boost Statechart库可能是要走的路。但是,它对C并没有帮助。让我们专注于C用例。
解决方案
Boost具有状态图库。 http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html
不过,我无法说出它的用途。本人尚未使用(尚未使用)
以我的经验,使用" switch"语句是处理多种可能状态的标准方法。尽管令我感到惊讶的是,我们正在将过渡值传递给每个状态的处理。我认为状态机的全部意义在于每个状态都执行一个动作。然后,下一个动作/输入确定要转换到的新状态。因此,我希望每个状态处理功能都能立即执行固定的进入状态的操作,然后再决定是否需要转换到另一个状态。
在C ++中,请考虑"状态"模式。
还有一个逻辑网格,随着状态机的变大,它更易于维护
对于简单的情况,可以使用开关样式方法。我发现过去有效的方法也是处理过渡:
static int current_state; // should always hold current state -- and probably be an enum or something void state_leave(int new_state) { // do processing on what it means to enter the new state // which might be dependent on the current state } void state_enter(int new_state) { // do processing on what is means to leave the current atate // might be dependent on the new state current_state = new_state; } void state_process() { // switch statement to handle current state }
我对boost库一无所知,但是这种方法非常简单,不需要任何外部依赖,并且易于实现。
对于状态模式,本文是一篇不错的文章(尽管它是C ++,而不是C)。
如果我们可以把手放在" Head First Design Patterns"一书上,那么说明和示例将非常清楚。
有一本书的标题为《 C / C ++中的实用状态图》。
但是,对于我们所需要的来说,它太繁重了。
我更喜欢对大多数状态机使用表驱动的方法:
typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t; typedef struct instance_data instance_data_t; typedef state_t state_func_t( instance_data_t *data ); state_t do_state_initial( instance_data_t *data ); state_t do_state_foo( instance_data_t *data ); state_t do_state_bar( instance_data_t *data ); state_func_t* const state_table[ NUM_STATES ] = { do_state_initial, do_state_foo, do_state_bar }; state_t run_state( state_t cur_state, instance_data_t *data ) { return state_table[ cur_state ]( data ); }; int main( void ) { state_t cur_state = STATE_INITIAL; instance_data_t data; while ( 1 ) { cur_state = run_state( cur_state, &data ); // do other program logic, run other state machines, etc } }
当然,这可以扩展为支持多个状态机等。还可以适应转换动作:
typedef void transition_func_t( instance_data_t *data ); void do_initial_to_foo( instance_data_t *data ); void do_foo_to_bar( instance_data_t *data ); void do_bar_to_initial( instance_data_t *data ); void do_bar_to_foo( instance_data_t *data ); void do_bar_to_bar( instance_data_t *data ); transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = { { NULL, do_initial_to_foo, NULL }, { NULL, NULL, do_foo_to_bar }, { do_bar_to_initial, do_bar_to_foo, do_bar_to_bar } }; state_t run_state( state_t cur_state, instance_data_t *data ) { state_t new_state = state_table[ cur_state ]( data ); transition_func_t *transition = transition_table[ cur_state ][ new_state ]; if ( transition ) { transition( data ); } return new_state; };
表驱动的方法更易于维护和扩展,更易于映射到状态图。
我们可能已经看到我对另一个提到FSM的C问题的回答!这是我的方法:
FSM { STATE(x) { ... NEXTSTATE(y); } STATE(y) { ... if (x == 0) NEXTSTATE(y); else NEXTSTATE(x); } }
定义以下宏
#define FSM #define STATE(x) s_##x : #define NEXTSTATE(x) goto s_##x
可以对其进行修改以适合特定情况。例如,我们可能具有要驱动FSM的文件" FSMFILE",因此我们可以将读取下一个字符的操作合并到宏本身中:
#define FSM #define STATE(x) s_##x : FSMCHR = fgetc(FSMFILE); sn_##x : #define NEXTSTATE(x) goto s_##x #define NEXTSTATE_NR(x) goto sn_##x
现在,我们有两种类型的过渡:一种进入状态并读取新字符,另一种则不消耗任何输入就进入状态。
我们还可以使用以下方式自动处理EOF:
#define STATE(x) s_##x : if ((FSMCHR = fgetc(FSMFILE) == EOF)\ goto sx_endfsm;\ sn_##x : #define ENDFSM sx_endfsm:
这种方法的好处是,我们可以将绘制的状态图直接转换为工作代码,相反,可以轻松地从代码中绘制状态图。
在其他用于实现FSM的技术中,转换的结构被埋在控制结构中(同时,如果...则是……),并由变量值(通常是"状态"变量)控制,而关联好变量可能是一项复杂的任务绘制成复杂的代码。
我从一篇伟大的"计算机语言"杂志上发表的一篇文章中学到了这种技术,不幸的是,该杂志不再出版。
对于简单的状态机,只需为状态使用switch语句和枚举类型。根据输入在switch语句内进行转换。在实际程序中,我们显然会更改" if(input)"以检查过渡点。希望这可以帮助。
typedef enum { STATE_1 = 0, STATE_2, STATE_3 } my_state_t; my_state_t state = STATE_1; void foo(char input) { ... switch(state) { case STATE_1: if(input) state = STATE_2; break; case STATE_2: if(input) state = STATE_3; else state = STATE_1; break; case STATE_3: ... break; } ... }
switch()是在C中实现状态机的一种强大且标准的方法,但是如果我们有很多状态,它会降低可维护性。另一种常见的方法是使用函数指针来存储下一个状态。这个简单的示例实现了置位/复位触发器:
/* Implement each state as a function with the same prototype */ void state_one(int set, int reset); void state_two(int set, int reset); /* Store a pointer to the next state */ void (*next_state)(int set, int reset) = state_one; /* Users should call next_state(set, reset). This could also be wrapped by a real function that validated input and dealt with output rather than calling the function pointer directly. */ /* State one transitions to state one if set is true */ void state_one(int set, int reset) { if(set) next_state = state_two; } /* State two transitions to state one if reset is true */ void state_two(int set, int reset) { if(reset) next_state = state_one; }
我们可能需要研究libero FSM生成器软件。通过状态描述语言和/或者(Windows)状态图编辑器,我们可以生成C,C ++,java和许多其他代码,以及精美的文档和图。
来自iMatix的源代码和二进制文件
问题类似于"是否存在典型的数据库实施模式"?
答案取决于我们想要实现什么?如果要实现更大的确定性状态机,则可以使用模型和状态机生成器。
可以在www.StateSoft.org SM Gallery上查看示例。贾努斯·多布罗沃尔斯基(Janusz Dobrowolski)