如何在c++中清晰地从istream中提取字符串分隔的字符串

How to cleanly extract a string delimited string from an istream in c++

本文关键字:字符串 istream 提取 分隔 c++ 清晰      更新时间:2023-10-16

我试图从istream中提取字符串,其中字符串作为分隔符,但我还没有发现任何字符串操作,其行为接近istream中的find()substr()

下面是一个示例istream content:delim_oneFUUBARdelim_two我的目标是让FUUBAR变成一个字符串,尽可能少的变通。

我当前的解决方案是使用此解决方案将所有istream内容复制到字符串中,然后使用字符串操作提取。是否有一种方法可以避免这种不必要的复制,并且只从istream中读取所需的内容,以保留分隔字符串之后的所有内容,以防以类似的方式发现更多内容?

您可以轻松地创建一个使用预期分隔符或分隔符的类型:

struct Text
{
    std::string t_;
};
std::istream& operator>>(std::istream& is, Text& t)
{
    is >> std::skipws;
    for (char c: t.t_)
    {
        if (is.peek() != c)
        {
            is.setstate(std::ios::failbit);
            break;
        }
        is.get(); // throw away known-matching char
    }
    return is;
}

在ideone上查看

当前一个流提取自然停止而不使用分隔符时(例如,int提取后跟着一个不以数字开头的分隔符),这就足够了,除非前一个提取是std::string,否则通常是这种情况。可以将单字符分隔符指定为getline,但是假设分隔符是"</block>"并且流包含"<black>metalic</black></block>42" -您希望将"<black>metallic</black>"提取为string,丢弃"</block>"分隔符,并在流中保留"42":

struct Until_Delim {
    Until_Delim(std::string& s, std::string delim) : s_(s), delim_(delim) { }
    std::string& s_;
    std::string delim_;
};
std::istream& operator>>(std::istream& is, const Until_Delim& ud)
{
    std::istream::sentry sentry(is);
    size_t in_delim = 0;
    for (char c = is.get(); is; c = is.get())
    {
        if (c == ud.delim_[in_delim])
        {
            if (++in_delim == ud.delim_.size())
                break;
            continue;
        }
        if (in_delim) // was part-way into delimiter match...
        {
            ud.s_.append(ud.delim_, 0, in_delim);
            in_delim = 0;
        }
        ud.s_ += c;
    }
    // may need to trim trailing whitespace...
    if (is.flags() & std::ios_base::skipws)
        while (!ud.s_.empty() && std::isspace(ud.s_.back()))
            ud.s_.pop_back();
    return is;
}

可以这样使用:

string a_string;
if (some_stream >> Until_Delim(a_string, "</block>") >> whatevers_after)
    ...

这个表示法可能看起来有点粗俗,但是在标准库的std::quoted()中有先例。

您可以看到代码在这里运行

标准流配备了可以进行分类的区域设置,即std::ctype<> facet。当下一个可用字符中不存在某种分类时,我们可以对流中的ignore()字符使用此方面。下面是一个工作示例:

#include <iostream>
#include <sstream>
using mask = std::ctype_base::mask;
template<mask m>
void scan_classification(std::istream& is)
{
    auto& ctype = std::use_facet<std::ctype<char>>(is.getloc());
    while (is.peek() != std::char_traits<char>::eof() && !ctype.is(m, is.peek()))
        is.ignore();
}
int main()
{
    std::istringstream iss("some_string_delimiter3.1415another_string");
    double d;
    scan_classification<std::ctype_base::digit>(iss);
    if (iss >> d)
        std::cout << std::to_string(d); // "3.1415"
}