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

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

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

另外,还有单行注释//中用\进行折行注释的蛋疼情况(这个情况连很多编辑器高亮都没考虑到)。

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

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

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

那什么是状态机呢?K&R第一章怎么没有介绍呢?

【一个简单的状态机】

先看《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 a word */
#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');//action
state = OUT;
}
}
}
return ;
}

让我们回到主题吧——

【“编写一个程序,删除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中遇到\,说明可能进入折行注释部分,则进入状态9       ex. int a = b; //hehe\

10)状态9中遇到\,说明可能进入折行注释部分,则维护状态9       ex. int a = b; //hehe\\\

11)状态9中遇到其它字符,则说明进入了折行注释部分,则恢复状态4    ex. int a = b; // hehe\a or hehe\<enter>

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

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

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

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

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

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

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

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

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

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

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

上面的状态机涉及到了[0, 9]一共10种状态,对应的状态转换图(或者说状态机/自动机)如下:

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

 /*
*Copyright (C) Zhang Haiba
*Date 2014-02-26
*File exercise1_23.c
*
*this program removes all comments in grammatical C code,
*and as a integrity solution for exercise1-23 in <<K&R>>
*/ #include <stdio.h>
#define debug
//#define debug(fmt, args...) fprintf(stderr, fmt, ##args) void dfa(); int main(void)
{
dfa();
return ;
} void dfa()
{
int c, state; state = ;
while ((c = getchar()) != EOF) {
if (state == && c == '/') // ex. [/]
state = ;
else if (state == && c == '*') // ex. [/*]
state = ;
else if (state == && c == '/') // ex. [//]
state = ;
else if (state == ) { // ex. [<secure/_stdio.h> or 5/3]
putchar('/');
state = ;
} else if (state == && c == '*') // ex. [/*he*]
state = ;
else if (state == ) // ex. [/*heh]
state = ; else if (state == && c == '/') // ex. [/*heh*/]
state = ;
else if (state == ) // ex. [/*heh*e]
state = ; else if (state == && c == '\\') // ex. [//hehe\]
state = ;
else if (state == && c == '\\') // ex. [//hehe\\\\\]
state = ;
else if (state == ) // ex. [//hehe\<enter> or //hehe\a]
state = ;
else if (state == && c == '\n') // ex. [//hehe<enter>]
state = ; else if (state == && c == '\'') // ex. [']
state = ;
else if (state == && c == '\\') // ex. ['\]
state = ;
else if (state == ) // ex. ['\n or '\' or '\t etc.]
state = ;
else if (state == && c == '\'') // ex. ['\n' or '\'' or '\t' ect.]
state = ; else if (state == && c == '\"') // ex. ["]
state = ;
else if (state == && c == '\\') // ex. ["\]
state = ;
else if (state == ) // ex. ["\n or "\" or "\t ect.]
state = ;
else if (state == && c == '\"') // ex. ["\n" or "\"" or "\t" ect.]
state = ; //debug("c = %c, state = %d\n", c, state); if ((state == && c != '/') || state == || state == || state == || state == )
putchar(c);
}
}

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

test.c如下:

 /*
*This code make no sense(Compiled successfully),
*but for exercise1_23 in <<K&R>> to test remove all comments in C code.
*/ # include <stdio.h>
# include <secure/_stdio.h>
#include "/Users/apple/blog/zhanghaiba/KandR/test.h"
#define CHAR '\'' /*/test/*/
# define LESS(i) ( ((i) << ) / )
# define STRING "\"string\"" //to ensure legal int main(void)
{
int w; // \a
int x;/*hehe*/
double y; // \
double z; // \b \\\\
19 int none; ///*testing*/
int idx;
if (idx > && idx < ) idx = idx/; //go and \
24 con_tinue\
25 hehe /* // */
char a = '/'; // /
char b = '*'; // *
char c = '\''; // '
char d = '\n'; // enter
char e = '\"'; // "
char f = '\\'; // \
34 char g = '<'; // <
char h = '>'; // >
char i = '"'; // " /* special***string */
char special0[] = "\"hello, world!\"";
char special1[] = "//test";
char special2[] = "he\"/*hehe*/";
char *special = " \' hi \0\b\t \\\\ \a\e\f\n\r\v wolegequ \\ ";
return ;
}

测试截图对比如下:

(说明:由于编辑器高亮的原因,注意//后面加字符\的折行注释部分颜色其实不对的,另外17行\后面是有一个空格的)

【测试用例(2)./a.out < ~/open_src_code/glibc-2.17/malloc/malloc.c > test2.c】

glibc-2.17源码中的的malloc.c包括空行和注释有5163行,经过上面去注释后代码变成3625行。

测试发现去注释成功。这里不可能贴对比代码了。

有兴趣的读者可自行测试。

欢迎提供测试不正确的用例代码。

@Author: 张海拔

@Update: 2014-2-26

@Link: http://www.cnblogs.com/zhanghaiba/p/3569928.html

怎样删除C/C++代码中的所有注释?浅谈状态机的编程思想的更多相关文章

  1. 删除 java代码中所有的注释

    删除 java代码中所有的注释.java public class CleanCommons { private static Pattern pattern = Pattern.compile(&q ...

  2. 【转】代码中特殊的注释技术——TODO、FIXME和XXX的用处

    (转自:http://blog.csdn.net/reille/article/details/7161942) 作者:reille 本博客网址:http://blog.csdn.net/reille ...

  3. 代码中特殊的注释技术——TODO、FIXME和XXX的用处

    本文内容概要: 代码中特殊的注释技术--TODO.FIXME和XXX的用处. 前言:今天在阅读Qt  Creator的源代码时,发现一些注释中有FIXME英文单词,用英文词典居然查不到其意义!实际上, ...

  4. 编程风格——代码中特殊的注释技术——TODO、FIXME和XXX的用处

    代码中特殊的注释技术——TODO.FIXME和XXX的用处 前言:今天在阅读Qt Creator的源代码时,发现一些注释中有FIXME英文单词,用英文词典居然查不到其意义!实际上,在阅读一些开源代码时 ...

  5. 代码中特殊的注释技术——TODO、FIXME和XXX的用处 (转载)

    转自:http://blog.csdn.net/reille/article/details/7161942 作者:reille 本博客网址:http://blog.csdn.net/reille/, ...

  6. 代码中特殊的注释技术——TODO、FIXME和XXX的用处 (转载)

    转自:http://blog.csdn.net/reille/article/details/7161942 作者:reille 本博客网址:http://blog.csdn.net/reille/, ...

  7. .net中对象序列化技术浅谈

    .net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储 ...

  8. 对kotlin和java中的synchronized的浅谈

    synchronized在java中是一个关键字,但是在kotlin中是一个内联函数.假如分别在java和kotlin代码锁住同一个对象,会发生什么呢,今天写了代码试了试.首先定义people类 12 ...

  9. 代码中特殊的注释技术——TODO、FIXME和XXX的用处

    前言:今天在阅读Qt  Creator的源代码时,发现一些注释中有FIXME英文单词,用英文词典居然查不到其意义!实际上,在阅读一些开源代码时,我们常会碰到诸如:TODO.FIXME和XXX的单词,它 ...

随机推荐

  1. Day02 (黑客成长日记)

    #用户登录次数为三代码 # i = 0 # while i < 3: # username = input('请输入账号:') # password = input('请输入密码:') # if ...

  2. html-minifier中文文档

    HTMLMinifier是一个高度可配置的.经过良好测试的.基于javascript的HTML缩小器.参见相应的博客文章,了解它的工作原理.每个选项的描述.测试结果和结论.在线测试套件.还可以看到相应 ...

  3. httphandler httpmodule一些个人理解

    asp.net 对于http请求需要走一个管道就行一层一层的过滤:比如身份验证,根据请求的资源不同分发给具体哪个dll来处理 这些管道中就是httpmodule.所以我们自己写的httpmodule实 ...

  4. verilog HDL-并行语句之assign

    线网型数据对象: 是verilog hdl常用数据对象之一,起到电路节点之间的互联作用,类似于电路板上的导线. wire是verilog hdl默认的线网型数据对象. 线网型数据对象的读操作在代码任何 ...

  5. SDWebImage之SDImageCache

    SDImageCache和SDWebImageDownloader是SDWebImage库的最重要的两个部件,它们一起为SDWebImageManager提供服务,来完成图片的加载.SDImageCa ...

  6. workman的学习总结

    我们知道php主要是用来做web应用的,而且平时使用的都是都是和其他的web服务器来结合使用,比如和apache,nginx和apache的时候,是作为apache的一个动态模块来加载,和nginx的 ...

  7. STL详解

    STL概貌                                                                                                ...

  8. PHP中字符串与html相互转化函数

    在一般通信过程中,可能会遇到文档文本编码中所不包括很多字符,或者无法在键盘上输入的字符.例如,版权符号(©).分币符号(¢)和语法重音符号等就属于这种字符.为了克服这些缺点,专门设计了一组统一的按键编 ...

  9. SSD报告 - QRadar远程命令执行

    SSD报告 - QRadar远程命令执行 漏洞摘要 QRadar中的多个漏洞允许远程未经身份验证的攻击者使产品执行任意命令.每个漏洞本身并不像链接那么强大 - 这允许用户从未经身份验证的访问更改为经过 ...

  10. 用document.readyState实现网页加载进度条

    概述 之前以为给网页设置加载进度条很麻烦,今天一学真是超级简单,记录下来供以后开发时参考,相信对其他人也有用. readyState 主要运用了document.readyState和nprogres ...