从 istream 中读取格式化输入

3
下面的问题已经从实际需求中简化。
考虑以下程序:
#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++方式”完成这个任务(即,最小的努力)?
1个回答

4
尽管这不是一个自然而然的过程,但有一个相对简单的方法可以改变流所认为的空格。方法是使用一个 std::locale 对象来 imbue() 流,该对象的 std::ctype<char> 选项被替换为将期望的字符视为空格。 虽然这里提到了 imbue(), locale, ctype 等概念,它们并不是日常开发中必须用到的,因此下面举个例子,演示如何将 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<char>的这个具体实现可以用来将一个或两个任意的char作为空格(一个适当的C++ 2011版本可能允许任意数量的参数; 另外,它们不一定是模板参数)。无论如何,如果采用此方法,在您的 main() 函数开头插入以下行即可:

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

请注意,这只将 ,\n 视为空格字符。这也意味着不会跳过其他字符作为空格。同时,多个逗号字符的序列被视为一个分隔符而不是可能创建一堆空字符串。还要注意上述的 std::ctype<char> 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<char>::classic_table() 返回了一个空指针)。如果使用最新版本的clang编译,则会因为clang不支持lambda而失败。除了这两个注意事项外,上述代码应该是正确的...


我没有看到表格的其余部分被填充了默认值...这难道不会破坏所有非空字符类型吗? - Ben Voigt
@Ben Voigt:我喜欢我的代码是正确的,但幸运的是它确实是:关键在于:table_() - Dietmar Kühl
等一下,我还不确定。 ctype <char> :: mask是一个typedef为char的类型,因此所有内容都将被值初始化为零。这会清除通常的空格字符的“松散性”,但也会破坏所有其他ctype类别。没有字符会测试为“upper”、“lower”、“digit”、“xdigit”等。 - Ben Voigt
当facet在“digit”类别中没有字符时,格式化输入不会出错吗? - Ben Voigt
结合https://dev59.com/jm035IYBdhLWcg3wH8Ul,这完美地解决了我的问题。 - Samveen
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接