【问题标题】:Reading formatted input from an istream从 istream 读取格式化输入
【发布时间】:2012-01-27 01:27:33
【问题描述】:

以下问题已根据实际需求进行了简化。

考虑以下程序:

#include <iostream>
#include <iterator>
#include <string>
#include <set>
#include <algorithm>

using namespace std;

typedef string T; // to simplify, always consider T as string

template<typename input_iterator>
void do_something(const input_iterator& first, const input_iterator& last) {
    const ostream_iterator<T> os(cout, "\n");
    const set<T> words(first, last);
    copy(words.begin(), words.end(), os);
}

int main(int argc, char** argv) {
    const istream_iterator<T> is(cin), eof;
    do_something(is, eof);
    return 0;
}

程序从istream (cin) 中提取所有单词并对其进行处理。默认情况下,每个单词都由空格分隔。格式化提取背后的逻辑在istream_iterator内部。

我现在需要做的是将两个迭代器传递给do_something(),以便提取的单词将由标点符号而不是空格分隔(空格将被视为“正常”字符)。您将如何以“干净的 C++ 方式”(即,以最少的努力)做到这一点?

【问题讨论】:

    标签: c++


    【解决方案1】:

    虽然不是先验明显,但有一种相对简单的方法可以更改流认为是空白的内容。做到这一点的方法是imbue() 带有std::locale 对象的流,其std::ctype&lt;char&gt; facet 被替换以将所需的字符视为空格。 imbue()localectype - 嗯?!?好的,好吧,这些不一定是您日常使用的东西,所以这里有一个简单的示例,它设置 std::cin 以使用逗号和换行符作为间隔:

    #include <locale>
    template <char S0, char S1>
    struct commactype_base {
        commactype_base(): table_() {
            this->table_[static_cast<unsigned char>(S0)] = std::ctype_base::space;
            this->table_[static_cast<unsigned char>(S1)] = std::ctype_base::space;
        }
        std::ctype<char>::mask table_[std::ctype<char>::table_size];
    };
    template <char S0, char S1 = S0>
    struct ctype:
        commactype_base<S0, S1>,
        std::ctype<char>
    {
        ctype(): std::ctype<char>(this->table_, false) {}
    };
    

    实际上,std::ctype&lt;char&gt; 的这个特定实现实际上可以用来使用一个或两个任意的chars 作为空格(适当的 C++2011 版本可能允许任意数量的参数;另外,不要t 真的必须是模板参数)。无论如何,有了这个,只需在 main() 函数的开头添加以下行即可:

    std::cin.imbue(std::locale(std::locale(), new ::ctype<',', '\n'>));
    

    请注意,这实际上只将,\n 视为空格字符。这也意味着没有其他字符作为空格被跳过。 ...当然,多个逗号字符的序列被认为只是一个分隔符,而不是可能创建一堆空字符串。另请注意,上面的std::ctype&lt;char&gt; facet 删除了所有其他字符分类。如果您想解析字符串以外的其他对象,您可能希望保留其他字符分类并仅更改空格。这是一种可以做到的方法:

    template <char S0, char S1>
    struct commactype_base {
        commactype_base(): table_() {
            std::transform(std::ctype<char>::classic_table(),
                           std::ctype<char>::classic_table() + std::ctype<char>::table_size,
                           this->table_, 
                           [](std::ctype_base::mask m) -> std::ctype_base::mask {
                               return m & ~(std::ctype_base::space);
                           });
            this->table_[static_cast<unsigned char>(S0)] |= std::ctype_base::space;
            this->table_[static_cast<unsigned char>(S1)] |= std::ctype_base::space;
        }
        std::ctype<char>::mask table_[std::ctype<char>::table_size];
    };
    

    遗憾的是,这与我系统上的 gcc 版本崩溃(显然 std::ctype&lt;char&gt;::classic_table() 产生一个空指针。用当前版本的 clang 编译它不起作用,因为 clang 不支持 lambda。使用两个警告上面的代码应该是正确的,虽然......

    【讨论】:

    • 我没有看到用默认值填充表格的其余部分...这不会破坏所有非空格字符类型吗?
    • @Ben Voigt:我喜欢我的代码是正确的,但幸运的是:微妙的关键是: table_()
    • 所以ctype&lt;char&gt;::mask::mask() 用默认类型掩码填充表格?整洁,但是通常的空白字符仍然被标记为空格,因为您没有更改它们。
    • 等等,我不相信。 ctype&lt;char&gt;::maskchar 的 typedef,因此所有内容都初始化为零。这清除了通常空白字符的“空间”,但它也打破了所有其他 ctype 类别。没有字符会测试为upperlowerdigitxdigit 等。
    • ???默认掩码为空。我创建了一个空掩码表,并将表中的两个条目设置为空格。使用这个特殊的 std::ctype&lt;char&gt; 方面来改变空间的含义肯定不会太成功,但我并没有打算这样做。
    猜你喜欢
    • 2017-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 2021-06-30
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    相关资源
    最近更新 更多