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

时间:2014-04-12 22:14:11

标签: c++

从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>());
}

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

给定一个缓冲区char s包含一系列(空格分隔;处理其他分隔符很容易完成,例如,使用合适的操纵器)整数如何解码它们而不复制序列,如果可能的话,甚至没有创建std::istream

1 个答案:

答案 0 :(得分:5)

使用自定义流缓冲区可以轻松完成缓冲区的复制,只需设置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>指定的序列,但可以实例化std::num_get<char, char const*>方面。它不会成为默认std::locale的一部分,但很容易创建相应的std::locale并安装它,例如,作为{{1}中的全局std::locale对象}}:

main()

请注意,int main() { std::locale::global(std::locale(std::locale(), new std::num_get<char, char const*>())); ... 对象将清理添加的构面,即无需添加任何清理代码:在最后std::locale持有时,构面被引用计数并释放一个特定的方面消失了。不幸的是,为了实际使用它,需要一个std::locale对象,它只能从某个流对象中获得。但是,可以使用任何流(尽管在多线程系统中,它应该是每个流的单独流对象,以避免意外的竞争条件):

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*>无法提供:

  1. 由于使用的strtol()方面可以被覆盖以解析任意格式的表示,例如罗马数字,因此在输入格式方面更灵活。
  2. 很容易设置千位分隔符的使用或更改小数点的表示(只需更改std::locale std::numpunct<char>使用的std::locale来设置它们。
  3. 缓冲区不必以空值终止。例如,可以通过在调用fmt时将itit+8作为范围来解析由8位数值组成的连续字符序列。
  4. 但是,std::num_get<char, char const*>::get()可能是大多数用途的好方法。另一方面,上面提供了一种在某些情况下可能有用的替代方案。