在istream对象上扫描
scanf on an istream object
注意:我看过这篇文章什么是扫描格式输入的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
或其相关项,原因有三:
- 许多格式字符串,包括
%s
的所有简单用法,都和gets
一样危险。 - 几乎不可能从畸形输入中恢复,因为
scanf
没有告诉您在字符中进入输入的距离,当它遇到意外的东西时。 - 数字溢出触发未定义行为:是的,这意味着
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
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- CMake-按正确顺序将项目与C运行时对象文件链接
- 空基优化子对象的地址
- 将对象数组的引用传递给函数
- 你能重载对象变量名本身返回的内容吗
- C++使用整数的压缩数组初始化对象
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 将对象移动到std::shared_ptr
- 代理对象的常量正确性
- 提升 ASIO 无法识别计时器对象
- 将Ref对象作为类成员
- 将包含C样式数组的对象初始化为成员变量(C++)
- 如何返回一个类的两个对象相加的结果
- 使用std::函数映射对象方法
- 是否需要删除包含对象的"pair"?
- 如何在自删除后将对象设置为nullptr
- 迭代时从向量和内存中删除对象
- 标记(在标记和扫描中)函数如何从根部访问的对象集
- 在istream对象上扫描