如何使用C或c++读取一个简单的文件格式?
例如,以Wavefront .obj文件格式为例,示例:
# this is a box
o 1
v -0.5 -0.5 0.5
v -0.5 -0.5 -0.5
v -0.5 0.5 -0.5
v -0.5 0.5 0.5
v 0.5 -0.5 0.5
v 0.5 -0.5 -0.5
v 0.5 0.5 -0.5
v 0.5 0.5 0.5
usemtl Default
f 4 3 2 1
f 2 6 5 1
f 3 7 6 2
f 8 7 3 4
f 5 8 4 1
f 6 7 8 5
由于文件可能相当大,我创建了一个带有操作符[]的中间类(FileBuffer)。它在内存中只有4096字节的文件,并在需要时读取新的部分。文件格式非常简单,但我不喜欢使用flex/bison之类的格式。这只会使事情复杂化。
解释这个(类型)文件的合适方法是什么?目前我有很多嵌套的for/while循环和许多计数器跟踪。还有许多switch/elseif语句。如何使这些代码易于维护,并且总体上更加结构化?
谢谢。
如果是我,我会尽可能多地利用标准库:
struct Command { /* Abstract Base Class */ ... };
struct VCommand : Command { std::vector<double> dims; ... }
struct FCommand : Command { std::vector<int> vertexes; ... }
struct FootCommand : Command { enum {LEFT, RIGHT, IN, OUT} e; ... }
std::vector<Command*> commandList; // DANGER: raw pointers
void ParseInput(std::istream& in) {
// untested code:
std::string line;
while(getline(in, line)) {
std::stringstream lineStream(line);
std::string command;
lineStream >> command;
if(command == "v") {
std::istream_iterator<double>(lineStream) begin;
std::istream_iterator<double> end;
// Add the "v" command to the parse tree
commandList.push_back(new VCommand(begin, end));
} else if (command == "f") {
std::istream_iterator<int>(lineStream) begin;
std::istream_iterator<int> end;
// Add the "v" command to the parse tree
commandList.push_back(new FCommand(begin, end));
} else if (command == "quit") {
...
} else if (command == "put_your_left_foot_in") {
...
commandList.push_back(new FootCommand(LEFT, IN));
}
}
}
如果我理解正确的话,每一行定义一种特定类型的数据,其类型由第一个确定词。我将首先定义一个抽象基类和类的具体实例,用于每行的类型;我在std::map<std::string,
LineParser*>
中注册这些实例。
为了读取文件,我可能会安装一个过滤流buf去掉上游的注释和空行。然后一个简单的循环就可以做到:
std::string line;
while ( std::getline( filteredInput, line ) ) {
std::string keyword;
std::istringstream toParse( line );
toParse >> keyword;
std::map<std::string, LineParser*>::const_iterator
parser = registry.find( keyword );
if ( parser == registry.end() ) {
// Syntax error: unknown keyword...
} else {
parser->parse( toParse );
}
}
我将从定义(或获取)文件语法/结构开始。
然后根据该语法为输入流构建解析器。