所以我需要一些关于如何在C ++中很好地解析文本文件的想法。我正在解析的文件具有以下格式:
Command_A list of arguments
Command_B list of arguments
etc etc
现在我正在使用ifstream来打开文件然后我有这个超长的if-else语句系列来确定如何为每种类型的命令做什么。这被证明有点笨拙(特别是因为一些命令用于解析其他文件...所以我已经嵌套if-elses,其中有多个ifstream用于不同的文件)。
我一直在寻找另一种方法,但我不确定什么是最好的方法。我在考虑使用std :: map,其中键是命令字符串,值是函数指针,但我不熟悉在地图中存储函数指针(特别是如果不同的函数具有不同的返回类型等) 。
以下基本上是我目前正在做的事情。我遍历文件并使用" getline"获得当前行。然后我使用stringstream来解析命令。然后我使用很长的if-elses列表来确定要调用的函数。文件中的每一行还附带一个参数列表,因此我使用stringstream来解析这些参数,然后将这些参数传递给我调用的函数。
这里的问题是双重的
1)我有非常多的if-elses(大约50)
2)有些命令要求我解析新文件,因此我必须在当前ifstream中打开另一个ifstream。 (见command_c)
所以我正在寻找一种更简单/更有效/更漂亮的方式来做到这一点。
/*Open file and verify validity*/
std::ifstream parseFile(filename.c_str());
if(!parseFile.good())
{
cerr<<"ERROR: File is either corrupt or does not exist."<<endl;
exit(1); //Terminate program
}
//Loop over file line by line
std::string line;
while(!parseFile.eof())
{
std::getline(parseFile, line);
std::stringstream ss;
std::string command;
ss.str(line);
ss >> command;
if(command == "COMMAND_A")
{
float x,y,z;
ss >> x >> y >> z;
FunctionA(x,y,z);
}
else if(command == "COMMAND_B")
{
float a,b,c,d,e,f;
ss >> a >> b >> c >> d >> e >> f;
FunctionB(a,b,c,d,e,f);
}
else if(command == "Command_C")
{
string nextFile;
ss >> nextFile;
ParseFile(nextFile); //This is not recursive...this is another function
}
else if(...)
{
...
}
// etc, etc (this continues on for a long time)
}
parseFile.close();
答案 0 :(得分:0)
你可以有一个命令图并注册一堆函数:
#include<fstream>
#include<functional>
#include<iostream>
#include<map>
#include<sstream>
int main() {
typedef std::function<bool (std::istringstream&)> command_function;
typedef std::map<std::string, command_function> command_map;
command_map map;
// register commands
map.insert(command_map::value_type("print", [](std::istringstream& s) {
std::string line;
if( ! getline(s, line)) return false;
std::cout << line << '\n';
return true;
}));
map.insert(command_map::value_type("add", [](std::istringstream& s) {
double a;
double b;
if( ! (s >> a >> b)) return false;
std::cout << "a + b = " << a + b << '\n';
return true;
}));
// sample data
std::istringstream file(
"print Hello World\n"
"add 1 2\n");
// command parsing
std::string line;
while(getline(file, line)) {
std::istringstream line_stream(line);
std::string command;
if(line_stream >> command >> std::ws) {
auto pos = map.find(command);
if(pos != map.end())
pos->second(line_stream);
}
}
return 0;
}
答案 1 :(得分:0)
我已经编写了很多类型的解析器,我发现编写一个相当通用的函数通常是一个好主意,该函数需要一行并生成一个字符串列表(例如std::vector<std::string>
,然后处理该列表中的第一个元素作为&#34;我们接下来要做什么&#34;,并让每个命令使用它喜欢的参数(例如,翻译为浮点数,用作文件名等)。 / p>
然后可以将其与基于表的系统组合,其中函数[或对象]与字符串相关联。例如std::map<std::string, BaseCommand> table;
。
然后你最终得到这样的东西:
class CommandA : BaseCommand
{
public:
virtual int Run(const std::vector<std::string>& argv);
};
table["CommandA"] = new CommandA;
table["CommandB"] = new CommandB;
...
std::vector<std::string> argv = parseLine(line);
if (table.find(argv[0]) != table.end())
{
int result = table[argv[0]].second->Run(argv);
if (result < 0)
{
... do error handling here...
}
}
当然,您可以通过许多不同的方式来做到这一点,这只是一种可能的解决方案。
答案 2 :(得分:0)
是的,将功能放在地图中。这样做的关键是std::function<void()>
。不幸的是,void()
意味着它包含不带参数的函数,并且不返回任何内容。显然,你的函数有参数。所以我们所做的是存储函数,每个函数都采用std::stringstream&
(行),解析出他们需要的参数,然后调用函数。最简单的方法是使用内联lambda。采用字符串流并且不返回任何内容的Lambdas看起来像这样:[](std::stringstream& ss) {code}
。
此外,我使用此功能可以轻松检索您的参数:
template<class T>
T get(std::stringstream& ss)
{
T t;
ss<<t;
if (!ss) // if it failed to read
throw std::runtime_error("could not parse parameter");
return t;
}
这是地图:
std::unordered_set<std::string, std::function<void(std::stringstream&))> cmd_map=
"COMMAND_A", [](std::stringstream& ss)
{FunctionA(get<float>(ss), get<float>(ss), get<float>(ss));},
"COMMAND_B", [](std::stringstream& ss)
{FunctionB(get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss));},
"COMMAND_C", [](std::stringstream& ss)
{FunctionA(get<string>(ss));},
这是解析器:
//Loop over file line by line
std::string line;
while(std::getline(parseFile, line)) //use this instead of eof
{
std::stringstream ss(line);
std::string command;
ss >> command;
auto it = cmd_map.find(command);
if (it != cmd_map.end())
{
try
{
(*it)(); //call the function
} catch(std::runtime_error& err) {
std::cout << "ERROR: " << err.what() << '\n';
}
} else {
std::cout << "command " << command << " not found";
}
}
parseFile.close();