怎样删除C++代码中的注释——有限状态机思想的使用
2017-05-30 17:45
483 查看
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则什么也不输出;
可以看出,借助自定义的状态,可以使编程思路更加清晰。
在遍历输入字符流的时候,程序(机器)就只能处于两种状态,对应不同状态或状态切换可以有相应的处理动作。
这样的程序不妨称为“状态机”。
按照上面的思路,代码实现就非常简单了——
[cpp] view
plain copy
#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 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中遇到*,则恢复状态3 ex. int a = b; /***
09)状态3中不是遇到/或*,说明多行注释只是遇到*,恢复状态2 ex. int a = b; /*hehe*h
10)状态4中遇到\,说明可能进入折行注释部分,则进入状态5 ex. int a = b; //hehe\
11)状态5中遇到\,说明可能进入折行注释部分,则维护状态5 ex. int a = b; //hehe\\\
12)状态5中遇到其它字符,则说明进入了折行注释部分,则恢复状态4 ex. int a = b; // hehe\a or hehe\<enter>
13)状态4中遇到回车符\n,说明单行注释结束,则恢复状态0 ex. int a = b; //hehe<enter>
14)状态0中遇到',说明进入字符常量中,则进入状态6 ex. char a = '
15)状态6中遇到\,说明遇到转义字符,则进入状态7 ex. char a = '\
16)状态7中遇到任何字符,都恢复状态6 ex. char a = '\n 还有如'\t', '\'', '\\' 等 主要是防止'\'',误以为结束
17)状态6中遇到',说明字符常量结束,则进入状态0 ex. char a = '\n'
18)状态0中遇到",说明进入字符串常量中,则进入状态8 ex. char s[] = "
19)状态8中遇到\,说明遇到转义字符,则进入状态9 ex. char s[] = "\
20)状态9中遇到任何字符,都恢复状态8 ex. char s[] = "\n 主要是防止"\",误以为结束
21)状态8中遇到"字符,说明字符串常量结束,则恢复状态0 ex. char s[] = "\"hehe"
前面说过,不同状态可以有相应的动作。比如状态0、6、7、8、9都需要输出当前字符,再考虑一些特殊情况就可以了。
读者实现时可以借助debug宏定义,将测试语句输出到标准错误输出,需要时可以重定位到标准输出,即2>&1,然后通过重定向|到more进行查看。
上面的状态机涉及到了[0, 9]一共10种状态,对应的状态转换图(或者说状态机/自动机)如下:
了这些状态表示,编写代码就很容易了——
[cpp] view
plain copy
#include<stdio.h>
int main()
{
char c;
int state=0;
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
while((c=getchar())!=EOF)
{
switch(state)
{
case 0:
if(c=='/')// ex. [/]
state=1;
else if(c=='\'')// ex. [']
state=6;
else if(c=='\"')// ex. ["]
state=8;
else
putchar(c);
break;
case 1:
if(c=='*')// ex. [/*]
state=2;
else if(c=='/')// ex. [//]
state=4;
else
{ // ex. [<secure/_stdio.h> or 5/3]
putchar('/');
putchar(c);
state=0;
}
break;
case 2:
if(c=='*') // ex. [/*he*]
state=3;
else // ex. [/*heh]
state=2;
break;
case 3:
if(c=='/')// ex. [/*heh*/]
state=0;
else if(c=='*')
state=3;//ex. [/***]注意这里,不加这个条件,*的个数是奇数的时候出错
else// ex. [/*heh*e]
state=2;
break;
case 4:
if(c=='\\')// ex. [//hehe\]
state=5;
else if(c=='\n')// ex. [//hehe<enter>]
{
state=0;
putchar(c);
}
break;
case 5:
if(c=='\\')// ex. [//hehe\\\\\]
state=5;
else// ex. [//hehe\<enter> or //hehe\a]
state=4;
break;
case 6:
if(c=='\\')// ex. ['\]
state=7;
else if(c=='\'')// ex. ['\n' or '\'' or '\t' ect.]
{
state=0;
putchar(c);
}
break;
case 7:// ex. ['\n or '\' or '\t etc.]
state=6;
break;
case 8:
if(c=='\\')// ex. ["\]
state=9;
else if(c=='\"')// ex. ["\n" or "\"" or "\t" ect.]
{
state=0;
putchar(c);
}
break;
case 9:// ex. ["\n or "\" or "\t ect.]
state=8;
break;
}
if(state==6||state==7||state==8||state==9)
putchar(c);
}
return 0;
}
【测试用例(1)a.out < test.c > test2.c】
test.c如下:
[cpp] view
plain copy
/*
*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) << 31) / 2 )
# define STRING "\"string\"" //to ensure legal
int main(void)
{
int w; // \a
int x;/*hehe*/
double y; // \
double z; // \b \\\\
int none;
///*testing*/
int idx;
if (idx > 3 && idx < 6) idx = idx/100; //go and \
con_tinue\
hehe
/* // */
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[] = "//test";
char special2[] = "he\"/*hehe*/";
char *special = " \' hi \0\b\t \\\\ \a\e\f\n\r\v wolegequ \\ ";
return 0;
}
如果不考虑字符常量和字符串常量,问题确实很简单。只需要去掉//和/* */的注释。
考虑到字符常量'\''和字符串常量"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则什么也不输出;
可以看出,借助自定义的状态,可以使编程思路更加清晰。
在遍历输入字符流的时候,程序(机器)就只能处于两种状态,对应不同状态或状态切换可以有相应的处理动作。
这样的程序不妨称为“状态机”。
按照上面的思路,代码实现就非常简单了——
[cpp] view
plain copy
#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 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中遇到*,则恢复状态3 ex. int a = b; /***
09)状态3中不是遇到/或*,说明多行注释只是遇到*,恢复状态2 ex. int a = b; /*hehe*h
10)状态4中遇到\,说明可能进入折行注释部分,则进入状态5 ex. int a = b; //hehe\
11)状态5中遇到\,说明可能进入折行注释部分,则维护状态5 ex. int a = b; //hehe\\\
12)状态5中遇到其它字符,则说明进入了折行注释部分,则恢复状态4 ex. int a = b; // hehe\a or hehe\<enter>
13)状态4中遇到回车符\n,说明单行注释结束,则恢复状态0 ex. int a = b; //hehe<enter>
14)状态0中遇到',说明进入字符常量中,则进入状态6 ex. char a = '
15)状态6中遇到\,说明遇到转义字符,则进入状态7 ex. char a = '\
16)状态7中遇到任何字符,都恢复状态6 ex. char a = '\n 还有如'\t', '\'', '\\' 等 主要是防止'\'',误以为结束
17)状态6中遇到',说明字符常量结束,则进入状态0 ex. char a = '\n'
18)状态0中遇到",说明进入字符串常量中,则进入状态8 ex. char s[] = "
19)状态8中遇到\,说明遇到转义字符,则进入状态9 ex. char s[] = "\
20)状态9中遇到任何字符,都恢复状态8 ex. char s[] = "\n 主要是防止"\",误以为结束
21)状态8中遇到"字符,说明字符串常量结束,则恢复状态0 ex. char s[] = "\"hehe"
前面说过,不同状态可以有相应的动作。比如状态0、6、7、8、9都需要输出当前字符,再考虑一些特殊情况就可以了。
读者实现时可以借助debug宏定义,将测试语句输出到标准错误输出,需要时可以重定位到标准输出,即2>&1,然后通过重定向|到more进行查看。
上面的状态机涉及到了[0, 9]一共10种状态,对应的状态转换图(或者说状态机/自动机)如下:
了这些状态表示,编写代码就很容易了——
[cpp] view
plain copy
#include<stdio.h>
int main()
{
char c;
int state=0;
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
while((c=getchar())!=EOF)
{
switch(state)
{
case 0:
if(c=='/')// ex. [/]
state=1;
else if(c=='\'')// ex. [']
state=6;
else if(c=='\"')// ex. ["]
state=8;
else
putchar(c);
break;
case 1:
if(c=='*')// ex. [/*]
state=2;
else if(c=='/')// ex. [//]
state=4;
else
{ // ex. [<secure/_stdio.h> or 5/3]
putchar('/');
putchar(c);
state=0;
}
break;
case 2:
if(c=='*') // ex. [/*he*]
state=3;
else // ex. [/*heh]
state=2;
break;
case 3:
if(c=='/')// ex. [/*heh*/]
state=0;
else if(c=='*')
state=3;//ex. [/***]注意这里,不加这个条件,*的个数是奇数的时候出错
else// ex. [/*heh*e]
state=2;
break;
case 4:
if(c=='\\')// ex. [//hehe\]
state=5;
else if(c=='\n')// ex. [//hehe<enter>]
{
state=0;
putchar(c);
}
break;
case 5:
if(c=='\\')// ex. [//hehe\\\\\]
state=5;
else// ex. [//hehe\<enter> or //hehe\a]
state=4;
break;
case 6:
if(c=='\\')// ex. ['\]
state=7;
else if(c=='\'')// ex. ['\n' or '\'' or '\t' ect.]
{
state=0;
putchar(c);
}
break;
case 7:// ex. ['\n or '\' or '\t etc.]
state=6;
break;
case 8:
if(c=='\\')// ex. ["\]
state=9;
else if(c=='\"')// ex. ["\n" or "\"" or "\t" ect.]
{
state=0;
putchar(c);
}
break;
case 9:// ex. ["\n or "\" or "\t ect.]
state=8;
break;
}
if(state==6||state==7||state==8||state==9)
putchar(c);
}
return 0;
}
【测试用例(1)a.out < test.c > test2.c】
test.c如下:
[cpp] view
plain copy
/*
*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) << 31) / 2 )
# define STRING "\"string\"" //to ensure legal
int main(void)
{
int w; // \a
int x;/*hehe*/
double y; // \
double z; // \b \\\\
int none;
///*testing*/
int idx;
if (idx > 3 && idx < 6) idx = idx/100; //go and \
con_tinue\
hehe
/* // */
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[] = "//test";
char special2[] = "he\"/*hehe*/";
char *special = " \' hi \0\b\t \\\\ \a\e\f\n\r\v wolegequ \\ ";
return 0;
}
相关文章推荐
- 怎样删除C/C++代码中的所有注释?浅谈状态机的编程思想
- 怎样删除C/C++代码中的所有注释?浅谈状态机的编程思想
- 状态机编程思想(2):删除代码注释(目前支持C/C++和Java)
- 状态机编程思想(2):删除代码注释(目前支持C/C++和Java)
- Java-流的简单使用:读取文件、写入文件(面试题:删除注释代码)
- 删除C/C++中的注释-有限状态机(C语言实现)
- C代码怎样才能被C++代码使用
- 第16周项目5-编程处理C++代码(输入m、n两个数字,从第m行起的n行代码将作为注释使用)
- cublas中执行矩阵乘法运算的函数 首先要注意的是cublas使用的是以列为主的存储方式,和c/c++中的以行为主的方式是不一样的。处理方法可参考下面的注释代码
- 使用VAssistX为VS2008 c++代码添加函数头注释
- 怎样注释C/C++代码
- 使用VAssistX为VS2008 c++代码添加函数头注释http://blog.sina.com.cn/s/blog_4aff4b970101bfqs.html
- 使用VAssistX为VS2008 c++代码添加函数头注释
- 将C/C++代码中的注释删除
- 使用VAssistX为VS2008 c++代码添加函数头注释 .
- Java-流的简单使用:读取文件、写入文件(面试题:删除注释代码)
- 使用VAssistX为VS2008 c++代码添加函数头注释
- doxygen 使用简介(C,C++为代码作注释)
- DOxygen for C++使用说明——注释代码二
- 使用VAssistX为VS2008 c++代码添加函数头注释