K&R习题1-23中,要求“编写一个程序,删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许注释”。
如果不考虑字符常量和字符串常量,问题确实很简单。只需要去掉//和/* */的注释。
考虑到字符常量‘\‘‘和字符串常量"he\"/*hehe*/",还有类似<secure/_stdio.h>的头文件以及表达式5/3中的除号/,情况就比较复杂了。
我想,这种问题最适合用正则表达式来解析,perl之类的语言应当很好处理,问题是这里让你用C语言实现,但是C语言对正则表达式并没有显式的支持。
学过编译原理的应该知道,正则表达式对应三型文法,也就对应着一个有限状态自动机(可以用switch偏重算法来实现,或者用状态转换矩阵/表偏重数据结构来实现),
所以这里的问题其实是设计一个状态机dfa,把输入的字符流扔进去跑就可以了。
那什么是状态机呢?K&R第一章怎么没有介绍呢?
【一个简单的状态机】
先看《K&R》第一章的一个简单习题1-12:"编写一个程序,以每行一个单词的形式打印其输入"
在这个题目之前,1.5.4节的单词计数示例中,其实K&R已经展示了一个非常简单的状态机。但没有提到这种编程思想。
当然这个题目也可以状态机的思想来编程。
回到题目,我们设初始的状态state为OUT,表示当前字符不在单词中(不是单词的组成字符),如果当前字符在单词中(属于单词的一部分),则state设为IN。
显然字符只能处于上述两种状态之一,有了这两个状态,我们就可以借助状态来思考问题 ——
(1)若当前状态为OUT,而当前字符是空白字符(空格、制表符、换行符),则维护当前状态仍为OUT;否则改变状态为IN。
(2)若当前状态为IN,遇到的当前字符是非空白字符,则维护当前状态为IN;否则改变状态为OUT。
这样,每当状态为IN的时候,意味着发现了一个单词,便可以输出当前字符;
每当状态从IN切换为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 (c == ‘ ‘ || c == ‘\n‘ || c == ‘\t‘) { if (state == IN) putchar(‘\n‘); state = OUT; } else { putchar(c); state = IN; } } return 0; }
让我们回到主题——
【“编写一个程序,删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许注释”】
按照注释的各方面规则,我们来设计一个状态机——
01)设正常状态为0
02)状态0中遇到/,说明可能会遇到注释,则进入状态1 ex. int a = b; /
03)状态1中遇到*,说明进入多行注释部分,则进入状态2 ex. int a= b; /*
04)状态1中遇到/,说明进入单行注释部分,则进入状态4 ex. int a = b; //
05)状态1中既没有遇到*或/,说明/是路径符号或除号,则恢复状态0 ex. <secure/_stdio.h> or 5/3
06)状态2中遇到*,说明多行注释可能要结束,则进入状态3 ex. int a = b; /*heh*
07)状态2中不是遇到*,说明多行注释还在继续,则维持状态2 ex. int a = b; /*hehe
08)状态3中遇到/,说明多行注释要结束,则恢复状态0 ex. int a = b; /*hehe*/
09)状态3中不是遇到/,说明多行注释只是遇到*,还要继续,则恢复状态2 ex. int a = b; /*hehe*h
10)状态4中遇到回车符\n,说明单行注释结束,则恢复状态0 ex. int a = b;
//hehe<enter>
11)状态0中遇到‘,说明进入字符常量中,则进入状态5 ex. char a = ‘
12)状态5中遇到\,说明遇到转义字符,则进入状态6 ex. char a = ‘\
13)状态6中遇到任何字符,都恢复状态5 ex. char a = ‘\n 还有如‘\t‘, ‘\‘‘, ‘\\‘ 等 主要是防止‘\‘‘,误以为结束
14)状态5中遇到‘,说明字符常量结束,则进入状态0 ex. char a = ‘\n‘
15)状态0中遇到",说明进入字符串常量中,则进入状态7 ex. char s[] = "
16)状态7中遇到\,说明遇到转义字符,则进入状态8 ex. char s[] = "\
17)状态8中遇到任何字符,都恢复状态7 ex. char s[] = "\n 主要是防止"\",误以为结束
18)状态7中遇到"字符,说明字符串常量结束,则恢复状态0 ex. char s[] = "\"hehe"
这里涉及到了[0, 8]一共9种状态,对应的状态转换图(或者说状态机/自动机)如下:
有了这些状态表示,编写代码就很容易了——
1 /* 2 *Copyright (C) Zhang Haiba 3 *Date 2014-02-26 4 *File exercise1_23.c 5 * 6 *this program removes all comments in grammatical C code, 7 *and as a integrity solution for exercise1-23 in <<K&R>> 8 */ 9 10 #include <stdio.h> 11 #define debug 12 //#define debug(fmt, args...) fprintf(stderr, fmt, ##args) 13 14 void dfa(); 15 16 int main(void) 17 { 18 dfa(); 19 return 0; 20 } 21 22 void dfa() 23 { 24 int c, state; 25 26 state = 0; 27 while ((c = getchar()) != EOF) { 28 if (state == 0 && c == ‘/‘) // ex. [/] 29 state = 1; 30 else if (state == 1 && c == ‘*‘) // ex. [/*] 31 state = 2; 32 else if (state == 1 && c == ‘/‘) // ex. [//] 33 state = 4; 34 else if (state == 1) // ex. [<secure/_stdio.h> or 5/3] 35 state = 0; 36 37 else if (state == 2 && c == ‘*‘) // ex. [/*he*] 38 state = 3; 39 else if (state == 2) // ex. [/*heh] 40 state = 2; 41 42 else if (state == 3 && c == ‘/‘) // ex. [/*heh*/] 43 state = 0; 44 else if (state == 3) // ex. [/*heh*e] 45 state = 2; 46 47 else if (state == 4 && c == ‘\n‘) // ex. [//hehe<enter>] 48 state = 0; 49 50 else if (state == 0 && c == ‘\‘‘) // ex. [‘] 51 state = 5; 52 else if (state == 5 && c == ‘\\‘) // ex. [‘\] 53 state = 6; 54 else if (state == 6) // ex. [‘\n or ‘\‘ or ‘\t etc.] 55 state = 5; 56 else if (state == 5 && c == ‘\‘‘) // ex. [‘\n‘ or ‘\‘‘ or ‘\t‘ ect.] 57 state = 0; 58 59 else if (state == 0 && c == ‘\"‘) // ex. ["] 60 state = 7; 61 else if (state == 7 && c == ‘\\‘) // ex. ["\] 62 state = 8; 63 else if (state == 8) // ex. ["\n or "\" or "\t ect.] 64 state = 7; 65 else if (state == 7 && c == ‘\"‘) // ex. ["\n" or "\"" or "\t" ect.] 66 state = 0; 67 68 //debug("c = %c, state = %d\n", c, state); 69 70 if ((state == 0 && c != ‘/‘) || 71 (state == 1 && c != ‘*‘ && c != ‘/‘) || 72 state == 5 || state == 6 || state == 7 || state == 8) 73 putchar(c); 74 } 75 }
【测试用例(1)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 "/Users/apple/blog/zhanghaiba/KandR/test.h" #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‘; // enter char 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; }
测试截图对比如下——
【测试用例(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