删除C++代码里的注释

问题描述

写一段代码,删除C++文件里的注释。包含单行注释、多行注释,需要正确处理换行的字符串,如:

std::string s = "ni hao \
shi jie";

解决思路

  1. 读入代码文件,得到字符串。遍历字符串,根据字符和字符之间的关系来判断是否遇到了注释。

如果第一个字符遇到了/,然后判断第二个字符是/*

如果第二个字符是/,接下来就判断是否遇到了换行符。

如果第二个字符是*,接下来就判断是否遇到了*,如果是就继续判断是否遇到了/

第一版:处理标准格式的单行注释和多行注释

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>

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;
}

总结

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>

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;
}