题目:
编写一个删除c语言程序中所有的注释语句。要正确的处理带引号的字符串与字符常量。在c语言中注释不允许嵌套
首先需要理解什么是注释嵌套,c语言中有两种注释符,一种是行注释符//,一种是块注释符/**/
其中行注释符,一次只能注释一行,//之后的全部为注释
块注释符要成对出现,注释从/*开始到*/结束。
注释嵌套是指对块注释符所说,例如下面为注释嵌套的例子:
/* /* this is the comments */ */
注释从第一个/*开始,到第一个*/结束。所以最后一个*/不在注释范围内,这种注释是不被允许的,否则会导致编译错误
自我解答:
#include <stdio.h> #define IN 1 #define OUT 0 int main() { int c; bool comInStr = OUT; //the comment symbol // or /* in the string bool blockCom = OUT; // the character is in comment block "/**/" bool lineCom = OUT; // the character is in a comment line "//" bool isComSymbol = false; // if it is comment symbol "//" or "/*" bool outComBlock = false; //the flag for out of comment block while((c = getchar()) != EOF) { if(comInStr) { putchar(c); //output the content in " " if(c == '"') comInStr = OUT; //end of the content in " " } else { if(lineCom == IN) { if(c == '\n') // will not output until to the end of the line { putchar(c); lineCom = OUT; isComSymbol = false; } } else if(blockCom == IN) { if(outComBlock) { if(c == '/') { blockCom = OUT; //find the end symbol "*/" isComSymbol = false; } outComBlock = false; } else { if(c == '*') outComBlock = true; } } else { if(c == '"') { comInStr = IN; //found the string putchar(c); continue; } if(isComSymbol) { if(c == '/') lineCom = IN; else if(c == '*') blockCom = IN; else { putchar('/'); putchar(c); isComSymbol = false; } } else { if(c == '/') isComSymbol = true; else putchar(c); } } } } return 0; }
编程思路:定义布尔型变量lineCom和blockCom 分别表示当前是否在注释行和注释块之内。
顶层逻辑为:如果当前字符在注释行之内,则不输出字符,直至到行尾;如果当前字符在注释块之内,则不输出字符,直至遇到注释块结束符*/。通过识别“//”和“/*”来区分是否是注释行或注释块,在识别过程中需要忽略这两种字符在字符串常量之内出现
为上述程序写一个测试输入,尽可能包含各种情况:
#include <stdio.h> //test line comment //test line comment /* test block comment define the line comment and block comment */ #define LINECOMMENT "//" /* test // in string */ #define BLOCKCOMMENT "/*" // test /* in string int main() { int a = 0; /*test comment between two statements*/ int b = 1; char str[] = "I am //, who are you"; // char str1[] = "I am /*, and you"; // "haha I am here" /* */ */ return 0; }
输出如下:
#include <stdio.h> #define LINECOMMENT "//" #define BLOCKCOMMENT "/*" int main() { int a = 0; int b = 1; char str[] = "I am //, who are you"; char str1[] = "I am /*, and you"; return 0; }
从输出中可见,删除了注释部分并且保留了 字符串和字符常量中的 注释符号
参考答案:
#include <stdio.h> void rcomment(int c); void in_comment(void); void echo_quote(int c); /* remove all comments from a valid C program */ int main() { int c, d; while((c = getchar()) != EOF) rcomment(c); return 0; } /* rcomment: read each character, remove the comments */ void rcomment(int c) { int d; if(c == '/') if((d = getchar()) == '*') in_comment(); /* beginning comment */ else if(d == '/') { putchar(c); /* another slash */ rcomment(d); } else { putchar(c); /* not a comment */ putchar(d); } else if(c == '\'' || c == '"') echo_quote(c); /* quote begins */ else putchar(c); /* not a comment */ } /* in_comment: inside of a valid comment */ void in_comment(void) { int c, d; c = getchar(); /* prev character */ d = getchar(); /* curr character */ while(c != '*' || d != '/') /* search for end */ { c = d; d = getchar(); } } /* echo_quote: echo character within quotes */ void echo_quote(int c) { int d; putchar(c); while((d = getchar()) != c) { /* search for end */ putchar(d); if(d == '\\') putchar(getchar()); /* ignore escape seq */ } putchar(d); }
这个程序假设输入是一个合法的C程序。函数rcomment搜索注释语句的起始标志(/*);在找到这个标志时,它将调用另一个函数in_comment搜索注释语句的结束标志(*/),从而确保C程序中的注释语句都能被删除。
函数rcomment还将搜索单引号和双引号;在找到它们时,它将调用另一个函数echo_quote。函数echo_quote的参数将指明找到的字符是一个单引号还是一个双引号。echo_quote确保引号中的内容能够按原样输出,不会被误认为是注释。函数echo_quote不会把跟在一个反斜杠后面的引号看做是结束引号(参见教材第13页和练习1-2中关于转义字符序列的讨论)。其他任何字符都将按原样输出。
本程序将在getchar返回文件结束符时结束运行。
补充:
参考答案中的思路比较清晰,可读性较强。但此程序也存在一些问题:
首先参考答案程序的前提必须是一个合法的C程序。
例如下面的输入:int main() /*main function
被认为是一个不合法的程序,如果用这种输入,程序将会在in_comment中陷入死循环。而自我解答对这种输入并不敏感,当用同样的输入时,输出为:int main() 依然能把/*后面的注释去掉。
再例如下面的输入:#define LINECOMMENT "//,程序将在echo_quote中陷入死循环。自我解答依然可以正常输出。
其次参考答案程序只能处理块注释符即/**/的情况,对于行注释//不起作用。
例如用下面的输入int main() //main function
程序不能识别//而原样打印出来,自我解答可以处理这种情况
从参考答案中可以借鉴的地方有两个:一是在rcomment里面的递归调用,一个是对转义字符的处理。
自我解答中没有进行转义字符处理和单引号的处理,没有增加对单引号的处理是因为当时考虑,单引号定义的是单个字符不会出现注释符的情况。但是单引号内仍然可能会有转义字符的情况例如:'\''.在自我解答的程序进行这个功能的扩展也比较简单,如下即可:
int c, lastc; 增加lastc用于记录上次字符是单引号还是双引号
if(comInStr)
{
putchar(c);
if(c == '\\') //如果是转义字符打印下一个字符
{
putchar(getchar()); //print the next char
continue;
}
if(c == lastc) // 判断引号类型是否一致
comInStr = OUT; //end of the content in " "
}
在line61行增加 lastc = c; // store ' or " in lastc 记录当前引号类型