C++ istream对象上的scanf

C++ istream对象上的scanf,c++,c,c++11,C++,C,C++11,注意:我在提问之前看过这篇文章,但这篇文章并不能解决我的问题。这篇文章寻求用C++的方式来实现它,但正如我已经提到的,有时仅仅用C++的方式来实现它是不方便的,我有明确的例子 我正在尝试从istream对象读取数据,有时只使用C++风格的方式(例如运算符>>)会带来不便,例如,数据的格式是特殊的123:456,因此您必须将“:”嵌入到空格中(这非常粗糙,与scanf中的%d:%d相反),或者00123,在00123中,您希望读取为字符串并转换为十进制而不是八进制(与scanf中的%d相反),可能

注意:我在提问之前看过这篇文章,但这篇文章并不能解决我的问题。这篇文章寻求用C++的方式来实现它,但正如我已经提到的,有时仅仅用C++的方式来实现它是不方便的,我有明确的例子

我正在尝试从istream对象读取数据,有时只使用C++风格的方式(例如运算符>>)会带来不便,例如,数据的格式是特殊的123:456,因此您必须将“:”嵌入到空格中(这非常粗糙,与scanf中的%d:%d相反),或者00123,在00123中,您希望读取为字符串并转换为十进制而不是八进制(与scanf中的%d相反),可能还有许多其他情况

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

所以我的问题是,有没有一种方法可以在istream对象上进行类似scanf的数据提取?我认为fscanf内部使用fgetc从文件*中逐字读取数据,而istream也提供了这样的接口。因此,只需复制和粘贴fscanf的代码,并用istream对象替换文件*就可以了,但这是非常有技巧的。有没有一种更聪明、更干净的方法,或者在这方面有一些现有的工作


<>谢谢。

< P>我不建议你混合C++输入输出和C输入输出。不,它们确实不兼容,但它们可以简单地互操作错误。< /P> 例如,Oracle文档建议不要将其混合使用

但是没有人阻止您将数据读入缓冲区,并使用标准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中总是一个痛苦的来源)

我从来没有真正尝试过,这样的解析基础设施对您的项目来说可能真的是一种过火的技术。但是boost库通常经过良好的测试和设计。您至少可以尝试使用它。

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

  • 许多格式字符串,例如包括
    %s
    的所有简单用法,与
    得到的
    一样危险
  • 从格式错误的输入中恢复几乎是不可能的,因为
    scanf
    不会告诉您当它遇到意外情况时,输入中的字符数有多大
  • 数值溢出触发未定义的行为:是的,这意味着如果输入中的数值字段有太多的数字,允许
    scanf
    使整个程序崩溃
  • < C++ > C++之前,C++规范定义了<代码> istRAM/<代码>按“代码> SCANF</代码>的方式格式化的数字输入,这意味着最后的反对意见也很可能适用于它们!(在C++ 11中,规范被改变为使用<代码> StRoT*<代码>,并在检测到溢出时做一些可预测的事情) 相反,您应该做的是:使用
    getline
    将整行输入读取到
    std::string
    对象中,手工编写逻辑以将它们拆分为字段(我不记得C++字符串的等价物是什么,但我确信它存在)然后使用/family函数将数字字符串转换为机器编号

    <>我不能强调这一点:在C或C++中,“强”>只将<或> 100%个可靠的字符串转换为数字,除非你有幸有一个C++运行时,C++已经在这方面符合11,这是用<代码> Stto*函数,你必须正确使用:

    errno = 0;
    result = strtoX(s, &ends, 10); // omit 10 for floats
    if (s == ends || *ends || errno)
      parse_error();
    
    (上面链接的OpenBSD手册页解释了为什么要做这件相当复杂的事情。)


    (如果您很聪明,可以使用
    结尾
    和一些手动逻辑跳过冒号,而不是
    strep

    基于@tmyklebu的评论,我通过fopencookie实现了streamScanf,它将istream包装为文件*,

    可能相关@Scis:只是非常松散。它肯定不是重复的。@LightnessRacesinOrbit这就是为什么我没有将其标记为:)只是建议它可能在某种程度上有用\相关(即使松散)。POSIX.1-2008中似乎有
    fmemopen
    open_memstream
    。然而,
    fopencookie
    是一个GNU扩展,它可以完全替代
    istream
    。可惜。@tmyklebu,认识fopencookie很有趣。但我已经决定使用istream了。另外,我在我的原始帖子中没有提到,我使用模板,并且我依赖操作符>>上的重载来处理大多数情况,而在istream上使用scanf是为了处理一些特殊情况。因此,感谢fopencookie上的指针,但我仍然会在istream上寻找fscanf。sscanf的问题是,如果返回值小于要分析的项数,则无法判断,因为在分析过程中出现了一些错误,或者底层字符串已用完(因此您必须从inputStream中读取更多内容,然后重试).我已经说过了('当你的流只有一个长…')。可能不是最好的方式。你提到的绝对是一个很好的例子。但更常见的情况是
    scanf
    接受多行。例如,如果输入是
    char buf[]=“123 456\n789”
    ,那么
    scanf(buf,“%d%d%d”,&a,&b,&c)
    将工作,但
    getline
    将错过第三个。任意长流的主要问题是getline何时返回。如果你的流是一条无止境的线,这个方法对你没有任何帮助