题目描述:
请编写注释转化程序,实现对一个C/C++语言程序源文件中注释的转换功能
具体需求:
1>C++风格的注释//注释转换为标准C分风格/* */注释
2>/* */风格的注释保持原样
3>所有的转换需要符合语法规则
4>注释转换需要支持注释嵌套
注释转化要求:
注释的嵌套情形很多,这里只是举例,你需要遵照C/C++语言的注释 规则来编写代码,我不会仅测试这里的例子。
1、单行注释或没有嵌套,注释行直接转换,如:
1>//123 ==》 /*123*/
2>/*123*/ /*123*/ 不变
3>/*123
*/ 保持原样
2、有嵌套的注释(一个注释中还有嵌套其他注释符号//,/**/)嵌套中多余的每个注释符号用两个空格代替。
如单行:
1>//123/*456*/ ==》 /*123 456*/
2>//123//456 ==》 /*123 456*/
3>//123*//*456 ==》 /*123 456*/
如跨行:
/*............. /*.............
//............. ==》 ................
//............. ................
*/ */
注意:
1、除以下两种情况的修改,源文件转换后不能有任何其他的修改:
1>多余的注释符用空格代替
2>//在注释开始替换为/*,行尾增加*/
2、下面的3种情形无需转换
1>/*123*//*456*/
2>/*123*//*456
*/
3>/*123
*//*456
*/
3、不需要考虑输入文件中不符合语法规则的注释
首先我们定义一个状态机进行分类,将其分为C语言注释状态、C++注释状态、字符串状态、空状态和结束状态五大类,初始状态为空状态。(考虑到字符串中不对代码有影响的注释不应该被转换,因此多定义了一个字符串类)
读到/*时进入C语言注释状态,读到*/时返回空状态
读到//时进入C++注释状态,读到\n或\r\n时返回空状态
读到"时,对后续字符进行判断,看是否再次出现",有则进入字符串状态,再访问完第二个"后返回空状态
读到EOF时进入结束状态,并结束掉程序
在这里,我们顺便提一下状态机的概念:
状态机的概念:
有限状态机简称状态机,是表示有限个状态以及在这些状态之间转移的行为的模型。有限状态机是闭环系统,可以用有限的状态处理无穷的状态。
状态机就是一组状态,各个状态之间,依据一定的条件,(如输入一个 1 或者是 0)存在一定的转换,(从状态X转换到状态Y)它有
一个起始状态和若干终结状态,从起始状态开始,根据输入的串转换状态,直到所有的输入的被状态机处理,看看最后停留的状态是否为终结状态,是的话就说这个串符合这个状态机规则,或者说被这个状态机接受!
通常我们使用switch case语句来处理有限状态机
//定义状态
typedef enum
{
NO_COMMENT_STATE,
C_COMMENT_STATE,
CPP_COMMENT_STATE,
STRING_STATE,
END_STATE
}enum_state;
//定义状态机
typedef struct
{
FILE *inputfile;
FILE *outputfile;
enum_state ulstate;
}state_machine;
具体状况如下图所示:
为了方便大家的阅读,我在这里先把我写的一些函数声明在这里列举一下
#ifndef _CONVERTCOMMENT_H
#define _CONVERTCOMMENT_H
#include
#include
#include
FILE* Fopen(char *filename, char *mode);
void Fclose(FILE *fp);
char read_ch(FILE *fp);
void write_ch(char ch, FILE *fp);
void write_double_ch(char ch1, char ch2, FILE *fp);
void eventpro(char ch);
void eventpro_no(char ch);
void eventpro_c(char ch);
void eventpro_cpp(char ch);
void eventpro_str(char ch);
int convertcomment(FILE *inputfile, FILE *outputfile);
#endif
定义两个包裹函数,减少代码的冗余,提升阅读性
FILE* Fopen(char *filename, char *mode)
{
FILE *fp = fopen(filename, mode);
if(NULL == fp)
{
printf("open %s fail.\n",filename);
exit(1);
}
return fp;
}
void Fclose(FILE *fp)
{
fclose(fp);
}
对转换后的代码进行一个简单的封装(展示可以跳过这块,理解代码思路后可在回过头来看这)
char read_ch(FILE *fp)
{
assert(NULL != fp);
return fgetc(fp);
}
void write_ch(char ch, FILE *fp)
{
assert(NULL != fp);
fputc(ch, fp);
}
void write_double_ch(char ch1, char ch2, FILE *fp)
{
assert(NULL != fp);
fputc(ch1, fp);
fputc(ch2, fp);
}
定义两个状态,并对状态机进行初始化
state_machine g_state;
state_machine g_pre_state;
int convertcomment(FILE *inputfile, FILE *outputfile)
{
if(NULL == inputfile || NULL == outputfile)
{
printf("argument is invalid.\n");
return -1;
}
//初始化状态机
g_state.inputfile = inputfile;
g_state.outputfile = outputfile;
g_state.ulstate = NO_COMMENT_STATE;
char ch;
while(END_STATE != g_state.ulstate)
{
ch = read_ch(g_state.inputfile);
eventpro(ch);
}
return 0;
}
在这里定义一个状态切换函数
void eventpro(char ch)
{
switch(g_state.ulstate)
{
case NO_COMMENT_STATE:
eventpro_no(ch);
break;
case C_COMMENT_STATE:
eventpro_c(ch);
break;
case CPP_COMMENT_STATE:
eventpro_cpp(ch);
break;
case STRING_STATE:
eventpro_str(ch);
break;
}
}
对初始状态的处理:
void eventpro_no(char ch)
{
char nextch;
switch(ch)
{
case '/':
nextch = read_ch(g_state.inputfile);
if('/' == nextch) //C++ Comment
{
write_double_ch('/','*', g_state.outputfile);
g_state.ulstate = CPP_COMMENT_STATE;
}
else if('*' == nextch) //C Comment
{
write_double_ch('/','*', g_state.outputfile);
g_state.ulstate = C_COMMENT_STATE;
}
else
{
write_double_ch('/', nextch, g_state.outputfile);
}
break;
case '"':
write_ch('"', g_state.outputfile);
eventpro_str(ch);
break;
case EOF:
g_state.ulstate = END_STATE;
break;
default:
write_ch(ch, g_state.outputfile);
break;
}
}
对C语言注释的处理:
void eventpro_c(char ch)
{
char nextch;
switch(ch)
{
case '/':
nextch = read_ch(g_state.inputfile);
if('/' == nextch || '*' == nextch) //C++ Comment
{
write_double_ch(' ', ' ', g_state.outputfile);
}
else
{
write_double_ch('/', nextch, g_state.outputfile);
}
break;
case '*':
nextch = read_ch(g_state.inputfile);
if('/' == nextch)
{
write_double_ch('*', '/', g_state.outputfile);
g_state.ulstate = NO_COMMENT_STATE;
}
else
{
write_double_ch('*', nextch, g_state.outputfile);
}
break;
case '"':
eventpro_str(ch);
break;
case EOF:
g_state.ulstate = END_STATE;
break;
default:
write_ch(ch, g_state.outputfile);
break;
}
}
对C++注释的处理:
void eventpro_cpp(char ch)
{
char nextch;
switch(ch)
{
case '\n':
write_double_ch('*', '/', g_state.outputfile);
fputc('\n',g_state.outputfile);
g_state.ulstate = NO_COMMENT_STATE;
break;
case '/':
nextch = read_ch(g_state.inputfile);
if('/' == nextch || '*' == nextch)
{
write_double_ch(' ', ' ', g_state.outputfile);
}
else
{
write_double_ch('/', nextch, g_state.outputfile);
}
break;
case '*':
nextch = fgetc(g_state.inputfile);
if('/' == nextch)
{
write_double_ch(' ', ' ', g_state.outputfile);
}
else
{
write_double_ch('*', nextch, g_state.outputfile);
}
break;
case '"':
eventpro_str(ch);
break;
case EOF:
write_double_ch('*', '/', g_state.outputfile);
g_state.ulstate = END_STATE;
break;
default:
write_ch(ch, g_state.outputfile);
break;
}
}
对字符串状态的处理:
void eventpro_str(char ch)
{
char nextch;
int flag = 1;
int mark = 1;
long n = 1;
write_ch('"',g_state.outputfile);
nextch = read_ch(g_state.inputfile);
while(flag && mark && EOF != nextch)
{
if('"' == nextch)
{
flag = 0;
}
else if('\n' == nextch)
{
mark = 0;
}
else
{
nextch = read_ch(g_state.inputfile);
n++;
}
}
if(1 == flag && 1 == mark) //EOF情况
{
fseek(g_state.inputfile, -n+1, 1);
nextch = read_ch(g_state.inputfile);
while(EOF != nextch)
{
write_ch(nextch, g_state.outputfile);
nextch = read_ch(g_state.inputfile);
}
}
else if(1 == flag && 0 == mark) //'\n'情况
{
fseek(g_state.inputfile, -n-1, 1);
nextch = read_ch(g_state.inputfile);
while('\n' != nextch)
{
write_ch(nextch, g_state.outputfile);
nextch = read_ch(g_state.inputfile);
}
}
else //'"'情况
{
fseek(g_state.inputfile, -n, 1);
nextch = read_ch(g_state.inputfile);
while('"' != nextch)
{
write_ch(nextch, g_state.outputfile);
nextch = read_ch(g_state.inputfile);
}
write_ch('"', g_state.outputfile);
}
}
以上代码在实现之后,自己也进行了很多苛刻的用例进行测试,发现了一些问题:
1>在Windows系统里面,每行结尾是“<回车><换行>”,即“ \r\n”,而不像Linux中时以"\n"结尾的;
2>当遇见////////////////////////////时,这种情况我不想产生太多的空格,仍旧打算用两个空格来替换,然后我发现,当代码进行6/2=3用例测试时,会转化为6 2=3;于是又对代码进行了完善,才使得用例通过
3>当遇见"的时候,并不一定进入字符串状态,应该用fseek进行检测,如果检测到下一个"时,才意味着进入了字符状态。如果检测到\n、\r\n或EOF,则只能将"视为普通字符来看
以下是我用的一些测试用例:
//每个区由若干个内存块组成
//每个区由若干个内存块组成,//每个块是4096个字节
//int i = 0;*/
//*//*int i = 0;
// /**/int i = 0;
/* int i = 0;
*//*
*/
/* int i = 0;
// */int j = 0;
/*
//每个区由若干个内存块组成,每个块是4096个字节
//每个块的第0个整数指向下个区
//所以是单链表结构
//所以每个区只有4092个字节存放真正的数据
*/
/* int i = 0;*//*int j = 0;
*/
/*
*//*
*/int i =0;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 5
"abcdefghijklmn~~~~!!!!!!!!"
/////////////xyz
//6/2=3
char *str = "hello //comment convert.";
char *str1 = "hello /* comment */ convert.";
/*abc**/
//abc"xy//z"
//abc"xy*/z"
希望这篇文章能够帮助到你,可能还有某些特殊的用例个人没有找到,还希望大家能够提出,多多指教