删除C++代码里的注释
问题描述
写一段代码,删除C++文件里的注释。包含单行注释、多行注释,需要正确处理换行的字符串,如:
std::string s = "ni hao \
shi jie";
解决思路
- 读入代码文件,得到字符串。遍历字符串,根据字符和字符之间的关系来判断是否遇到了注释。
如果第一个字符遇到了/
,然后判断第二个字符是/
、*
。
如果第二个字符是/
,接下来就判断是否遇到了换行符。
如果第二个字符是*
,接下来就判断是否遇到了*
,如果是就继续判断是否遇到了/
第一版:处理标准格式的单行注释和多行注释
std::string f(const std::string & str)
{
std::string ret;
int state = 0;
for (char c : str)
{
if (0 == state)
{
if ('/' == c)
{
//状态0中遇到/,说明可能会遇到注释,则进入状态1
state = 1;
}
else
{
ret += c;
}
}
else if (1 == state)
{
if ('*' == c)
{
//状态1中遇到*,说明进入多行注释,则进入状态2
state = 2;
}
else if ('/' == c)
{
//状态1中遇到/,说明进入单行注释,则进入状态4
state = 4;
}
else
{
//状态1中没有遇到*或/,说明/是除号,则恢复状态0
ret += '/';
ret += c;
state = 0;
}
}
else if (2 == state)
{
if ('*' == c)
{
//状态2中遇到*,说明多行注释可能要结束,则进入状态3
state = 3;
}
else
{
//状态2中不是遇到*,说明多行注释还在继续,则维持状态2
}
}
else if (3 == state)
{
if ('/' == c)
{
//状态3中遇到/,说明多行注释结束,则恢复状态0
state = 0;
//删除注释后补个换行
ret += '\n';
}
}
else if (4 == state)
{
if ('\n' == c)
{
//状态4中遇到换行符,说明单行注释结束,则进入状态0
state = 0;
//删除注释后补个换行
ret += '\n';
}
else
{
//维持状态4
}
}
}
return ret;
}
int main()
{
std::ifstream fin("input.cpp");
std::ofstream fout("output.cpp");
if (!fin.is_open())
{
std::cerr << "Open input file failed." << std::endl;
return 0;
}
if (!fout.is_open())
{
std::cerr << "Open output file failed." << std::endl;
return 0;
}
std::string str_in;
std::string str_line;
while (getline(fin, str_line))
{
str_in += str_line;
str_in += '\n';
}
std::cout << str_in << std::endl;
std::string str_out = f(str_in);
std::cout << str_out << std::endl;
fout.write(str_out.c_str(), str_out.size());
fin.close();
fout.close();
system("pause");
return 0;
}
第二版:处理特殊格式的多行注释
当state由0->1->2->3时,也就是遇到了/
、*
、*
之后,下一个字符不一定就是/
,举例说明,如:/*abc**/
、/*abc*d*/
、/**a/*/
所以,需要在 else if (3 == state) {}
的大括号里加如下代码。如果不加会得到什么结果,可自行测试。
else if (3 == state)
{
if ('/' == c)
{
//状态3中遇到/,说明多行注释结束,则恢复状态0
state = 0;
//删除注释后补个换行
ret += '\n';
}
else if ('*' == c)
{
//状态3中继续遇到*,说明多行注释可能要结束,维持状态3
}
else
{
//状态3中不是遇到/或*,说明多行注释只是遇到*,还要继续,则恢复状态2
state = 2;
}
}
第三版:处理字符串常量
如果有代码std::string str_test = "//nihao";
,会得到std::string str_test = "
,这显然是错误的。
所以我们需要增加对字符串常量的处理,修改方法:在if (0 == state){}
的大括号里增加对双引号的判断。
if (0 == state)
{
if ('/' == c)
{
//状态0中遇到/,说明可能会遇到注释,则进入状态1
state = 1;
}
else if ('\"' == c)
{
//状态0中遇到",说明进入字符串常量中,则进入状态7
state = 7;
ret += c;
}
else
{
ret += c;
}
}
else if (7 == state)
{
if ('\"' == c)
{
//状态7中遇到"字符,说明字符串常量结束,则恢复状态0
state = 0;
}
ret += c;
}
上面的修改还不行,没有对字符串常量含有反斜杠的情况(即转义字符)做特殊处理,假如有个双引号的转义字符,如std::string test = "ni\"//hao";
,程序会在遇到第二个双引号时认为字符串常量读取完毕了。所以继续修改如下:
else if (7 == state)
{
/*在状态7中,遇到反斜杠、双引号需要特殊处理
反斜杠代表下一个字符是转义字符,直接跳过判断
双引号代表字符串结束
除上述两种情况外,不做任何处理*/
if ('\\' == c)
{
//状态7中遇到\,说明字符串常量里遇到转义字符,则进入状态8
state = 8;
ret += c;
}
else if ('\"' == c)
{
//状态7中遇到"字符,说明字符串常量结束,则恢复状态0
state = 0;
ret += c;
}
else
{
ret += c;
}
}
else if (8 == state)
{
//状态8中遇到任何字符,都恢复状态7
//即转义字符结束
state = 7;
ret += c;
}
第四版:处理双引号这个特殊的字符常量
测试代码:
char ch1 = '\"';
//test
char ch2 = '\"';
如果没有增加对转义双引号这个字符的处理的话,上面代码两个双引号中间的代码会被认为属于字符串常量。
修改方法:在if (0 == state){}
的大括号里增加对单引号的判断
if (0 == state)
{
if ('/' == c)
{
//状态0中遇到/,说明可能会遇到注释,则进入状态1
state = 1;
}
else if ('\'' == c)
{
//状态0中遇到',说明进入字符常量中,则进入状态5
state = 5;
ret += c;
}
else if ('\"' == c)
{
//状态0中遇到",说明进入字符串常量中,则进入状态7
state = 7;
ret += c;
}
else
{
ret += c;
}
}
else if (5 == state)
{
if ('\\' == c)
{
//状态5中遇到\,说明遇到转义字符,则进入状态6
state = 6;
}
else if ('\'' == c)
{
//状态5中遇到',说明字符常量结束,则进入状态0
state = 0;
}
else
{
//进到这里说明正在读取那个不是转义字符的字符,维持状态5
}
ret += c;
}
else if (6 == state)
{
//状态6中遇到任何字符,都恢复状态5
//即转义字符结束
state = 5;
ret += c;
}
总结
std::string f(const std::string & str)
{
std::string ret;
int state = 0;
for (char c : str)
{
if (0 == state)
{
if ('/' == c)
{
//状态0中遇到/,说明可能会遇到注释,则进入状态1
state = 1;
}
else if ('\'' == c)
{
//状态0中遇到',说明进入字符常量中,则进入状态5
state = 5;
ret += c;
}
else if ('\"' == c)
{
//状态0中遇到",说明进入字符串常量中,则进入状态7
state = 7;
ret += c;
}
else
{
ret += c;
}
}
else if (1 == state)
{
if ('*' == c)
{
//状态1中遇到*,说明进入多行注释,则进入状态2
state = 2;
}
else if ('/' == c)
{
//状态1中遇到/,说明进入单行注释,则进入状态4
state = 4;
}
else
{
//状态1中没有遇到*或/,说明/是除号,则恢复状态0
ret += '/';
ret += c;
state = 0;
}
}
else if (2 == state)
{
if ('*' == c)
{
//状态2中遇到*,说明多行注释可能要结束,则进入状态3
state = 3;
}
else
{
//状态2中不是遇到*,说明多行注释还在继续,则维持状态2
}
}
else if (3 == state)
{
if ('/' == c)
{
//状态3中遇到/,说明多行注释结束,则恢复状态0
state = 0;
//删除注释后补个换行
ret += '\n';
}
else if ('*' == c)
{
//状态3中继续遇到*,说明多行注释可能要结束,维持状态3
}
else
{
//状态3中不是遇到/或*,说明多行注释只是遇到*,还要继续,则恢复状态2
state = 2;
}
}
else if (4 == state)
{
if ('\n' == c)
{
//状态4中遇到换行符,说明单行注释结束,则进入状态0
state = 0;
//删除注释后补个换行
ret += '\n';
}
else
{
//维持状态4
}
}
else if (5 == state)
{
if ('\\' == c)
{
//状态5中遇到\,说明遇到转义字符,则进入状态6
state = 6;
}
else if ('\'' == c)
{
//状态5中遇到',说明字符常量结束,则进入状态0
state = 0;
}
else
{
//进到这里说明正在读取那个不是转义字符的字符,维持状态5
}
ret += c;
}
else if (6 == state)
{
//状态6中遇到任何字符,都恢复状态5
//即转义字符结束
state = 5;
ret += c;
}
else if (7 == state)
{
/*在状态7中,遇到反斜杠、双引号需要特殊处理
反斜杠代表下一个字符是转义字符,直接跳过判断
双引号代表字符串结束
除上述两种情况外,不做任何处理*/
if ('\\' == c)
{
//状态7中遇到\,说明字符串常量里遇到转义字符,则进入状态8
state = 8;
ret += c;
}
else if ('\"' == c)
{
//状态7中遇到"字符,说明字符串常量结束,则恢复状态0
state = 0;
ret += c;
}
else
{
ret += c;
}
}
else if (8 == state)
{
//状态8中遇到任何字符,都恢复状态7
//即转义字符结束
state = 7;
ret += c;
}
}
return ret;
}
int main()
{
std::ifstream fin("input.cpp");
std::ofstream fout("output.cpp");
if (!fin.is_open())
{
std::cerr << "Open input file failed." << std::endl;
return 0;
}
if (!fout.is_open())
{
std::cerr << "Open output file failed." << std::endl;
return 0;
}
std::string str_in;
std::string str_line;
while (getline(fin, str_line))
{
str_in += str_line;
str_in += '\n';
}
std::cout << str_in << std::endl;
std::string str_out = f(str_in);
std::cout << str_out << std::endl;
fout.write(str_out.c_str(), str_out.size());
fin.close();
fout.close();
system("pause");
return 0;
}