【问题标题】:How to cleanly extract a string delimited string from an istream in c++如何从c ++中的istream中干净地提取字符串分隔字符串
【发布时间】:2014-05-06 16:55:21
【问题描述】:

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

以下是 istream 内容示例: delim_oneFUUBARdelim_two 我的目标是将FUUBAR 变成一个尽可能少的变通方法的字符串。

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

【问题讨论】:

    标签: c++ string delimiter istream


    【解决方案1】:

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

    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() 中有先例。

    可以看到运行here的代码。

    【讨论】:

    • 这同样只适用于数字或其他输入,这些输入与基于相应类型的分隔符有明显区别。
    • @Florian:好点 - 一些代码将涵盖最常见的添加重要情况。干杯。
    • +1 表示您的反应性;) 我不太确定您的 operator>> 的第一行在做什么,但 en.cppreference.com/w/cpp/concept/FormattedInputFunction 说应该创建一个哨兵对象。这有意义吗?
    • @Florian:如果流设置为跳过空格,则第一行将跳过空格,因为 get() 不会为您执行此操作,但您是对的,可以使用哨兵并且还做了一些其他有用的事情。感谢您指出了这一点!干杯。
    【解决方案2】:

    标准流配备了可以进行分类的语言环境,即std::ctype&lt;&gt; 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"
    }
    

    【讨论】:

    • 您正在描述如何查找数字,但这不是我要提取的唯一内容,分隔符之间的类型可以是任何东西,我只想要简短而干净的字符串方式
    • @nOvoid 也许正则表达式库可以帮助您解决这个问题。 -- en.cppreference.com/w/cpp/regex/basic_regex
    • 我只是在寻找一种更聪明、最好不使用额外库的方法,因为我希望这样一个常见的任务可以在没有库的情况下完成。也许其他人知道?
    • @nOvoid 正则表达式是完全标准的 C++(从 C++11 开始)。除此之外,您在解析字符串时会有一段不愉快的时光。
    猜你喜欢
    • 2018-02-05
    • 2015-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多