将非统一字符串解析为整数

Parsing a non-uniform string into integers

本文关键字:整数 字符串      更新时间:2023-10-16

我正在为.obj文件编写一个解析器,文件的一部分采用格式

f[int]/[int][int]/[int]/[int]

并且整数的长度未知。在每个[int]/[int]对中,它们都需要放在单独的数组中。将它们分离为整数的最简单方法是什么?

您可以使用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();

考虑使用scanf函数之一(如果使用<stdio.h>和FILE*sscanf读取文件以解析内存缓冲区中的一行,则为fscanf(。所以,如果你有一个缓冲区,里面有数据和两个像这样的整数数组:

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(。

我会使用正则表达式来实现这一点。如果你有一个兼容C++11的编译器,你可以使用,否则你可以查看boost::regex。在类似Perl的语法中,正则表达式模式看起来像这样:f ([0-9]+)/([0-9]+) ([0-9]+)/([0-9]+) ([0-9]+)/([0-9]+)。然后依次获取子匹配项(括号内的内容(,并使用istringstream将它们从字符串或char*转换为整数。

   #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正则表达式:

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]);

虽然这对您的情况来说可能已经足够了,但我忍不住添加了一种更灵活的方式来读取这些索引元组,它可以更好地处理非三角形面和不同的面规范格式。为此,我们假设您已经将面线放入std::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元素std::tuple:中的各个顶点、texcord和法线索引

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装载机所需的灵活性。但也可能是上面的版本只适用于带有顶点和纹理坐标的三角形已经足够了。