有没有典型的状态机实现模式?

Posted

技术标签:

【中文标题】有没有典型的状态机实现模式?【英文标题】:Is there a typical state machine implementation pattern? 【发布时间】:2010-09-13 02:09:59 【问题描述】:

我们需要在 C 中实现一个简单的状态机。 标准的 switch 语句是最好的方法吗? 我们有一个当前状态(state)和一个用于转换的触发器。


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 用例。

【问题讨论】:

另见***.com/questions/1647631/c-state-machine-design 【参考方案1】:

对于简单的情况,您可以使用您的 switch 样式方法。我发现过去效果很好的是也可以处理转换:

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 state
    // might be dependent on the new state

    current_state = new_state;


void state_process() 
    // switch statement to handle current state

   

我对 boost 库一无所知,但是这种方法非常简单,不需要任何外部依赖,而且很容易实现。

【讨论】:

【参考方案2】:

我也更喜欢表格驱动的方法。我过去使用过switch 语句。我遇到的主要问题是调试转换并确保设计的状态机已正确实现。这发生在有大量状态和事件的情况下。

使用表格驱动的方法是在一个地方汇总状态和转换。

下面是这种方法的演示。

/*Demo implementations of State Machines
 *
 * This demo leverages a table driven approach and function pointers
 *
 * Example state machine to be implemented
 *
 *          +-----+      Event1        +-----+      Event2        +-----+
 *    O---->|  A  +------------------->|  B  +------------------->|  C  |
 *          +-----+                    +-----+                    +-----+
 *             ^                                                     |
 *             |                       Event3                        |
 *             +-----------------------------------------------------+
 *
 * States: A, B, C
 * Events: NoEvent (not shown, holding current state), Event1, Event2, Event3
 *
 * Partly leveraged the example here: http://web.archive.org/web/20160808120758/http://www.gedan.net/2009/03/18/finite-state-machine-matrix-style-c-implementation-function-pointers-addon/
 *
 * This sample code can be compiled and run using GCC.
 * >> gcc -o demo_state_machine demo_state_machine.c
 * >> ./demo_state_machine
 */

#include <stdio.h>
#include <assert.h>

// Definitions of state id's, event id's, and function pointer
#define N_STATES  3
#define N_EVENTS  4

typedef enum 
  STATE_A,
  STATE_B,
  STATE_C,
 StateId;

typedef enum 
  NOEVENT,
  EVENT1,
  EVENT2,
  EVENT3,
 Event;
typedef void (*StateRoutine)();

// Assert on number of states and events defined
static_assert(STATE_C==N_STATES-1,
  "Number of states does not match defined number of states");
static_assert(EVENT3==N_EVENTS-1,
  "Number of events does not match defined number of events");

// Defining State, holds both state id and state routine
typedef struct 
    StateId id;
    StateRoutine routine;
  State;

// General functions
void evaluate_state(Event e);

// State routines to be executed at each state
void state_routine_a(void);
void state_routine_b(void);
void state_routine_c(void);

// Defining each state with associated state routine
const State state_a = STATE_A, state_routine_a;
const State state_b = STATE_B, state_routine_b;
const State state_c = STATE_C, state_routine_c;

// Defning state transition matrix as visualized in the header (events not
// defined, result in mainting the same state)
State state_transition_mat[N_STATES][N_EVENTS] = 
    state_a, state_b, state_a, state_a,
    state_b, state_b, state_c, state_b,
    state_c, state_c, state_c, state_a;

// Define current state and initialize
State current_state = state_a;

int main()

    while(1) 
    // Event to receive from user
    int ev;

    printf("----------------\n");
    printf("Current state: %c\n", current_state.id + 65);
    printf("Event to occur: ");
    // Receive event from user
    scanf("%u", &ev);
    evaluate_state((Event) ev); // typecast to event enumeration type
    printf("-----------------\n");
    ;
    return (0);


/*
 * Determine state based on event and perform state routine
 */
void evaluate_state(Event ev)

    //Determine state based on event
  current_state = state_transition_mat[current_state.id][ev];
  printf("Transitioned to state: %c\n", current_state.id + 65);
    // Run state routine
    (*current_state.routine)();


/*
 * State routines
 */
void state_routine_a() 
  printf("State A routine ran. \n");


void state_routine_b() 
  printf("State B routine ran. \n");

void state_routine_c() 
  printf("State C routine ran. \n");


【讨论】:

【参考方案3】:

您可以在 c 中使用极简的 UML 状态机框架。 https://github.com/kiishor/UML-State-Machine-in-C

它支持有限和分层状态机。它只有 3 个 API、2 个结构和 1 个枚举。

状态机由state_machine_t 结构表示。它是一种抽象结构,可以被继承来创建状态机。

//! Abstract state machine structure
struct state_machine_t

   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
;

状态由指向框架中state_t结构的指针表示。

如果框架配置为有限状态机,则state_t 包含,

typedef struct finite_state_t state_t;

// finite state structure
typedef struct finite_state_t
  state_handler Handler;        //!< State handler function (function pointer)
  state_handler Entry;          //!< Entry action for state (function pointer)
  state_handler Exit;           //!< Exit action for state (function pointer)
finite_state_t;

框架提供了一个 API dispatch_event 将事件分派到状态机和两个用于状态遍历的​​ API。

state_machine_result_t dispatch_event(state_machine_t* const pState_Machine[], uint32_t quantity);
state_machine_result_t switch_state(state_machine_t* const, const state_t*);

state_machine_result_t traverse_state(state_machine_t* const, const state_t*);

有关如何实现分层状态机的更多详细信息,请参阅 GitHub 存储库。

代码示例https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.mdhttps://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine_enhanced/readme.md

【讨论】:

能否也添加一个符合问题的代码示例? 仓库中的demo文件夹有一个例子。 github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/…。我目前正在研究另外一个涉及按键、led 和计时器的嵌入式系统示例,但它仍然不完整。准备好后会通知您。【参考方案4】:

在Martin Fowler's UML Distilled 中,他在第 10 章状态机图(强调我的)中声明(没有双关语):

状态图可以通过三种主要方式实现:嵌套开关状态模式状态表

让我们用一个手机显示屏状态的简化示例:

嵌套开关

Fowler 给出了一个 C# 代码示例,但我已经根据我的示例对其进行了调整。

public void HandleEvent(PhoneEvent anEvent) 
    switch (CurrentState) 
    case PhoneState.ScreenOff:
        switch (anEvent) 
        case PhoneEvent.PressButton:
            if (powerLow)  // guard condition
                DisplayLowPowerMessage(); // action
                // CurrentState = PhoneState.ScreenOff;
             else 
                CurrentState = PhoneState.ScreenOn;
            
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        
        break;
    case PhoneState.ScreenOn:
        switch (anEvent) 
        case PhoneEvent.PressButton:
            CurrentState = PhoneState.ScreenOff;
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        
        break;
    case PhoneState.ScreenCharging:
        switch (anEvent) 
        case PhoneEvent.UnplugPower:
            CurrentState = PhoneState.ScreenOff;
            break;
        
        break;
    

状态模式

这是我使用 GoF 状态模式的示例的实现:

状态表

从 Fowler 那里获得灵感,下面是我的示例表:

源状态目标状态事件保护动作 -------------------------------------------------- ---------------------------------- ScreenOff ScreenOff pressButton powerLow displayLowPowerMessage ScreenOff ScreenOn pressButton !powerLow ScreenOn ScreenOff 按下按钮 ScreenOff Screen充电插头电源 ScreenOn Screen充电插头电源 ScreenCharging ScreenOff 拔掉电源

比较

嵌套开关将所有逻辑集中在一个位置,但是当有很多状态和转换时,代码可能难以阅读。它可能比其他方法更安全、更容易验证(没有多态性或解释)。

状态模式实现可能会将逻辑分散到几个单独的类中,这可能会使理解它作为一个整体成为一个问题。另一方面,小班分开很容易理解。如果您通过添加或删除转换来更改行为,则该设计特别脆弱,因为它们是层次结构中的方法,并且可能对代码进行大量更改。如果你遵循小接口的设计原则,你会发现这种模式做得并不好。但是,如果状态机是稳定的,则不需要进行此类更改。

状态表方法需要为内容编写某种解释器(如果您对所使用的语言进行了反思,这可能会更容易),这可能需要大量工作。正如 Fowler 指出的那样,如果您的表与您的代码是分开的,您可以修改软件的行为而无需重新编译。然而,这有一些安全隐患;该软件的行为基于外部文件的内容。

编辑(不是真正的 C 语言)

还有一种流畅的界面(也称为内部特定领域语言)方法,这可能由具有first-class functions 的语言提供便利。 Stateless library 存在,该博客显示了一个带有代码的简单示例。讨论了Java implementation (pre Java8)。我也看到了Python example on GitHub。

【讨论】:

你用什么软件制作图片? 我怀疑它可能是通过 PlantUML 创建的 plantuml.com/state-diagram【参考方案5】:

我在 edx.org 课程 Embedded Systems - Shape the World UTAustinX - UT.6.02x,第 10 章中发现了 Moore FSM 的一个非常巧妙的 C 实现,作者 Jonathan Valvano 和 Ramesh Yerraballi....

struct State 
  unsigned long Out;  // 6-bit pattern to output
  unsigned long Time; // delay in 10ms units 
  unsigned long Next[4]; // next state for inputs 0,1,2,3
; 

typedef const struct State STyp;

//this example has 4 states, defining constants/symbols using #define
#define goN   0
#define waitN 1
#define goE   2
#define waitE 3


//this is the full FSM logic coded into one large array of output values, delays, 
//and next states (indexed by values of the inputs)
STyp FSM[4]=
 0x21,3000,goN,waitN,goN,waitN, 
 0x22, 500,goE,goE,goE,goE,
 0x0C,3000,goE,goE,waitE,waitE,
 0x14, 500,goN,goN,goN,goN;
unsigned long currentState;  // index to the current state 

//super simple controller follows
int main(void) volatile unsigned long delay;
//embedded micro-controller configuration omitteed [...]
  currentState = goN;  
  while(1)
    LIGHTS = FSM[currentState].Out;  // set outputs lines (from FSM table)
    SysTick_Wait10ms(FSM[currentState].Time);
    currentState = FSM[currentState].Next[INPUT_SENSORS];  
  

【讨论】:

【参考方案6】:

对于支持 __COUNTER__ 的编译器,您可以将它们用于简单(但较大)的状态混搭。

  #define START 0      
  #define END 1000

  int run = 1;
  state = START;    
  while(run)
  
    switch (state)
    
        case __COUNTER__:
            //do something
            state++;
            break;
        case __COUNTER__:
            //do something
            if (input)
               state = END;
            else
               state++;
            break;
            .
            .
            .
        case __COUNTER__:
            //do something
            if (input)
               state = START;
            else
               state++;
            break;
        case __COUNTER__:
            //do something
            state++;
            break;
        case END:
            //do something
            run = 0;
            state = START;
            break;
        default:
            state++;
            break;
      
   

使用__COUNTER__ 而不是硬编码的数字的好处是你 可以在其他状态中间添加状态,而无需每次都重新编号。 如果编译器不支持__COUNTER__,则可以谨慎使用__LINE__

【讨论】:

你能解释一下你的答案吗? 在正常的“切换”状态下,你有例如案例 0,案例 1,案例 2,...案例 100。如果现在要在 5 和 6 之间添加 3 个案例,则必须将其余案例重新编号为 100,即现在的 103。__COUNTER__eliminates 的使用需要重新编号,因为预编译器会在编译期间进行编号。【参考方案7】:

我最喜欢的模式之一是状态设计模式。对相同的给定输入集做出不同的响应或表现不同。 对状态机使用 switch/case 语句的问题之一是,当您创建更多状态时,switch/case 变得更难/难以阅读/维护,促进无组织的意大利面条代码,并且越来越难以在不破坏某些内容的情况下进行更改。我发现使用设计模式可以帮助我更好地组织数据,这就是抽象的重点。 不要围绕您来自的状态来设计您的状态代码,而是构建您的代码,以便在您进入新状态时记录状态。这样,您就可以有效地记录您之前的状态。我喜欢@JoshPetit 的回答,并且直接从 GoF 书中采取了他的解决方案:

stateCtxt.h:

#define STATE (void *)
typedef enum fsmSignal

   eEnter =0,
   eNormal,
   eExit
FsmSignalT;

typedef struct fsm 

   FsmSignalT signal;
   // StateT is an enum that you can define any which way you want
   StateT currentState;
FsmT;
extern int STATECTXT_Init(void);
/* optionally allow client context to set the target state */
extern STATECTXT_Set(StateT  stateID);
extern void STATECTXT_Handle(void *pvEvent);

stateCtxt.c:

#include "stateCtxt.h"
#include "statehandlers.h"

typedef STATE (*pfnStateT)(FsmSignalT signal, void *pvEvent);

static FsmT      fsm;
static pfnStateT UsbState ;

int STATECTXT_Init(void)
    
    UsbState = State1;
    fsm.signal = eEnter;
    // use an enum for better maintainability
    fsm.currentState = '1';
    (*UsbState)( &fsm, pvEvent);
    return 0;


static void ChangeState( FsmT *pFsm, pfnStateT targetState )

    // Check to see if the state has changed
    if (targetState  != NULL)
    
        // Call current state's exit event
        pFsm->signal = eExit;
        STATE dummyState = (*UsbState)( pFsm, pvEvent);

        // Update the State Machine structure
        UsbState = targetState ;

        // Call the new state's enter event
        pFsm->signal = eEnter;            
        dummyState = (*UsbState)( pFsm, pvEvent);
    


void STATECTXT_Handle(void *pvEvent)

    pfnStateT newState;

    if (UsbState != NULL)
    
         fsm.signal = eNormal;
         newState = (*UsbState)( &fsm, pvEvent );
         ChangeState( &fsm, newState );
            



void STATECTXT_Set(StateT  stateID)

     prevState = UsbState;
     switch (stateID) 
     
         case '1':               
            ChangeState( State1 );
            break;
          case '2':
            ChangeState( State2);
            break;
          case '3':
            ChangeState( State3);
            break;
     

statehandlers.h:

/* define state handlers */
extern STATE State1(void);
extern STATE State2(void);
extern STATE State3(void);

statehandlers.c:

#include "stateCtxt.h:"

/* Define behaviour to given set of inputs */
STATE State1(FsmT *fsm, void *pvEvent)
   
    STATE nextState;
    /* do some state specific behaviours 
     * here
     */
    /* fsm->currentState currently contains the previous state
     * just before it gets updated, so you can implement behaviours 
     * which depend on previous state here
     */
    fsm->currentState = '1';
    /* Now, specify the next state
     * to transition to, or return null if you're still waiting for 
     * more stuff to process.  
     */
    switch (fsm->signal)
    
        case eEnter:
            nextState = State2;
            break;
        case eNormal:
            nextState = null;
            break;
        case eExit:
            nextState = State2;
            break;
    

    return nextState;


STATE  State3(FsmT *fsm, void *pvEvent)

    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '2';
    /* Now, specify the next state
     * to transition to
     */
     return State1;


STATE   State2(FsmT *fsm, void *pvEvent)
   
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '3';
    /* Now, specify the next state
     * to transition to
     */
     return State3;

对于大多数状态机,尤其是。有限状态机,每个状态都会知道它的下一个状态应该是什么,以及转换到下一个状态的标准。对于松散状态设计,情况可能并非如此,因此可以选择公开 API 以转换状态。如果您需要更多抽象,可以将每个状态处理程序分离到自己的文件中,这相当于 GoF 书中的具体状态处理程序。如果您的设计很简单,只有几个状态,那么为了简单起见,stateCtxt.c 和 statehandlers.c 都可以合并到一个文件中。

【讨论】:

State3 和 State2 有返回值,即使声明为 void。【参考方案8】:

我也使用了表格方法。但是,有开销。为什么要存储第二个指针列表? C 中没有 () 的函数是 const 指针。所以你可以这样做:

struct state;
typedef void (*state_func_t)( struct state* );

typedef struct state

  state_func_t function;

  // other stateful data

 state_t;

void do_state_initial( state_t* );
void do_state_foo( state_t* );
void do_state_bar( state_t* );

void run_state( state_t* i ) 
    i->function(i);
;

int main( void ) 
    state_t state =  do_state_initial ;

    while ( 1 ) 
        run_state( state );

        // do other program logic, run other state machines, etc
    

当然,根据您的恐惧因素(即安全与速度),您可能需要检查有效指针。对于大于三个左右状态的状态机,上面的方法应该比等效的开关或表格方法更少的指令。你甚至可以宏化为:

#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))

此外,我从 OP 的示例中感觉到,在考虑/设计状态机时应该进行简化。我不认为过渡状态应该用于逻辑。每个状态函数都应该能够在不明确了解过去状态的情况下执行其给定的角色。基本上,您设计的是如何从您所处的状态转换到另一个状态。

最后,不要从“功能”边界开始设计状态机,为此使用子功能。而是根据您何时必须等待某些事情发生才能继续进行划分状态。这将有助于最大限度地减少在获得结果之前必须运行状态机的次数。这在编写 I/O 函数或中断处理程序时可能很重要。

另外,还有一些经典 switch 语句的优缺点:

优点:

它是用语言编写的,所以它有记录且清晰 状态是在它们被调用的地方定义的 可以在一个函数调用中执行多个状态 所有状态通用的代码都可以在switch语句前后执行

缺点:

可以在一个函数调用中执行多个状态 所有状态通用的代码都可以在switch语句前后执行 开关实现可能很慢

请注意这两个属性,即赞成和反对。我认为这种切换为状态之间提供了过多共享的机会,并且状态之间的相互依赖可能变得难以管理。然而,对于少数状态,它可能是最可读和可维护的。

【讨论】:

您能否详细说明一下:Of course depending on your fear factor (i.e. safety vs speed) you may want to check for valid pointers.?如何做到这一点?【参考方案9】:

This article 是一个很好的状态模式(尽管它是 C++,而不是专门的 C)。

如果你能把手放在《Head First Design Patterns》这本书上,说明和例子都很清楚。

【讨论】:

【参考方案10】:

我更喜欢对大多数状态机使用表驱动方法:

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;
;

表驱动方法更易于维护和扩展,并且更易于映射到状态图。

【讨论】:

非常好的入门方式,至少对我来说是起点。需要说明的是,run_state() 的第一行有一个调皮的“.”。那不应该在那里。 如果这个答案也至少能说出其他两种方法的两个词会更好:一个带有大开关盒的“全局”方法,并用State Design Pattern分隔状态并让每个state 自己处理它的转换。 嗨,我知道这篇文章很旧,但我希望我能得到答案:) instance_data_t 变量中肯定应该有什么?我想知道如何更改中断中的状态......这是在这个变量中存储有关已处理中断的信息的好方法吗?例如存储按钮被按下的信息,因此状态应该改变。 @GRoNGoR 在我看来,您正在处理事件驱动的状态机。我认为您确实可以使用它来存储事件数据。 NUM_STATES 是如何定义的。【参考方案11】:

您的问题类似于“是否有典型的数据库实现模式”? 答案取决于你想达到什么目标?如果您想实现更大的确定性状态机,您可以使用模型和状态机生成器。 示例可在 www.StateSoft.org - SM Gallery 查看。雅努什·多布罗沃尔斯基

【讨论】:

【参考方案12】:

您可能想查看 libero FSM 生成器软件。从状态描述语言和/或(Windows)状态图编辑器中,您可以为 C、C++、java 和许多其他语言生成代码......加上漂亮的文档和图表。 来自iMatix的源代码和二进制文件

【讨论】:

【参考方案13】:

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;

【讨论】:

【参考方案14】:

对于简单的状态机,只需为您的状态使用 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;
    
    ...

【讨论】:

可能值得将“状态”放入函数中,并将其设为静态。 @Steve Melnikoff:仅当您只有一台状态机时。将其保留在函数之外,您可以拥有一组具有自己状态的状态机。 @Vicky:一个函数可以包含任意数量的状态机,如果需要,可以包含一组状态变量,如果它们没有在其他地方使用,它们可以存在于函数中(作为静态变量) .【参考方案15】:

您可能已经看到我对另一个 C 问题的回答,其中我提到了 FSM!这是我的做法:

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

可以根据具体情况进行修改。例如,您可能有一个文件FSMFILE 想要驱动您的 FSM,因此您可以将读取下一个字符的操作合并到宏本身中:

#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 的其他技术中,转换的结构隐藏在控制结构中(while、if、switch ...)并由变量值(通常是 state 变量)控制,并且它可能是一项复杂的任务将漂亮的图表与复杂的代码联系起来。

我从一篇发表在伟大的“计算机语言”杂志上的文章中学到了这项技术,不幸的是,该杂志已不再出版。

【讨论】:

从根本上说,一个好的 FSM 是关于可读性的。这提供了一个很好的接口,并且实现是最好的。遗憾的是,该语言中没有原生的 FSM 结构。我现在可以将其视为 C1X 的后期添加! 我喜欢这种用于嵌入式应用程序的方法。有没有办法将此方法与事件驱动的状态机一起使用?【参考方案16】:

有一本书的标题是Practical Statecharts in C/C++。 但是,对于我们需要的东西来说,它方式太重了。

【讨论】:

我对这本书有完全相同的反应。怎么可能需要 700 多页来描述和实现我认为非常直观和直接的东西?!?!?【参考方案17】:

还有logic grid,随着状态机变大,它更易于维护

【讨论】:

链接不见了,here is an archive【参考方案18】:

在 C++ 中,考虑State pattern。

【讨论】:

【参考方案19】:

根据我的经验,使用“switch”语句是处理多种可能状态的标准方法。尽管我很惊讶您将转换值传递给每个状态处理。我认为状态机的全部意义在于每个状态都执行一个动作。然后下一个动作/输入确定要转换到哪个新状态。所以我本来希望每个状态处理函数立即执行任何为进入状态而固定的操作,然后再决定是否需要转换到另一个状态。

【讨论】:

有不同的底层模型:Mealy 机器和 Moore 机器。 Mealy 的行动取决于过渡,Moore 的行动取决于状态。【参考方案20】:

Boost 有状态图库。 http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html

不过,我无法谈论它的用途。自己没用过(还)

【讨论】:

以上是关于有没有典型的状态机实现模式?的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot技术专题-StateMachine状态机设计

c语言设计模式--状态模式(状态机)

设计模式 状态机

设计模式 状态机

游戏设计模式之三 状态模式有限状态机 & Unity版本实现

游戏设计模式之三 状态模式有限状态机 & Unity版本实现