查表C语言状态机,geek青年的状态机,查表,纯C语言实现

enum message { play, stop, forward, backward, record, pause };

4. 状态迁移表

在讲如何查表前,我们先设计 表 本身。我们期待表格能够描述 状态迁移 中的要素。记得么,一共4个。 (1) 当前状态, (2)当前消息, (3)将迁移到的状态,(4)在状态迁移中的动作。我们期待能用表格,而不是如普通青年一文中用代码(switch-case)的方式描述。因为我们相信,改表格比改代码容易。

状态迁移表与状态迁移图完全等价。

2ca8ff5a62574783302a235f1625e220.png

表格看起来像下面这样,如果想像划上竖线效果更佳。

1 struct transition fsm[transition_num] = {

2         /* current_state, message/event, next_state*/

3     {s_play,stop,s_stop},

4     {s_play,pause,s_pause},

5     {s_pause,pause,s_play},

6     {s_pause,stop,s_stop},

7     {s_stop,forward,s_forward},

8     {s_stop,play,s_play},

9     {s_stop,backward,s_backward},

10     {s_stop,record,s_record},

11     {s_forward,stop,s_stop},

12     {s_backward,stop,s_stop},

13     {s_record,stop,s_stop} };

每一行,都是一组状态迁移的匹配。第一列是当前状态,第二列是接收到的消息,第三列是在此种情况下将迁移到的状态。每增加一个迁移的匹配,我们就按这样的规则增加一行。这规定了状态机迁移中4要素里的3条,剩下的那条是在迁移中的动作,后面再介绍。

当然,为了遵循C语言的语法,我们需要在此前就定义 (1) 状态枚举、 (2) 消息枚举,还有 (3) 迁移的结构体。如下。

1 enum state { s_stop=‘s‘, s_play=‘p‘, s_forward=‘f‘, s_backward=‘b‘, s_pause=‘_‘, s_record=‘r‘  };

2 enum message { play, stop, forward, backward, record, pause };

3

4 struct transition {

5     enum state current;

6     enum message m;

7     enum state next;

8 };

我们还需要定义一共多少条迁移规则,是为了我们还没有写出来的代码准备的,不过此处已经用到,所以定义如下。

1 #define transition_num  11

5. 迁移时的动作

我们希望把迁移时的动作放在每个状态到达之处。即,每个状态都可以有一些"副作用"。这与迁移时的动作是等价的,证明略去。如果仅想在迁移时写代码,也可以利用这种方法实现。

状态机的动作 表格如下:

1 struct state_action state_action_map[state_num] = {

2     {s_stop, do_stop},

3     {s_play, do_play},

4     {s_forward, do_forward},

5     {s_backward, do_backward},

6     {s_pause, do_pause},

7     {s_record, do_record}};

每一行,是一个状态对应的动作。第一列是状态,第二列是对应的动作。这样,每增加一个状态 (如果它有对应动作),就在这里加入一行;动作对应的函数需要实现,后面会介绍。

类似于状态迁移图,为了遵循C语言语法,我们需要在此前声明如下。

1 #define state_num 6

2 typedef void (*action_foo)() ;

3

4 enum state { s_stop=‘s‘, s_play=‘p‘, s_forward=‘f‘, s_backward=‘b‘, s_pause=‘_‘, s_record=‘r‘  };

5

6 /* action starts */

7 void do_stop() {printf ("I am in state stop and should doing something here.\n");}

8 void do_play() {printf ("I am in state play and should doing something here.\n");}

9 void do_forward() {printf ("I am in state forward and should doing something here.\n");}

10 void do_backward() {printf ("I am in state backward and should doing something here.\n");}

11 void do_pause() {printf ("I am in state pause and should doing something here.\n");}

12 void do_record() {printf ("I am in state record and should doing something here.\n");}

13

14 struct state_action {

15     enum state m_state;

16     action_foo foo;

17 };

第1行,是状态的数量。第2行和第7行到第12行,以及第16行,使用了函数指针(指向函数的指针,一个指针,它的基类型是一个函数),用于表示要执行的动作。第4行,是状态枚举。第14行到第17行,是 状态-动作 对应关系的结构体。

第7行至第12行,是动作的执行部分。当增加的状态需要动作时,程序员要在此处加入一个函数,它遵守第2行的签名约定。

6. 引擎

如果表格的数据结构已定,代码就好写了。我们的引擎代码的核心部分是查表,遍历表格,找到与当前状态、当前消息匹配的将迁移到的状态。

我们还是自顶向下,假设 查表部分已经完成,为主函数提供与 普通青年一文相同的接口--而内部实现是不同的。

1 void state_change(enum message m)

2 {

3     static state = s_stop;

4     enum state next;

5     int index = 0;

6

7     index = lookup_transition(state, m, fsm);

8     if(index!=ERR)

9     {

10         state = fsm[index].next;

11         lookup_action(state, state_action_map)();

12     }

13     return;

14 }

如第3行如示,初始状态是 停止。在第7行,我们引用了一个尚未写好的函数,lookup_transition。虽然函数还不存在,不过我们能猜出来它的作用,查表,找到 当前状态是 state,当前消息是 m 时所对应的表项的下标 index。fsm参数是为了可能有多个状态迁移表设计的,此处可以略过。

当查找到 index 以后,且 index 不是 ERR (没找到),就可以令 下一个状态为state = fsm[index].next,见第10行。

以上,完成了状态迁移4要素中的3个:当前状态、当前消息、将迁移到的状态。

第11行,完成的功能是执行与状态对应的动作。这里又用到函数指针。在代码 lookup_action(state, state_action_map)() 中,lookup_action(state, state_action_map) 用于找到状态 state 对应的动作,后面的 "()",是因为这个动作是一个函数指针,可以使用这样的方式执行这个指针指向的函数。与上文中的 fsm 参数类似,state_action_map是为了应对有多个状态-动作表的情况,这里可以略过。

无论数据 (状态迁移、状态-动作)如何变化,引擎代码都不会变化。所以,甚至可以把引擎放在静态或动态链接库里,或者把数据放在外部文件里,运行时再载入,从而提高部署时的灵活性。

7. 查表

刚刚用到的两个未定义的函数 lookup_transition(state, m, fsm) 和 lookup_action(state, state_action_map) 都使用了查表的方法。

代码如下。可以看出,二者的结构非常类似,遍历数组 (for循环) ,找到符合条件的元素 (if判断),然后把该元素的索引或者该元素结构体的某个成员返回。

ERR 和 ACTION_NOT_FOUND 是用来容错的,万一表格有误,没有查到匹配的项。

1 int const ERR = -1;

2 int lookup_transition (enum state s, enum message m, struct transition * t)

3 {

4     int ret=ERR;

5     int i;

6     for(i=0;i

7     {

8         if(t[i].current == s && t[i].m == m)

9         {

10             ret = i;

11         }

12     }

13     return ret;

14 }

15

16 action_foo ACTION_NOT_FOUND = NULL;

17 action_foo lookup_action(enum state s, struct state_action* a)

18 {

19     action_foo ret = ACTION_NOT_FOUND;

20     int i=0;

21     for (i=0;i

22     {

23         if(s == a[i].m_state)

24         {

25             ret = a[i].foo;

26         }

27     }

28     return ret;

29 }

8. 总结

geek青年,从接口上看,与普通青年并无不同。甚至在情况相对简单 (状态少、状态迁移种类少) 的时候,代码量比普通青年还有不如。那么,geek青年的长处在哪里呢?

古人云:沧海横流方显英雄本色。古人又云:大丈夫山崩于前不变色,海啸于后不动容。

geek青年的长处在于,他始终如一,无论遇到的情形是多么糟糕多么恶劣,他始终没有变化。这个世界上,总需要一些因素,一些承诺,不随任何易变的感情、任何旁人不能承受的痛苦或诱惑而变化,稳定地坚持。这才能让我们对这个世界保留一丝希望,未来才能够和值得期待。

原文:http://blog.csdn.net/younggift/article/details/35848677

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值