如何解析存储在文本缓冲区中的整数序列

How to parse a sequence of integers stored in a text buffer?

本文关键字:整数 缓冲区 何解析 存储 文本      更新时间:2023-10-16

在C++中解析由流中的整数序列组成的文本很容易:只需解码即可。当数据以某种方式被接收并且在程序中很容易获得时,例如,接收base64编码的文本(解码不是问题),情况就有点不同了。数据位于程序的缓冲区中,只需要解码,而不需要读取。当然,也可以使用std::istringstream

std::vector<int> parse_text(char* begin, char* end) {
    std::istringstream in(std::string(begin, end));
    return std::vector<int>(std::istream_iterator<int>(in),
                            std::istream_iterator<int>());
}

由于接收到了很多这样的缓冲区,并且它们可能相当大,因此希望不要复制字符数组的实际内容,理想情况下,还可以避免为每个缓冲区创建流。因此,问题变成了:

给定一个包含(空间分隔的;处理其他分隔符很容易,例如使用合适的操纵器)整数序列的chars缓冲区,如何在不复制序列的情况下对其进行解码,如果可能的话,甚至不创建std::istream

使用自定义流缓冲区可以很容易地避免缓冲区的副本,该缓冲区只需设置使用缓冲区的get区域。流缓冲区实际上甚至不需要覆盖任何虚拟函数,只需要设置内部缓冲区:

class imemstream
    : private virtual std::streambuf
    , public std::istream
{
public:
    imemstream(char* begin, char* end)
        : std::streambuf()
        , std::istream(static_cast<std::streambuf*>(this))
    {
        this->setg(begin, begin, end); 
    }
};
std::vector<int> parse_data_via_istream(char* begin, char* end)
{
    imemstream in(begin, end);
    return std::vector<int>(std::istream_iterator<int>(in),
                            std::istream_iterator<int>());
}

这种方法避免了复制流,并使用现成的std::istream功能。但是,它确实创建了一个流对象。通过适当的更新功能,可以扩展流/流缓冲器以重置缓冲器并处理多个缓冲器。

为了避免创建流,可以使用std::num_get<...>的底层功能。实际的解析是由std::locale方面之一完成的。std::istream的数值解析是由std::num_get<char, std::istreambuf_iterator<char>>完成的。这个方面没有多大帮助,因为它使用了std::istreambuf_iterator<char>s指定的序列,但可以实例化std::num_get<char, char const*>方面。它不会是默认std::locale的一部分,但很容易创建相应的std::locale并将其安装,例如,作为main()中的全局std::locale对象:

int main()
{
    std::locale::global(std::locale(std::locale(),
                                    new std::num_get<char, char const*>()));
    ...

请注意,std::locale对象将清理添加的facet,即不需要添加任何清理代码:facet被引用计数,并在持有特定facet的最后一个std::locale消失时释放。不幸的是,要真正使用facet,它需要一个std::ios_base对象,而这个对象实际上只能从某个流对象中获得。然而,任何流都可以使用(尽管在多线程系统中,它可能应该是每个流的一个单独的流对象,以避免意外的竞争条件):

char const* skipspace(char const* it, char const* end)
{
    return std::find_if(it, end,
                        [](unsigned char c){ return !std::isspace(c); });
}
std::vector<int> parse_data_via_istream(std::ios_base& fmt,
                                        char const* it, char const* end)
{
    std::vector<int> rc;
    std::num_get<char, char const*> const& ng
        = std::use_facet<std::num_get<char, char const*>>(std::locale());
    std::ios_base::iostate error;
    for (long tmp;
         (it = ng.get(skipspace(it, end), end, fmt, error, tmp))
             , error == std::ios_base::goodbit; ) {
        rc.push_back(tmp);
    }
    return rc;
}

其中大部分只是一些错误处理和跳过前导空格:大多数情况下,std::istream提供了自动跳过格式化输入的空格的功能,并处理必要的错误协议。在每个缓冲区只获得一次方面,避免创建std::istream::sentry对象以及避免创建流方面,上述方法可能有一个小优点。当然,代码假设可以使用某个流将其作为std::ios_base&子对象传入,以提供解析标志,如要使用的基。

好吧,这是一个相当多的代码,strtol()也可以做很多事情。使用std::num_get<char, char const*>的方法具有strtol():所没有的一些灵活性

  1. 由于使用了std::locale的方面,它可以被覆盖来解析任意格式的表示,例如罗马数字,因此它在输入格式方面更灵活
  2. 设置使用数千个分隔符或更改小数点的表示方式很容易(只需更改fmt使用的std::locale中的std::numpunct<char>即可设置这些分隔符)
  3. 缓冲区不必以null结尾。例如,当调用std::num_get<char, char const*>::get()时,可以通过输入itit+8作为范围来解析由8个数字值组成的连续字符序列

然而,strtol()对于大多数用途来说可能是一个不错的方法。另一方面,上述内容提供了在某些情况下可能有用的替代方案。