在istream对象上的scanf

时间:2014-06-19 08:34:02

标签: c++ c c++11

注意:我在询问问题之前已经看过帖子What is the cin analougus of scanf formatted input?,而帖子并没有解决我的问题。这篇文章寻求C ++ - 这样做的方法,但正如我已经提到的那样,使用C ++是不方便的 - 有时这样做的方式,我有明确的例子。

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

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

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

感谢。

3 个答案:

答案 0 :(得分:9)

在任何情况下,永远不会使用scanf或其亲属任何,原因有三:

  1. 许多格式字符串,包括%s的所有简单用法,都与gets一样危险。
  2. 几乎不可能从格式错误的输入中恢复,因为scanf没有告诉你在字符中它在输入意外时所获得的输入中的距离。
  3. 数字溢出触发未定义的行为:是的,这意味着如果输入中的数字字段位数过多,则scanf允许使整个程序崩溃
  4. 在C ++ 11之前,C ++规范根据istream定义了scanf格式的数字输入,这意味着最后的异议很可能也适用于它们! (在C ++ 11中,规范被更改为使用strto*代替,并在检测到溢出时执行可预测的操作。)

    你应该做的是:用std::string读取输入到getline对象的整行输入,手动编码逻辑将它们分成字段(我不记得了我的头脑是什么C ++ - 相当于strsep的字符串,但我确定它存在),然后将数字字符串转换为具有strtol / strtod系列的机器数字功能

    我无法强调这一点: ONLY 100%可靠的方式将字符串转换为C或C ++中的数字,除非你有幸拥有一个C ++运行时在这方面,C ++ 11已经符合strto* FUNCTIONS,你必须正确使用它们:

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

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

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

答案 1 :(得分:1)

我不建议您混合C ++输入输出和C输入输出。不,他们真的不兼容,但他们可能只是简单的互操作错误。

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

但是没有人阻止你将数据读入缓冲区并使用sscanf等标准c函数进行解析。

...
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语言中的痛苦来源)。

我从来没有真正尝试boost\spirit这样的解析基础设施对你的项目来说真的太过分了。但是增强库通常经过充分测试和设计。你至少可以尝试使用它。

答案 2 :(得分:0)

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