在istream对象上的scanf

Kan*_* Li 6 c c++ c++11

注意:我看过帖子什么是scanf格式输入的cin analougus?在提问之前,这个帖子并没有解决我的问题.该帖子寻求C++ - 这样做的方式,但正如我已经提到的那样,使用C++是不方便的 - 有时这样做的方式,我有明确的例子.

我试图从istream对象中读取数据,有时候使用C++风格的方法(例如operator >>)是不方便的,例如数据是特殊形式的123:456所以你必须灌输':'作为space(非常hacky,而不是%d:scanf中的%d),或00123,你想要读取字符串并转换十进制而不是八进制(而不是scanf中的%d),以及可能的许多其他情况.

我选择istream作为接口的原因是因为它可以派生,因此更灵活.例如,我们可以创建内存流,或者动态生成的一些自定义流等.另一方面,C风格的文件*非常有限,至少在符合标准的方式下,创建自定义流.

所以我的问题是,有没有办法在istream对象上进行类似scanf的数据提取?我认为fscanf在内部使用fgetc从FILE*逐个字符地读取,而istream也提供这样的接口.因此可以通过复制和粘贴fscanf的代码并用istream对象替换FILE*来实现,但这非常hacky.是否有更聪明,更清洁的方式,还是有一些现有的工作?

谢谢.

zwo*_*wol 10

你应该从来没有,在任何情况下,使用scanf或者其亲属东西,原因有三:

  1. 许多格式字符串,包括例如所有简单用法%s,都同样危险gets.
  2. 几乎不可能从格式错误的输入中恢复,因为scanf它没有告诉你遇到意外事件时它在输入中输入的字符有多远.
  3. 数字溢出会触发未定义的行为:是的,如果输入中的数字字段包含太多数字,则表示scanf允许整个程序崩溃.

在C++ 11之前,C++规范定义istream了数字格式的输入scanf,这意味着最后的异议很可能也适用于它们!(在C++ 11中,规范被改为使用,strto*而是在检测到溢出时做一些可预测的事情.)

你应该做的是:将整行输入读入std::string对象,用getline手工编码逻辑将它们分成几个字段(我不记得我脑子里的C++ - 字符串相当于什么strsep,但我'确定它存在)然后使用strtol/ strtod系列函数将数字字符串转换为机器号.

我不能强调这一点:在C或C++中将字符串转换为数字的唯一 100%可靠方式,除非你足够幸运地拥有在这方面已经符合C++ 11标准的C++运行时,具有这些strto*功能,你必须正确使用它们:

errno = 0;
result = strtoX(s, &ends, 10); // omit 10 for floats
if (s == ends || *ends || errno)
  parse_error();
Run Code Online (Sandbox Code Playgroud)

(上面链接的OpenBSD联机帮助页解释了为什么你必须做这个相当复杂的事情.)

(如果你很聪明,你可以使用ends一些手动逻辑来跳过那个冒号,而不是strsep.)


Kan*_* Li 0

根据@tmyklebu的评论,我实现了streamScanf,它通过fopencookie将istream包装为FILE*: https: //github.com/likan999/codejam/blob/master/Common/StreamScanf.cpp