注意:我在询问问题之前已经看过帖子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。是否有更聪明,更清洁的方式,还是有一些现有的工作?
感谢。
答案 0 :(得分:9)
在任何情况下,永远不会使用scanf
或其亲属任何,原因有三:
%s
的所有简单用法,都与gets
一样危险。scanf
没有告诉你在字符中它在输入意外时所获得的输入中的距离。scanf
允许使整个程序崩溃。在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