我正在为 .obj 文件编写解析器,并且文件的一部分格式为
f [int]/[int] [int]/[int] [int]/[int]
并且整数的长度未知。在每个 [int]/[int] 对中,它们都需要放在单独的数组中。将它们分隔为整数的最简单方法是什么?
考虑使用其中一个scanf
函数(fscanf
如果您正在使用 <stdio.h> 和读取文件FILE*
,或者sscanf
解析内存缓冲区中的一行)。因此,如果您有一个包含数据的缓冲区和两个整数数组,如下所示:
int first[3], second[3];
char *buffer = "f 10/20 1/300 344/2";
然后你可以写:
sscanf(buffer, "f %d/%d %d/%d %d/%d",
&first[0], &second[0], &first[1], &second[1], &first[2], &second[2]);
(sscanf
的输入模式中的空格不是必需的,因为%d
跳过了空格,但它们提高了可读性。)
如果您需要错误检查,请分析以下结果sscanf
:此函数返回成功输入的值的数量(6
对于此示例,如果一切正确)。
你可以用 fscanf 做到这一点:
int matched = fscanf(fptr, "f %d/%d %d/%d %d/%d", &a, &b, &c, &d, &e, &f);
if (matched != 6) fail();
或 ifstream 和 sscanf:
char buf[100];
yourIfstream.getLine(buf, sizeof(buf));
int matched = sscanf(buf, "f %d/%d %d/%d %d/%d", &a, &b, &c, &d, &e, &f);
if (matched != 6) fail();
#include <stdlib.h>
long int strtol(const char *nptr, char **endptr, int base);
long long int strtoll(const char *nptr, char **endptr, int base);
该strtol
函数将从输入中解析一个整数,并返回整数在字符串中结束的位置。你可以像这样使用它
char *input = "f 123/234 234/345 345/456"
char *c = input;
char *endptr;
if (*c++ != 'f') fail();
if (*c++ != ' ') fail();
long l1 = strtol(c, &endptr, 10);
if (l1 < 0) fail(); /* you expect them unsigned, right? */
if (endptr == c) fail();
if (*endptr != '/') fail();
c = endptr+1;
...
我会为此使用正则表达式。如果你有一个兼容 C++11 的编译器,你可以使用 ,否则你可以查看 boost::regex。在类似 Perl 的语法中,您的正则表达式模式看起来像这样f ([0-9]+)/([0-9]+) ([0-9]+)/([0-9]+) ([0-9]+)/([0-9]+)
:然后依次获取子匹配项(括号内的内容)并使用 istringstream 将它们从字符串或 char* 转换为整数。
最简单的方法是使用 C++11 正则表达式:
static const std::regex ex("f (-?\\d+)//(-?\\d+) (-?\\d+)//(-?\\d+) (-?\\d+)//(-?\\d+)");
std::smatch match;
if(!std::regex_match(line, match, ex))
throw std::runtime_error("invalid face data");
int v0 = std::stoi(match[1]), t0 = std::stoi(match[2]),
v1 = std::stoi(match[3]), t1 = std::stoi(match[4]),
v2 = std::stoi(match[5]), t2 = std::stoi(match[6]);
虽然这对于您的情况可能已经足够了,但我不禁添加了一种更灵活的方式来读取这些索引元组,这可以更好地处理非三角形面和不同的面规范格式。为此,我们假设您已经将人脸线放入 astd::istringstream
并且已经吃掉了人脸标签。这通常是这种情况,因为读取 OBJ 文件的最简单方法仍然是:
for(std::string line,tag; std::getline(file, line); )
{
std::istringstream sline(line);
sline >> tag;
if(tag == "v")
...
else if(tag == "f")
...
}
现在读取面部数据("f"
当然是在案例内部),我们首先单独读取每个索引元组。然后,我们只需使用正则表达式为每种可能的索引格式解析这个索引并适当地处理它们,在 3-element 中返回单个顶点、texcoord 和正常索引std::tuple
:
for(std::string corner; sline>>corner; )
{
static const std::regex vtn_ex("(-?\\d+)/(-?\\d+)/(-?\\d+)");
static const std::regex vn_ex("(-?\\d+)//(-?\\d+)");
static const std::regex vt_ex("(-?\\d+)/(-?\\d+)/?");
std::smatch match;
std::tuple<int,int,int> idx;
if(std::regex_match(corner, match, vtn_ex))
idx = std::make_tuple(std::stoi(match[1]),
std::stoi(match[2]), std::stoi(match[3]));
else if(std::regex_match(corner, match, vn_ex))
idx = std::make_tuple(std::stoi(match[1]), 0, std::stoi(match[2]));
else if(std::regex_match(corner, match, vt_ex))
idx = std::make_tuple(std::stoi(match[1]), std::stoi(match[2]), 0);
else
idx = std::make_tuple(std::stoi(str), 0, 0);
//do whatever you want with the indices in std::get<...>(idx)
};
当然,这为以性能为导向的优化(如果有必要)提供了可能性,例如无需在每次循环迭代中分配新的字符串和流。但这是为适当的 OBJ 加载程序提供所需灵活性的最简单方法。但也可能是上述版本的仅具有顶点和 texcoords 的三角形对您来说已经足够了。