【问题标题】:Which C I/O library should be used in C++ code? [closed]在 C++ 代码中应该使用哪个 C I/O 库? [关闭]
【发布时间】:2010-09-12 06:10:08
【问题描述】:

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

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

真的是这样吗?用什么比较好?

【问题讨论】:

    标签: c++ iostream stdio


    【解决方案1】:

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

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

    C++ 在 C 基础上向前迈进的一步是类型安全。

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

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

    只是为了澄清 Colin Jensen 的 cmets。

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

    Mikael Jansson 澄清 cmets。

    • 他提到的其他使用格式样式的语言具有明确的保护措施,以防止 C stdio 库的危险副作用(在 C 但不是提到的语言中)导致运行时崩溃。

    注意我同意 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;
    }
    

    【讨论】:

    • 您指的崩溃是什么?我不知道在 C++ 中携带运行时信息 (RTTI) 的原始类型,那么这会以什么方式改进呢?
    • 下面的很容易crash:'char s[2];scanf("%s",s);'iostream在编译时做类型检查。所以问题是在编译时而不是运行时发现的。
    • iostream 的另一个好处是它们可以处理从 std::iostream 派生的任何内容。因此,接受流的函数同样适用于控制台、文件或字符串输入。
    • @rlbond - fprintf 适用于任何 FILE 对象,可以从任何文件描述符创建。因此,它同样适用于各种输入/输出机制:任何可以实现为文件描述符的东西。
    • @LokiAstari 大多数编译器在编译时检查 scanf 和 printf 作为编译器扩展,尽管语言不需要。
    【解决方案2】:

    这太冗长了。

    考虑 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 这个用例充其量是被误导了。

    【讨论】:

    • 我真的想不出有任何语言可以“窃取” iostream,这可能意味着它确实像每个人都认为的那样糟糕。 :)
    • 使用 -Wformat (with g++) 你会得到 printf 等参数类型检查。
    • Brad:我很确定 Java 中的 Streams 受到了 iostreams 的启发,至少是它们的可插拔特性。
    • -1:您使用的 iostreams 错误。您应该为您的统计数据重载operator&lt;&lt;,然后客户可以像std::cout &lt;&lt; stats; 一样使用它,这显然胜过printf(...) 或必须接受文件句柄的自定义打印函数。
    • 即使你重载了
    【解决方案3】:

    Boost Format Library 为 printf 样式的字符串格式化提供了一种类型安全、面向对象的替代方案,并且是对 iostreams 的补充,它不会因巧妙地使用 operator% 而遭受通常的冗长问题。如果您不喜欢使用 iostream 的运算符 进行格式化,我建议您考虑使用普通 C printf

    【讨论】:

      【解决方案4】:

      在过去的糟糕日子里,C++ 标准委员会一直在讨论该语言,而 iostreams 是一个不断变化的目标。如果您使用 iostreams,那么您就有机会每年左右重写部分代码。正因为如此,我一直使用自 1989 年以来没有显着变化的 stdio。

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

          这是一个我对任何解决方案仍然不完全满意的领域。

          【讨论】:

          • +1 关于编译器和类型安全。如果没有良好的编译器支持(可破译的模板错误),没有人会使用 C++,因此让 C 编译器达到相同的标准似乎是合理的。
          • 您可以考虑创建一个 io 操纵器,它可以按照您想要的方式格式化,而不影响流。
          【解决方案7】:

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

          【讨论】:

          • 您还可以使用 istream::read() 和 ostream::write(),它们执行未格式化的 I/O。如果您正在混合使用格式化和未格式化的 I/O,这将非常有用。
          【解决方案8】:

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

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

          有几个理由限制它们的使用:

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

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

          不只是 printf 本身不好。软件变旧并被重构和修改,并且可能从偏远的地方引入错误。假设你有

          .

          // 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 仍然可以编译,只是你现在得到随机段错误并且你不记得为什么了。

          iostream 的详细程度

          如果您认为 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';
          

          你必须为 operatorFixedPoint 替换矩阵成员怎么办?),除了其他非琐碎的事情,例如你必须传递 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);
          

          【讨论】:

            【解决方案9】:

            虽然 C++ iostreams API 有很多好处,但一个重要的问题是围绕 i18n。问题是参数替换的顺序可能因文化而异。经典的例子是这样的:

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

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

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

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

            【讨论】:

            • 这实际上根本不是 C++ 流的缺点,而是使用它们的代码的缺点。 任何 I/O 库都会有同样的问题。最好使用name.formal_name() 方法解决,该方法将根据name.culture 返回正确的结果(例如)。
            • @paxdiablo - 不,许多 I/O 库可以通过乱序插值来解决这个问题(boost::format 和现代风格的 stdio 都可以做到这一点)。虽然我的原始示例可以做得更好,但请考虑并发布cout &lt;&lt; student_name &lt;&lt; " has a GPA of " &lt;&lt; gpa;
            • 这也是没有位置参数的 C 风格格式字符串的一个缺点。
            • 将本地化混入 C++ 流是一大错误。
            【解决方案10】:

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

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

            【讨论】:

              【解决方案11】:

              我对 iolibraries 怀念的是格式化的输入。

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

              【讨论】:

                【解决方案12】:

                stdio 更适合读取二进制文件(例如将块装入向量 并使用 .resize() 等)。示例参见http://nuwen.net/libnuwen.html 中 file.hh 中的 read_rest 函数。

                读取二进制文件时,C++ 流可能会阻塞大量字节,从而导致错误的 eof。

                【讨论】:

                • 如果您使用格式化的提取操作,您可能会遇到空字节的问题。但也有 read() 和 write() 方法,其工作方式与 fread()/fwrite() 类似
                • 另外,您可以使用也可以正常工作的较低级别的streambuf接口:istreambuf_iterator it(file), e;矢量 v(it, e);
                【解决方案13】:

                既然 iostreams 已经成为一种标准,您应该知道您的代码肯定会与较新版本的编译器一起工作,因此您应该使用它们。我想现在大多数编译器都非常了解 iostream,使用它们应该没有任何问题。

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

                【讨论】:

                • 不幸的是,它们依赖于 ABI,因此您不能将它们用于互连作为共享库开发的插件。
                猜你喜欢
                • 2017-09-11
                • 2015-05-24
                • 1970-01-01
                • 2010-09-21
                • 2010-10-30
                • 1970-01-01
                • 2012-01-12
                • 2010-10-21
                • 1970-01-01
                相关资源
                最近更新 更多