状态机基本概念以及使用状态机实现单词计数(C语言)

转载时请表明出处
作者联系方式:liuyuxin0829@qq.com

  在本周的培训内容中,接触到了“状态机”一词,这是什么意思?用来做什么?怎么做?以下记录了初识状态机的学习感悟,并使用状态机原理实现了简单的单词计数实例。

什么是状态机?

  根据查阅到的资料总结,状态机是一个有向图形,又可称状态转移图,由一组节点和一组相应的转移函数组成。

 举一个简单的例子:人有三个状态(节点):健康、感冒、康复中。触发的条件(转移函数)有淋雨、吃药、打针、休息。状态机如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kBdMSc9a-1603538195649)(E:\个人博客\1.状态机以及使用状态机实现单词计数(C语言)\images\finite_state_machine.jpg)]

  使用状态机编写程序时,常用到4个概念:

  1. 状态(State):一个状态机至少包括两个状态,如上面例子,有健康、感冒、康复中三个状态;
  2. 事件(Event):事件就是执行某个操作的触发条件或者口令。对于“淋雨”就是一个事件。
  3. 动作(Action):事件发生后要执行动作,动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件发生后,也可以不执行任何动作,直接迁移到新状态。编程时,一个Action一般对应一个函数。
  4. 转换(Transition):也就是从一个状态变化为另一个状态,例如从健康到感冒就是一个变换。

状态机用来做什么?

  状态机是一个对真实世界的抽象,而且是逻辑严谨的数学抽象,所以非常时候用在数学领域。可以应用到各个层面上,例如硬件设计、编译器设计、以及编程实现各种具体业务逻辑,如下面的使用状态机实现单词计数,从单词计数可扩展至解析xml文件等等。

  举一些生活中常见的例子:电灯的开关逻辑、自动贩卖机的售卖货物的逻辑、空调的控制逻辑等都可以抽象为状态机。

使用状态机实现单词计数

  问题描述:一个字符串由多个单词组成,这些单词由空格、逗号、句点、换行符等多种符号隔开,请写一个程序统计输入的字符串中有多少个单词。

构建状态机模型:

  1. 字符类型:英文字符、符号;

  2. 字符状态:起始状态、单词状态、符号状态、结束状态;

  3. 状态转换规则:

    (1)起始状态下读到非符号,进入单词状态;

    (2)单词状态下读到符号,进入符号状态;

    (3)符号状态下读到非符号,进入单词状态;

    (4)在起始状态、单词状态、符号状态下读到‘ \0’,进入结束状态。

  4. 动作:每次进入单词状态,单词计数加1。

实现代码如下:

/**
 * 判断字符是否为符号。
 */
#define IS_SIGN(c) ((c) == ' ' || (c) == ',' || (c) == '.' || (c) == '\n' || (c) == '?' || (c) == '!')

/**
 * @enum char_state_t
 * 字符在字符串中的状态。
 */
typedef enum _char_state_t {
  /**
   * @const STATE_INIT
   * 起始状态。
   */
  STATE_START = 0,
  /**
   * @const STATE_WORD
   * 单词状态。
   */
  STATE_WORD,
  /**
   * @const STATE_SIGN
   * 符号状态。
   */
  STATE_SIGN,
  /**
   * @const STATE_END
   * 结束状态。
   */
  STATE_END
} char_state_t;
 
/**
 * @method count_word
 * 使用状态机计算字符串中单词个数。
 * 
 * @param {const char*} text 需要计算的字符串。
 * 
 * @return {int32_t} 单词个数(text为NULL时返回-1)。
 */
int32_t count_word(const char* text) {
  if(text == NULL) return -1;
  int32_t count = 0;
  char_state_t state = STATE_START;
  char curr = '\0';
  while (curr = *text++) {
    switch (state) {
      case STATE_START: /* 起始状态 */
        if (IS_SIGN(curr)) {
          state = STATE_SIGN;
        } else {
          state = STATE_WORD;
          count++;
        }
        break;
      case STATE_WORD: /* 单词状态 */
        if (IS_SIGN(curr)) {
          state = STATE_SIGN;
        }
        break;
      case STATE_SIGN: /* 符号状态 */
        if (!IS_SIGN(curr)) {
          state = STATE_WORD;
          count++;
        }
        break;
      default:
        break;
    }
  }
  state = STATE_END;
  return count;
}

int main(int argc, char* argv[]) {
  int ret = 0;
  if (argc == 2) {
    ret = count_word_state(argv[1]);
    printf("Word Number = %d\n", ret);
  } else {
    printf("Usage: [\"string\"]\n");
    ret = 0;
  }
  return ret;
}

总结

  状态机是一个数学模型,可理解为有向图,通常体现为一个状态转换图,涉及到状态(State)、事件(Event)、动作(Action)、转换(Transition)。状态机是计算机科学的重要基础概念之一,也可以说是一种总结归纳问题的思想,应用非常广泛。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是一个使用状态机实现的单片机矩阵键盘长按的代码示例,同样以C语言为例: ```c #include <reg52.h> sbit key1 = P1^0; // 第1列第1行 sbit key2 = P1^1; // 第1列第2行 sbit key3 = P1^2; // 第1列第3行 sbit key4 = P1^3; // 第1列第4行 sbit key5 = P1^4; // 第2列第1行 sbit key6 = P1^5; // 第2列第2行 sbit key7 = P1^6; // 第2列第3行 sbit key8 = P1^7; // 第2列第4行 unsigned char key_value = 0; // 存储键值 unsigned char key_state = 0; // 按键状态,0表示未按下,1表示按下 unsigned char key_press_count = 0; // 按键计数器 unsigned char long_press_time = 50; // 长按时间阈值,单位为10ms,即500ms enum KEY_STATE // 定义状态机的状态 { KEY_IDLE, KEY_PRESSED, KEY_LONG_PRESSED, KEY_RELEASED } key_current_state = KEY_IDLE; void key_scan() // 扫描键盘 { switch (key_current_state) { case KEY_IDLE: if (key1 == 0 || key2 == 0 || key3 == 0 || key4 == 0 || key5 == 0 || key6 == 0 || key7 == 0 || key8 == 0) // 判断是否有按键按下 { key_current_state = KEY_PRESSED; } break; case KEY_PRESSED: if (key_press_count < long_press_time) // 判断是否长按 { if (key1 == 1 && key2 == 1 && key3 == 1 && key4 == 1 && key5 == 1 && key6 == 1 && key7 == 1 && key8 == 1) // 判断是否松开 { key_current_state = KEY_RELEASED; } else { key_press_count++; } } else // 长按 { key_current_state = KEY_LONG_PRESSED; } break; case KEY_LONG_PRESSED: if (key1 == 1 && key2 == 1 && key3 == 1 && key4 == 1 && key5 == 1 && key6 == 1 && key7 == 1 && key8 == 1) // 判断是否松开 { key_value = 0xff; // 用0xff表示长按事件 key_current_state = KEY_IDLE; } break; case KEY_RELEASED: if (key1 == 1 && key2 == 1 && key3 == 1 && key4 == 1 && key5 == 1 && key6 == 1 && key7 == 1 && key8 == 1) // 判断是否松开 { if (key_press_count < long_press_time) // 判断是否短按 { key_value = key_press_count; // 用按键计数器的值表示键值 } key_current_state = KEY_IDLE; } else { key_press_count = 0; key_current_state = KEY_PRESSED; } break; default: break; } } void main() { while (1) { key_scan(); // 扫描键盘 if (key_value != 0) // 有按键按下 { // 进行相应的操作,比如控制LED灯亮灭 if (key_value == 1) // 按键1被按下 { P2 = 0x01; // 控制P2口的第1位为高电平,点亮LED1 } else if (key_value == 2) // 按键2被按下 { P2 = 0x02; // 控制P2口的第2位为高电平,点亮LED2 } else if (key_value == 0xff) // 长按事件 { // 进行相应的操作 } key_value = 0; // 清空键值 } } } ``` 在上面的代码中,我们使用了一个状态机实现键盘扫描、按键计数以及长按事件的处理。在状态机中,我们定义了四种状态,分别为`KEY_IDLE`、`KEY_PRESSED`、`KEY_LONG_PRESSED`和`KEY_RELEASED`,并根据键盘的按下、松开以及按键计数器的值来切换状态。在`main()`函数中,我们同样通过判断键值来进行相应的操作。相比于使用中断实现使用状态机可以更好地把握程序的流程,代码也更加清晰易懂。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值