应该在C++代码中使用哪个CI/O库?

Fer*_*cio 33 c++ iostream stdio

在新的C++代码中,我倾向于使用C++ iostream库而不是C stdio库.

我注意到一些程序员似乎坚持stdio,坚持认为它更便携.

这是真的吗?有什么好用的?

Mar*_*ork 39

回答原始问题:
可以使用iostream库完成使用stdio完成的任何操作.

Disadvantages of iostreams: verbose
Advantages    of iostreams: easy to extend for new non POD types.
Run Code Online (Sandbox Code Playgroud)

通过C制作的C++的前进是类型安全.

  • iostreams被设计为明确类型安全.因此,对象的赋值也明确地检查了所分配对象的类型(在编译器时)(如果需要,则生成编译时错误).从而防止运行时内存溢出或将浮点值写入char对象等.

  • 另一方面,scanf()/ printf()和family依赖于程序员正确的格式字符串,并且没有类型检查(我相信gcc有一个帮助的扩展).因此,它是许多错误的根源(因为程序员的分析不如编译器完美[不会说编译器完美只比人类更好]).

只是为了澄清Colin Jensen的评论.

  • 自上一个标准发布以来,iostream库一直保持稳定(我忘记了实际的年份,但大约10年前).

澄清Mikael Jansson的评论.

  • 他提到使用格式样式的其他语言有明确的安全措施来防止C stdio库的危险副作用(可能是C语言而不是上述语言)导致运行时崩溃.

NB我同意iostream库有点冗长.但我愿意忍受冗长以确保运行时安全.但是我们可以通过使用Boost格式库来减轻冗长.

#include <iostream>
#include <iomanip>
#include <boost/format.hpp>

struct X
{  // this structure reverse engineered from
   // example provided by 'Mikael Jansson' in order to make this a running example

    char*       name;
    double      mean;
    int         sample_count;
};
int main()
{
    X   stats[] = {{"Plop",5.6,2}};

    // nonsense output, just to exemplify

    // stdio version
    fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
            stats, stats->name, stats->mean, stats->sample_count);

    // iostream
    std::cerr << "at " << (void*)stats << "/" << stats->name
              << ": mean value " << std::fixed << std::setprecision(3) << stats->mean
              << " of " << std::setw(4) << std::setfill(' ') << stats->sample_count
              << " samples\n";

    // iostream with boost::format
    std::cerr << boost::format("at %p/%s: mean value %.3f of %4d samples\n")
                % stats % stats->name % stats->mean % stats->sample_count;
}
Run Code Online (Sandbox Code Playgroud)

  • iostream的另一个好处是它们可以处理从std :: iostream派生的任何东西.因此,接受流的函数同样适用于控制台,文件或字符串输入. (7认同)

Mik*_*son 16

它太冗长了.

思考iostream构造以执行以下操作(类似于scanf):

// nonsense output, just to examplify
fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
    stats, stats->name, stats->mean, stats->sample_count);
Run Code Online (Sandbox Code Playgroud)

这将需要类似的东西:

std::cerr << "at " << static_cast<void*>(stats) << "/" << stats->name
          << ": mean value " << std::precision(3) << stats->mean
          << " of " << std::width(4) << std::fill(' ') << stats->sample_count
          << " samples " << std::endl;
Run Code Online (Sandbox Code Playgroud)

字符串格式化是一种情况,面向对象可以而且应该回避有利于嵌入字符串的格式化DSL.考虑Lisp format,Python的printf样式格式,或PHP,Bash,Perl,Ruby及其字符串插值.

iostream 因为这个用例充其量是错误的.

  • -1:你正在使用iostreams错误.您应该为您的统计信息重载`operator <<`,然后客户端可以像`std :: cout << stats;`一样使用它,它显然胜过`printf(...)`或者自定义打印函数接受文件句柄. (18认同)
  • 我真的想不出任何"偷走"iostreams的语言,这可能意味着它真的像每个人似乎都想的那样糟糕.:) (12认同)
  • 使用-Wformat(使用g ++),您将获得printf等参数类型检查. (4认同)
  • 布拉德:我很确定Java中的Streams受到了iostream的启发,至少是它们的可插拔性. (3认同)
  • 即使你重载<<你仍然需要在它的函数体中写这个怪物. (3认同)

小智 14

升压格式库提供了printf风格的字符串格式化一个类型安全的,面向对象的替代品,到不从通常的冗长的问题遭受输入输出流的补充,由于巧妙地利用运营商%.如果您不喜欢使用iostream的operator <<进行格式化,我建议您考虑使用普通的C printf.


Col*_*sen 9

回到过去的糟糕时期,C++标准委员会一直在使用该语言,而iostreams则是一个不断变化的目标.如果您使用了iostream,那么您每年都有机会重写部分代码.因此,我总是使用自1989年以来没有显着变化的stdio.

如果我今天做的事情,我会使用iostreams.


Ada*_*rce 6

如果像我一样,你在学习C++之前学过C,那么stdio库似乎更自然.iostream与stdio有利有弊,但在使用iostream时我确实错过了printf().


Dan*_*ett 5

原则上我会使用iostreams,在实践中我做了太多格式化的小数等,使得iostreams太难以理解,所以我使用了stdio.Boost :: format是一种改进,但对我来说还不够激励.在实践中,stdio几乎是类型安全的,因为大多数现代编译器无论如何都会进行参数检查.

这是一个我仍然不满意任何解决方案的领域.


Seb*_*ach 5

我将比较 C++ 标准库中的两个主流库。

\n\n

您不应该在 C++ 中使用基于 C 风格格式字符串的字符串处理例程。

\n\n

限制使用它们有几个原因:

\n\n
    \n
  • 类型不安全
  • \n
  • 您不能将非 POD 类型传递给可变参数列表(即,既不能传递给 scanf+co.,也不能传递给 printf+co.),\也不能进入未定义行为的黑暗要塞
  • \n
  • 容易出错:\n
      \n
    • 您必须设法保持格式字符串和“值参数列表”同步
    • \n
    • 您必须正确保持同步
    • \n
  • \n
\n\n

在偏远地区引入的微妙错误

\n\n

不仅仅是 printf 本身不好。软件会变旧并被重构和修改,并且错误可能会从远程位置引入。假设你有

\n\n

\n\n
// foo.h\n...\nfloat foo;\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

和某个地方......

\n\n
// bar/frob/42/icetea.cpp\n...\nscanf ("%f", &foo);\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

三年后,您发现 foo 应该是某种自定义类型......

\n\n
// foo.h\n...\nFixedPoint foo;\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

但在某个地方...

\n\n
// bar/frob/42/icetea.cpp\n...\nscanf ("%f", &foo);\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

...那么你的旧 printf/scanf 仍然会编译,除了你现在得到随机段错误并且你不记得为什么。

\n\n

iostream 的详细程度

\n\n

如果您认为 printf() 不太冗长,那么您很可能没有充分利用其 iostream。例子:

\n\n
  printf ("My Matrix: %f %f %f %f\\n"\n          "           %f %f %f %f\\n"\n          "           %f %f %f %f\\n"\n          "           %f %f %f %f\\n",\n          mat(0,0), mat(0,1), mat(0,2), mat(0,3), \n          mat(1,0), mat(1,1), mat(1,2), mat(1,3), \n          mat(2,0), mat(2,1), mat(2,2), mat(2,3), \n          mat(3,0), mat(3,1), mat(3,2), mat(3,3));\n
Run Code Online (Sandbox Code Playgroud)\n\n

将其与正确使用 iostream 进行比较:

\n\n
cout << mat << \'\\n\';\n
Run Code Online (Sandbox Code Playgroud)\n\n

您必须为operator<< 定义适当的重载,它大致具有printf-thingy 的结构,但显着的区别是您现在拥有可重用且类型安全的东西;当然,你也可以为 printf 之类的东西重用,但是你又拥有了 printf (如果你用新的替换矩阵成员怎么办FixedPoint?),除了其他重要的事情,例如你必须传递 FILE* 句柄。

\n\n

C 风格格式字符串对于 I18N 并不比 iostream 更好

\n\n

请注意,格式字符串通常被认为是国际化的救援,但在这方面它们并不比 iostream 更好:

\n\n
printf ("Guten Morgen, Sie sind %f Meter gro\xc3\x9f und haben %d Kinder", \n        someFloat, someInt);\n\nprintf ("Good morning, you have %d children and your height is %f meters",\n        someFloat, someInt); // Note: Position changed.\n\n// ^^ not the best example, but different languages have generally different\n//    order of "variables"\n
Run Code Online (Sandbox Code Playgroud)\n\n

即,旧式 C 格式字符串与 iostream 一样缺乏位置信息。

\n\n

您可能需要考虑boost::format,它支持显式声明格式字符串中的位置。从他们的示例部分:

\n\n
cout << format("%1% %2% %3% %2% %1% \\n") % "11" % "22" % "333"; // \'simple\' style.\n
Run Code Online (Sandbox Code Playgroud)\n\n

一些 printf 实现提供位置参数,但它们是非标准的。

\n\n

应该使用 C 风格的格式字符串吗?

\n\n

除了性能(正如扬·邬达克所指出的)之外,我看不出有什么原因。但请记住:

\n\n
\n

\xe2\x80\x9c我们应该忘记小的效率,大约 97% 的时间:过早的优化是万恶之源。然而,我们不应该放弃这关键的 3% 的机会。一个好的程序员不会因为这样的推理而沾沾自喜,他会明智地仔细查看关键代码;但只有在该代码被识别之后\xe2\x80\x9d - Knuth

\n
\n\n

\n\n
\n

\xe2\x80\x9c瓶颈出现在令人惊讶的地方,因此在证明瓶颈所在之前,不要尝试再次猜测并进行速度黑客攻击。\xe2\x80\x9d - Pike

\n
\n\n

是的, printf 实现通常比 iostream 更快,通常比 boost::format 更快(来自我编写的一个小型且具体的基准测试,但这在很大程度上应该取决于具体情况:如果 printf=100%,则 iostream=160% ,并且 boost::format=220%)

\n\n

但不要盲目地忽略这一点:你真正花了多少时间在文本处理上?你的程序在退出之前运行了多长时间?\n是否与回退到 C 样式格式字符串、松散类型安全性、降低可重构性、\n增加非常微妙的错误的可能性有关,这些错误可能会隐藏多年,并且可能只会暴露自己对\n您最喜欢的客户面孔吗?

\n\n

就我个人而言,如果我不能获得超过 20% 的加速,我就不会退缩。但因为我的应用程序几乎将所有时间都花在字符串处理之外的其他任务上,所以我从来不需要这样做。我编写的一些解析器几乎将所有时间都花在字符串处理上,但它们的总运行时间是如此之小,以至于不值得进行测试和验证工作。

\n\n

一些谜语

\n\n

最后,我想预设一些谜语:

\n\n

找到所有错误,因为编译器不会(他只能在他友善的情况下提出建议):

\n\n
shared_ptr<float> f(new float);\nfscanf (stdout, "%u %s %f", f)\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果不出意外的话,这个有什么问题吗?

\n\n
const char *output = "in total, the thing is 50%"\n                     "feature  complete";\nprintf (output);\n
Run Code Online (Sandbox Code Playgroud)\n