K&R习题1-23中,要求“编写一个程序,删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许嵌套”。

如果不考虑字符常量和字符串常量,问题确实很简单。只需要去掉//和/* */的注释。

考虑到字符常量'\''和字符串常量"he\"/*hehe*/",还有类似<secure/_stdio.h>的头文件以及表达式5/3中的除号/,情况就比较复杂了。

我想,这种问题最适合用正则表达式来解析,perl之类的语言应当很好处理,问题是这里让你用C语言实现,但是C语言对正则表达式并没有显式的支持。

学过编译原理的应该知道,正则表达式对应三型文法,也就对应着一个有限状态自动机(可以用switch偏重算法来实现,或者用状态转换矩阵/表偏重数据结构来实现),

所以这里的问题其实是设计一个状态机,把输入的字符流扔进去跑就可以了。

【一个简单的状态机】

先看《K&R》第一章的一个简单习题1-12:"编写一个程序,以每行一个单词的形式打印其输入"

在这个题目之前,1.5.4节的单词计数示例中,其实K&R已经展示了一个非常简单的状态机。但没有提到这种编程思想。

当然这个题目也可以状态机的思想来编程。

回到题目,我们设初始的状态state为OUT,表示当前字符不在单词中(不是单词的组成字符),如果当前字符在单词中(属于单词的一部分),则state设为IN。

显然字符只能处于上述两种状态之一,有了这2个状态,我们就可以借助状态来思考问题 ——

(1)当前状态为OUT:若当前字符是空白字符(空格、制表符、换行符),则维护当前状态仍为OUT;否则改变状态为IN。

(2)当前状态为IN:若遇到的当前字符是非空白字符,则维护当前状态为IN;否则改变状态为OUT。

处于不同的状态,根据题意可以给予相对应的动作——

每当状态为IN的时候,意味字符属于单词的一部分,输出当前字符;

而当状态从IN切换为OUT的时候,说明一个单词结束了,此时我们输出一个回车符;状态为OUT则什么也不输出;

可以看出,借助自定义的状态,可以使编程思路更加清晰。

在遍历输入字符流的时候,程序(机器)就只能处于两种状态,对应不同状态或状态切换可以有相应的处理动作。

这样的程序不妨称为“状态机”。

按照上面的思路,代码实现就非常简单了——

#include <stdio.h>
#define OUT 0 /* outside aword */
#define IN 1  /* inside a word  */int main(void)
{int c, state;state = OUT;while ( ( c = getchar()) != EOF ) {if (state == OUT) {if (c == ' ' ||c == '\t' || c == '\n')state =OUT;else {state = IN;putchar(c);//action}} else {if (c != ' '&& c != '\t' && c != '\n') {state = IN;putchar(c);//action} else {putchar('\n');//actionstate =OUT;}}}return 0;
}

让我们回到主题吧——

【“编写一个程序,删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许嵌套”】

按照注释的各方面规则,我们来设计一个状态机——

00)设正常状态为0,并初始为正常状态

每遍历一个字符,就依次检查下列条件,若成立或全部检查完毕,则回到这里检查下一个字符

01)状态0中遇到/,说明可能会遇到注释,则进入状态1          ex. int a = b; /

02)状态1中遇到*,说明进入多行注释部分,则进入状态2         ex. int a= b; /*

03)状态1中遇到/,说明进入单行注释部分,则进入状态4         ex. int a = b; //

04)状态1中没有遇到*或/,说明/是路径符号或除号,则恢复状态0      ex. <secure/_stdio.h> or 5/3

05)状态2中遇到*,说明多行注释可能要结束,则进入状态3        ex. int a = b; /*heh*

06)状态2中不是遇到*,说明多行注释还在继续,则维持状态2       ex. int a = b; /*hehe

07)状态3中遇到/,说明多行注释要结束,则恢复状态0          ex. int a = b; /*hehe*/

08)状态3中不是遇到/,说明多行注释只是遇到*,还要继续,则恢复状态2  ex. int a = b; /*hehe*h

09)状态4中遇到回车符\n,说明单行注释结束,则恢复状态0        ex. int a = b; //hehe<enter>

10)状态0中遇到',说明进入字符常量中,则进入状态5           ex. char a = '

11)状态5中遇到\,说明遇到转义字符,则进入状态6           ex. char a = '\

12)状态6中遇到任何字符,都恢复状态5                 ex.char a = '\n 还有如'\t', '\'', '\\' 等主要是防止'\'',误以为结束

13)状态5中遇到',说明字符常量结束,则进入状态0           ex. char a = '\n'

14)状态0中遇到",说明进入字符串常量中,则进入状态7         ex. char s[] = "

15)状态7中遇到\,说明遇到转义字符,则进入状态8           ex. char s[] = "\

16)状态8中遇到任何字符,都恢复状态7                 ex. char s[] = "\n 主要是防止"\",误以为结束

17)状态7中遇到"字符,说明字符串常量结束,则恢复状态0        ex. char s[] = "\"hehe"

前面说过,不同状态可以有相应的动作。比如状态0、5、6、7、8都需要输出当前字符,再考虑一些特殊情况就可以了。

读者实现时可以借助debug宏定义,将测试语句输出到标准错误输出,需要时可以重定位到标准输出,即2>&1,然后通过重定向|到more进行查看。

有了这些状态表示,编写代码就很容易了——

#include <stdio.h>
#define debug
//#define debug(fmt, args...) fprintf(stderr, fmt, ##args)void dfa();int main(void)
{dfa();return 0;
}void dfa()
{int c, state;state = 0;while ((c = getchar()) != EOF) {if (state == 0 && c == '/')         // ex. [/]state = 1;else if (state == 1 && c == '*')     // ex. [/*]state = 2;else if (state == 1 && c == '/')    // ex. [//]state = 4;else if (state == 1) {                // ex. [<secure/_stdio.h> or 5/3]putchar('/');state = 0;}else if (state == 2 && c == '*')    // ex. [/*he*]state = 3;else if (state == 2)                // ex. [/*heh]state = 2;else if (state == 3 && c == '/')    // ex. [/*heh*/]state = 0;else if (state == 3)                // ex. [/*heh*e]state = 2;    else if (state == 4 && c == '\n')    // ex. [//hehe<enter>]state = 0;else if (state == 0 && c == '\'')     // ex. [']state = 5;else if (state == 5 && c == '\\')     // ex. ['\]state = 6;else if (state == 6)                // ex. ['\n or '\' or '\t etc.]state = 5;else if (state == 5 && c == '\'')    // ex. ['\n' or '\'' or '\t' ect.]state = 0;else if (state == 0 && c == '\"')    // ex. ["]state = 7;else if (state == 7 && c == '\\')     // ex. ["\]state = 8;else if (state == 8)                // ex. ["\n or "\" or "\t ect.]state = 7;else if (state == 7 && c == '\"')    // ex. ["\n" or "\"" or "\t" ect.]state = 0;//debug("c = %c, state = %d\n", c, state);if ((state == 0 && c != '/') || state == 5 || state == 6 || state == 7 || state == 8)putchar(c);}
}

【测试用例 a.out < test.c > test2.c】

test.c如下:

/*
*This code make no sense,
*but for exercise1_23 in<<K&R>> to test remove all comments in C code.
*/
#include <stdio.h>
#include <secure/_stdio.h>
#include <string.h>
#include "http://blog.csdn.net/bat67"
#define debug
#define LESS(i) ( ((i) << 31) / 2 )
#define STRING"\"string\"" //to ensure legal
#define CHAR '\''
int main(void)
{
#ifdef A"hehe..."
#else     /*hehe*/"blala"
#endif///*testing*/int idx;if (idx > 3 && idx < 6) idx =idx/100;/* // */   char a = '/';    // /char b = '*';    // *char c = '\'';    // 'char d = '\n';    // enterchar e = '\"';    // "   char f = '\\';    // \char g = '<';    // <char h = '>';    // >char i = '"';    // "/* special***string */char special0[] ="\"hello, world!\"";char special1[] ="//";char special2[] ="he\"/*hehe*/";char *special = " \'hi \0\b\t \\\\ \a\e\f\n\r\v wolegequ \\ ";return 0;
}

整理:状态机的编程思想相关推荐

  1. 状态机编程思想:删除代码注释(支持C/C++和Java)

    前言 有时为了信息保密或是单纯阅读代码,我们需要删除注释. 之前考虑过正则表达式,但是感觉实现起来相当麻烦.而状态机可以把多种情况归为一类状态再行分解,大大简化问题.本文就是基于状态机实现的. 删除C ...

  2. C语言状态机编程思想

    关注.星标公众号,直达精彩内容 文章来源:头条-嵌入式在左C语言在右 链接:https://www.toutiao.com/i6843028812112855564/ 有限状态机概念 有限状态机是一种 ...

  3. 基于STM32F103移植华为LiteOS_任务挂起与恢复_面向对象编程思想之按键状态机

    华为LiteOS_任务挂起与恢复_面向对象编程思想之按键状态机 因为在做华为LiteOS任务挂起和恢复需要使用到按键去触发任务挂起和恢复动作,因为我就萌发出使用状态机这种架构做一个按键检测触发.回想已 ...

  4. c语言按键状态机,C语言状态机编程思想

    原标题:C语言状态机编程思想 有限状态机概念 有限状态机是一种概念思想,把复杂的控制逻辑分解成有限个稳定状态,组成闭环系统,通过事件触发,让状态机按设定的顺序处理事务.单片机C语言的状态机编程,是利用 ...

  5. 编程思想(POP,OOP,COP,AOP,SOP) 整理

    面向过程(procedure oriented programming) POP      一种以过程为中心的编程思想,分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个 ...

  6. 嵌入式牛人 | 这些单片机编程思想超硬核

    来源:面包板社区,整理:晓宇 微信公众号:芯片之家(ID:chiphome-dy) 01 裸编程是什么? 先声明一个概念,裸编程,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系 ...

  7. Web 2.0 编程思想:16条法则(转)

    原文:Thinking in Web 2.0: Sixteen Ways 作者:Dion Hinchcliffe 1.在你开始之前,先定一个简单的目标.无论你是一个Web 2.0应用的创建者还是用户, ...

  8. Android知识架构 · Java的编程思想

    1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面.抽象只关注对象有哪些属性和行为,并不关注这些 ...

  9. 搬上小板凳,听嵌入式大牛讲解硬核单片机编程思想!

    来源:面包板社区,整理:晓宇 微信公众号:芯片之家(ID:chiphome-dy) 01 裸编程是什么? 先声明一个概念,裸编程,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系 ...

  10. 《Java编程思想》读书笔记

    前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次 ...

最新文章

  1. BUUCTF(PWN)suctf_2018_stack
  2. C#中的Decimal类型
  3. 算法训练 字符串编辑 java
  4. How does framework interpret $expand=Notes
  5. 弯下腰,拾起你无价的尊严
  6. 分布式系统:SpringBoot中Dubbo以及Zookeeper依赖包冲突 LoggerFactory is not a Logback LoggerContext but Logback
  7. 从零开始学前端:css3新属性scss和less --- 今天你学习了吗?(CSS:Day22)
  8. long 雪花算法_雪花算法(SnowFlake)Java版
  9. 通讯超时什么意思_威伦触摸屏通讯报错了怎么办?
  10. css设置图标居左_学会这几种方法css居中很简单
  11. 你实现团队管理了吗?
  12. 07.Thymeleaf使用与语法最全详解(精)
  13. Java生成二维码QRCode(亲测可通过扫码枪扫出)
  14. 《那些年啊,那些事——一个程序员的奋斗史》七
  15. Visual Studio远程调试程序
  16. Acme CAD Converter 的线宽要怎么设置啊
  17. HTML绘制七巧板,canvas绘制七巧板
  18. EC11旋转编码器驱动程序
  19. 5.PCIe协议分析3-PCIe TLP包详解1
  20. GUID partition table (GPT) 磁盘分区表详解

热门文章

  1. 手机网速测试软件排行榜,手机测网速软件,几款测速软件推荐
  2. 2020-08-05流量计怎么选你学会了么?
  3. 限行查询API_路帮网
  4. 【“计算机科学与技术”专业小白成长系列】 计算机操作系统简介
  5. 《信息系统设计与分析》读书笔记
  6. 数组与集合的区别(概况)
  7. 服务器虚拟机迁移的过程和步骤是什么,虚拟机迁移的详细步骤(新手如何做好虚拟机迁移)...
  8. 进销存系统(1):开源ECP编译安装
  9. 锐浪报表使用技巧Gird++
  10. macOS远程管理linux,MacOS远程控制工具