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

时间:2008-09-23 04:14:47

标签: c++ iostream stdio

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

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

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

13 个答案:

答案 0 :(得分:39)

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

Disadvantages of iostreams: verbose
Advantages    of iostreams: easy to extend for new non POD types.

C ++制作C ++的步骤是类型安全。

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

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

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

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

澄清Mikael Jansson的评论。

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

N.B。我同意iostream库有点冗长。但我愿意忍受冗长以确保运行时安全。但我们可以使用Boost Format Library来缓解详细程度。

#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;
}

答案 1 :(得分: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);

这需要以下内容:

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;

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

该用例的

iostream充其量是错误的。

答案 2 :(得分:14)

Boost Format Library为printf样式的字符串格式化提供了一种类型安全,面向对象的替代方案,并且是iostream的补充,由于巧妙地使用了operator%,因此不会出现通常的冗长问题。如果您不喜欢使用iostream的运算符&lt;&lt;

进行格式化,我建议您考虑使用普通的C打印。

答案 3 :(得分:9)

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

如果我今天做的话,我会使用iostreams。

答案 4 :(得分:6)

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

答案 5 :(得分:5)

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

这是一个我对任何解决方案都不满意的领域。

答案 6 :(得分:4)

对于二进制IO,我倾向于使用stdio的fread和fwrite。对于格式化的东西,我通常会使用IO Stream,虽然正如Mikael所说,非trival(非默认?)格式可以是PITA。

答案 7 :(得分:4)

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

您不应该在C ++中使用基于C-style-format-string的字符串处理例程。

有几个原因可以阻止它们的使用:

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

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

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

// foo.h
...
float foo;
...

某处...

// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...

三年后你发现foo应该是一些自定义类型......

// foo.h
...
FixedPoint foo;
...

但某处...

// bar/frob/42/icetea.cpp
...
scanf ("%f", &foo);
...

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

iostreams的详细程度

如果您认为printf()不那么详细,那么您有可能不会使用其iostream的全部力量。例如:

  printf ("My Matrix: %f %f %f %f\n"
          "           %f %f %f %f\n"
          "           %f %f %f %f\n"
          "           %f %f %f %f\n",
          mat(0,0), mat(0,1), mat(0,2), mat(0,3), 
          mat(1,0), mat(1,1), mat(1,2), mat(1,3), 
          mat(2,0), mat(2,1), mat(2,2), mat(2,3), 
          mat(3,0), mat(3,1), mat(3,2), mat(3,3));

将其与使用iostreams进行比较:

cout << mat << '\n';

您必须为运营商定义适当的重载&lt;&lt;它大致具有printf-thingy的结构,但显着的区别在于你现在有一些可重复使用和类型安全的东西;当然你也可以为printf-like制作一些可重复使用的东西,但是你再次使用printf(如果你用新的FixedPoint替换矩阵成员怎么办?),除了其他非平凡的东西,例如你必须传递FILE *手柄。

对于I18N而言,C风格的格式字符串并不比iostreams

更好

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

printf ("Guten Morgen, Sie sind %f Meter groß und haben %d Kinder", 
        someFloat, someInt);

printf ("Good morning, you have %d children and your height is %f meters",
        someFloat, someInt); // Note: Position changed.

// ^^ not the best example, but different languages have generally different
//    order of "variables"

即,旧式C格式字符串缺少与iostream相同的位置信息。

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

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

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

从不使用C风格的字符串吗?

除了表现(如Jan Hudec所指出的),我没有理由。但请记住:

  

“我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。然而,我们不应该放弃那个关键的3%的机会。一个好的程序员不会因为这样的推理而自满,他会明智地仔细研究关键代码;但只有在识别出代码之后“ - Knuth

  

“瓶颈发生在令人惊讶的地方,所以不要试图再次猜测并加速攻击,直到你证明这是瓶颈所在。” - 派克

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

但是不要盲目地忽略它的思考:你真正花费多少时间进行文本处理?退出前你的程序运行了多长时间? 是否与C风格的字符串相关,松散的类型安全性,降低可重构性, 增加非常微妙的错误的可能性,这些错误可能会隐藏自己多年,并且可能只能正确显示自己 进入你最喜欢的顾客面对?

就个人而言,如果我不能获得超过20%的加速,我就不会退缩。但是因为我的申请 几乎所有的时间花在除了字符串处理之外的其他任务上,我从来没有这么做过。一些解析器 我写的几乎所有的时间花在字符串处理上,但它们的总运行时间非常小 它不值得进行测试和验证。

一些谜语

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

查找所有错误,因为编译器不会(他只能建议他是否好):

shared_ptr<float> f(new float);
fscanf (stdout, "%u %s %f", f)

如果没有别的,这个怎么了?

const char *output = "in total, the thing is 50%"
                     "feature  complete";
printf (output);

答案 8 :(得分:3)

虽然C ++ iostreams API有很多好处,但一个重要的问题是i18n。问题是参数替换的顺序可以根据文化而变化。经典的例子是:

// i18n UNSAFE 
std::cout << "Dear " << name.given << ' ' << name.family << std::endl;

虽然这适用于英语,但在中文中,姓氏是第一位的。

在为国外市场翻译代码时,翻译片段充满了危险,因此新的l10ns可能需要更改代码而不仅仅是不同的字符串。

boost :: format似乎结合了最好的stdio(单个格式字符串,可以使用不同顺序的参数然后出现)和iostreams(类型安全性,可扩展性)。

答案 9 :(得分:2)

我使用iostreams,主要是因为这样可以让以后更容易摆弄流(如果我需要的话)。例如,您可能会发现要在某个跟踪窗口中显示输出 - 这对cout和cerr来说相对容易。当然,你可以在unix上摆弄管道和东西,但这不是那么便携。

我喜欢类似printf的格式,所以我通常首先格式化一个字符串,然后将其发送到缓冲区。使用Qt,我经常使用QString::sprintf(尽管他们建议使用QString::arg代替)。我也看过boost.format,但是不能真正习惯语法(太多%了)。不过,我应该看看它。

答案 10 :(得分:2)

我想念的iolibraries是格式化的输入。

iostreams没有很好的方法来复制scanf(),甚至boost也没有输入所需的扩展名。

答案 11 :(得分:1)

stdio更适合读取二进制文件(例如将块释放到向量&lt; unsigned char&gt;并使用.resize()等)。有关示例,请参阅http://nuwen.net/libnuwen.html中file.hh中的read_rest函数。

当读取二进制文件导致虚假的eof时,C ++流可能会扼杀大量字节。

答案 12 :(得分:1)

由于iostream已成为标准,因此您应该使用它们,因为您知道您的代码可以使用较新版本的编译器。我想现在大多数编译器都非常了解iostreams,使用它们应该没有任何问题。

但是如果你想坚持使用* printf函数,我认为没有任何问题。