在istream对象上扫描

scanf on an istream object

本文关键字:扫描 对象 istream      更新时间:2023-10-16

注意:我看过这篇文章什么是扫描格式输入的cin模拟?在问这个问题之前,这个帖子并没有解决我的问题。这篇文章寻求用c++的方式来做这件事,但正如我已经提到的,有时只使用c++的方式来做这件事是不方便的,我有明确的例子。

我试图从istream对象中读取数据,有时使用c++风格的方式(如操作符>>)很不方便,例如数据是特殊形式123:456,所以你必须让':'作为空间(这是非常hacky的,与scanf中的%d:%d相反),或者00123,你想要读取字符串并转换十进制而不是八进制(与scanf中的%d相反),可能还有许多其他情况。

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

所以我的问题是,是否有一种方法可以在istream对象上进行扫描式数据提取?我认为fscanf内部使用fgetc从FILE*逐字读取,istream也提供了这样的接口。因此,只需复制并粘贴fscanf的代码并将FILE*替换为istream对象,这是可能的,但这非常容易。有没有更聪明、更干净的方法,或者在这方面已经有了一些成果?

谢谢。

在任何情况下,您都不应该使用scanf或其相关项,原因有三:

  1. 许多格式字符串,包括%s的所有简单用法,都和gets一样危险。
  2. 几乎不可能从畸形输入中恢复,因为scanf没有告诉您在字符中进入输入的距离,当它遇到意外的东西时。
  3. 数字溢出触发未定义行为:是的,这意味着scanf被允许崩溃整个程序如果输入的数字字段有太多的数字。

在c++ 11之前,c++规范根据scanf定义了istream格式的数字输入,这意味着最后一个反对意见很可能也适用于它们!(在c++ 11中,规范改为使用strto*,并在检测到溢出时做一些可预测的事情。)

你应该做的是:用getline读取整行输入到std::string对象中,手工编码逻辑将它们分割成字段(我不记得strsep的c++字符串等效是什么,但我确信它存在),然后用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();

(上面链接的OpenBSD手册解释了为什么你必须做这个相当复杂的事情。)

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

我不建议您混合使用c++输入输出和C输入输出。不是说它们真的不兼容,但它们可能只是互操作错误。

例如Oracle文档建议不要混合使用http://www.oracle.com/technetwork/articles/servers-storage-dev/mixingcandcpluspluscode-305840.html

但是没有人会阻止你把数据读入缓冲区,然后用标准的c函数(比如sscanf)解析它。

...
string curString;
int a, b;
...
std::getline(inputStream, curString);
int sscanfResult == sscanf(curString.cstr(), "%d:%d", &a, &b);
if (2 != sscanfResult)
   throw "error";
...

但是在某些情况下,当你的流只是一个长而连续的符号序列时(比如一些字符串变成了内存流),它将不起作用。

从头创建自己的fscanf或移植(?)原始CRT函数实际上并不是最糟糕的主意。只要确保你已经对它进行了彻底的测试(低级的自定义字符操作一直是C语言的痛苦之源)。

我从来没有真正尝试过boostspirit,这样的解析基础设施对于您的项目来说可能真的是多余的。但是boost库通常经过良好的测试和设计。你至少可以试着用一下

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