更改 cin 的定界符 (C++)

52

我已将 "cin" 重定向为从文件流读取 cin.rdbuf(inF.rdbuf())。当我使用抽取运算符时,它会一直读取,直到遇到空格字符。

是否可以使用其他分隔符?我查阅了 cplusplus.com 的 API,但没有找到相关内容。


3
您不应该使用std::cinoperator<<,您是不是想用>> - Ben Voigt
你可以尝试在cin缓冲区中包含空格字符。 - Agnius Vasiliauskas
@0x69:这不起作用。这只是意味着在给定输入“ A B”时,提取第一个单词会得到“ A”而不是“A”。 - MSalters
4个回答

53

可以使用 std::ios_base::imbue 添加自定义的 ctype facet,从而更改 cin 或任何其他 std::istream 的单词间分隔符。

如果您正在读取类似于 /etc/passwd 的文件,则以下程序将逐个读取每个以 : 分隔的单词。

#include <locale>
#include <iostream>


struct colon_is_space : std::ctype<char> {
  colon_is_space() : std::ctype<char>(get_table()) {}
  static mask const* get_table()
  {
    static mask rc[table_size];
    rc[':'] = std::ctype_base::space;
    rc['\n'] = std::ctype_base::space;
    return &rc[0];
  }
};

int main() {
  using std::string;
  using std::cin;
  using std::locale;

  cin.imbue(locale(cin.getloc(), new colon_is_space));

  string word;
  while(cin >> word) {
    std::cout << word << "\n";
  }
}

1
在编程中,不受控制地使用 new 是非常危险的。更不用说你还没有使用 delete 删除你的结构体(而且没有办法删除一个未命名的指针)。因此,在可能的情况下,一定要始终尝试使用 shared_ptr - Earth Engine
32
这通常是非常好的建议,但在这种特殊情况下不适用。在这种情况下,std::facet 是一个引用计数指针,std::locale::locale 需要一个原始指针而不是共享指针,并且 std::locale::~locale 被定义为删除 facet 指针。如果您对 locale 接口有问题,请联系标准委员会,而不是我。请参阅 http://en.cppreference.com/w/cpp/locale/locale/locale 上的示例程序。 - Robᵩ
3
虽然我建议定义一个包装函数get_locale来包装那些带有注释的不寻常使用new,这样代码审查者就会意识到接口存在问题,而不是代码编写者。这就是我所说的“可控”使用new的方式。 - Earth Engine
6
如果不创建新的函数,更好的表示所有权转移的方法可能是 unique_ptr<colon_is_space>(new colon is_space).release()。虽然它基本上与您的代码相同,但更冗长,它表明你正在传递指针所有权。 - Earth Engine

24

对于字符串,您可以使用std::getline重载函数以使用不同的分隔符进行读取。

对于数字提取,分隔符实际上并不是“空格”,而是任何数字中无效的字符。


1
@JonathanMee:我并没有说空格不是分隔符,我是说分隔符的集合不仅仅包括空格。尝试使用istringstream("123_456") >> foo;或者尝试使用istringstream("123|456") >> foo; - Ben Voigt
1
@JonathanMee:没错,虽然情况比这更复杂,一些标点符号在数字解析期间是允许的。显然,它是否被归类为空格可能会影响状态标志,但空格并不是唯一导致数字提取停止的因素。 - Ben Voigt
1
@Wolf 流通常是标准库中性能最差的部分之一。但是通常你会使用输入/输出流,因此相对于输入/输出操作的成本,慢速性能将是可以忽略不计的。出于性能原因,数组应该优先于流。 - Jonathan Mee
1
“slow performance will be negligible relative to the cost of the input/output operation” 在我的经验中从未成立。实际上,在许多应用程序中,文件I/O和解析相对于其他处理成本或等待用户点击启动按钮或网络请求而言都是微不足道的。但在使用iostreams构建的I/O密集型应用程序中,主导因素是iostream代码,而不是I/O操作本身。 - Ben Voigt
1
嗯...我想这是我曾经历过的项目类型。感谢澄清。拥有一个平衡的观点很好。我想对@Wolf的问题给出更好的答案应该是: "getline并不比整个流慢,但如果性能是您关心的问题,您应该寻找非流选项。" - Jonathan Mee
显示剩余3条评论

19
这是对Robᵩ答案的改进,因为那个答案是正确的(我很失望它还没有被接受)。
你需要做的是改变ctype查看以确定分隔符的数组。
在最简单的情况下,你可以创建自己的数组:
const ctype<char>::mask foo[ctype<char>::table_size] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ctype_base::space};

在我的电脑上,'\n' 的值为10。我已将数组的该元素设置为分隔符值:ctype_base::space。使用 foo 初始化的 ctype 仅会在 '\n' 上进行分隔,而不是在 ' ''\t' 上进行分隔。
现在这是一个问题,因为传递到 ctype 的数组定义了不仅仅是分隔符,还定义了字母、数字、符号和一些其他流所需的垃圾。(Ben Voigt的答案涉及到了这一点。)所以我们真正想做的是修改一个 mask,而不是从头开始创建一个。
可以通过以下方式实现:
const auto temp = ctype<char>::classic_table();
vector<ctype<char>::mask> bar(temp, temp + ctype<char>::table_size);

bar[' '] ^= ctype_base::space;
bar['\t'] &= ~(ctype_base::space | ctype_base::cntrl);
bar[':'] |= ctype_base::space;

使用bar初始化的ctype将在'\n'':'上进行分隔,但不会在' ''\t'上进行分隔。

您可以按照以下方式设置cin或任何其他istream以使用您的自定义ctype

cin.imbue(locale(cin.getloc(), new ctype<char>(data(bar))));

您还可以在流的中间切换 ctype ,行为将会发生变化:

cin.imbue(locale(cin.getloc(), new ctype<char>(foo)));

如果您需要恢复默认行为,只需执行以下操作:
cin.imbue(locale(cin.getloc(), new ctype<char>));

实时示例


这将把 bar['\t'] 设置为零,可能不是预期的。要清除一个位,请使用 &~(按位与按位非)。! 是逻辑非,不会产生预期的效果。 - Ben Voigt
@BenVoigt 谢谢,我想去掉spacecntrl位,结果不小心把所有东西都去掉了。 - Jonathan Mee

5
这是对Jon的答案和来自cppreference.com的示例的改进。因此,它遵循相同的前提条件,但将它们与参数化分隔符结合在一起。
struct delimiter_ctype : std::ctype<char> {
    static const mask* make_table(std::string delims)
    {
        // make a copy of the "C" locale table
        static std::vector<mask> v(classic_table(), classic_table() + table_size);
        for(mask m : v){
            m &= ~space;
        }
        for(char d : delims){
            v[d] |= space;
        }
        return &v[0];
    }
    delimiter_ctype(std::string delims, ::size_t refs = 0) : ctype(make_table(delims), false, refs) {}
};

干杯!


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