Kon*_*ski 296

令我感到惊讶的是,这个问题中的每个人都声称这种情况std::cout要好得多printf,即使这个问题只是要求差异.现在,有一个区别 - std::cout是C++,而且printf是C(但是,你可以在C++中使用它,就像C中几乎所有其他东西一样).现在,我会在这里说实话; 双方printfstd::cout有自己的优势.

真正的差异

可扩展性

std::cout是可扩展的.我知道人们会说这printf也是可扩展的,但是C标准中没有提到这种扩展(所以你必须使用非标准功能 - 但是甚至不存在常见的非标准功能),并且这样的扩展是一个字母(因此很容易与已存在的格式冲突).

与完全不同printf,std::cout完全取决于运算符重载,因此自定义格式没有问题 - 您所做的就是定义一个子程序std::ostream作为第一个参数,您的类型作为第二个参数.因此,没有命名空间问题 - 只要你有一个类(不限于一个字符),你就可std::ostream以为它工作重载.

但是,我怀疑很多人会想要扩展ostream(说实话,我很少看到这样的扩展,即使它们很容易制作).但是,如果你需要它就在这里.

句法

因为它可以很容易地发现,无论是printfstd::cout使用不同的语法.printf使用模式字符串和可变长度参数列表使用标准函数语法.实际上,printf这就是为什么C有它们的原因 - printf格式太复杂,没有它们就无法使用.但是,std::cout使用不同的API - operator <<返回自身的API.

通常,这意味着C版本会更短,但在大多数情况下它并不重要.打印多个参数时,差异很明显.如果您必须编写类似的内容Error 2: File not found.,假设错误编号,并且其描述是占位符,则代码将如下所示.两个示例的工作方式相同(好吧,std::endl实际上是刷新缓冲区).

printf("Error %d: %s.\n", id, errors[id]);
std::cout << "Error " << id << ": " << errors[id] << "." << std::endl;
Run Code Online (Sandbox Code Playgroud)

虽然这看起来并不太疯狂(只是它的两倍),但实际设置参数格式时,事情变得更加疯狂,而不仅仅是打印它们.例如,打印类似的东西0x0424只是疯了.这是由std::cout混合状态和实际值引起的.我从来没有看到类似于std::setfill某种类型的语言(当然除了C++之外).printf明确区分参数和实际类型.我真的更愿意保持printf它的版本(即使它看起来有点神秘)与iostream它的版本相比(因为它包含太多的噪音).

printf("0x%04x\n", 0x424);
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl;
Run Code Online (Sandbox Code Playgroud)

翻译

这就是printf谎言的真正优势所在.该printf格式字符串是很好...字符串.与operator <<虐待相比,这使得翻译变得非常容易iostream.假设gettext()函数转换,并且您想要显示Error 2: File not found.,用于转换先前显示的格式字符串的代码将如下所示:

printf(gettext("Error %d: %s.\n"), id, errors[id]);
Run Code Online (Sandbox Code Playgroud)

现在,让我们假设我们翻译为Fictionish,其中错误编号在描述之后.翻译后的字符串看起来像%2$s oru %1$d.\n.现在,如何在C++中完成它?好吧,我不知道.为了翻译的目的,我想你可以伪造iostream哪些构造printf你可以传递给gettext什么东西.当然,$不是C标准,但它很常见,在我看来它是安全的.

不必记住/查找特定的整数类型语法

C有很多整数类型,C++也是如此.std::cout为你处理所有类型,同时printf需要特定的语法取决于整数类型(有非整数类型,但你将在实践中使用的唯一非整数类型printfconst char *(C字符串,可以使用to_c方法获得std::string)).例如,要打印size_t,您需要使用%zd,同时int64_t需要使用%"PRId64".这些表格可在http://en.cppreference.com/w/cpp/io/c/fprintfhttp://en.cppreference.com/w/cpp/types/integer上找到.

你不能打印NUL字节, \0

因为printf使用C字符串而不是C++字符串,所以如果没有特定的技巧,它就无法打印NUL字节.在某些情况下,它可以使用%c'\0'作为参数,尽管这显然是一个黑客.

无人问津的差异

性能

更新:事实证明它iostream太慢了,它通常比你的硬盘驱动器慢(如果你将程序重定向到文件).stdio如果需要输出大量数据,禁用同步可能会有所帮助.如果性能是一个真正的问题(而不是写几行到STDOUT),只需使用printf.

每个人都认为他们关心表现,但没有人愿意测量它.我的答案是,无论你是否使用printf或I/O都是瓶颈iostream.我认为从快速查看汇编(使用编译器选项使用clang 编译)printf 可能会更快-O3.假设我的错误示​​例,printf示例执行的调用比cout示例少.这是int mainprintf:

main:                                   @ @main
@ BB#0:
        push    {lr}
        ldr     r0, .LCPI0_0
        ldr     r2, .LCPI0_1
        mov     r1, #2
        bl      printf
        mov     r0, #0
        pop     {lr}
        mov     pc, lr
        .align  2
@ BB#1:
Run Code Online (Sandbox Code Playgroud)

您可以很容易地注意到两个字符串和2(数字)被作为printf参数推送.就是这样; 没有别的.为了比较,这被iostream编译为汇编.不,没有内联; 每一次operator <<调用都意味着使用另一组参数进行另一次调用.

main:                                   @ @main
@ BB#0:
        push    {r4, r5, lr}
        ldr     r4, .LCPI0_0
        ldr     r1, .LCPI0_1
        mov     r2, #6
        mov     r3, #0
        mov     r0, r4
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        mov     r0, r4
        mov     r1, #2
        bl      _ZNSolsEi
        ldr     r1, .LCPI0_2
        mov     r2, #2
        mov     r3, #0
        mov     r4, r0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r1, .LCPI0_3
        mov     r0, r4
        mov     r2, #14
        mov     r3, #0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r1, .LCPI0_4
        mov     r0, r4
        mov     r2, #1
        mov     r3, #0
        bl      _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
        ldr     r0, [r4]
        sub     r0, r0, #24
        ldr     r0, [r0]
        add     r0, r0, r4
        ldr     r5, [r0, #240]
        cmp     r5, #0
        beq     .LBB0_5
@ BB#1:                                 @ %_ZSt13__check_facetISt5ctypeIcEERKT_PS3_.exit
        ldrb    r0, [r5, #28]
        cmp     r0, #0
        beq     .LBB0_3
@ BB#2:
        ldrb    r0, [r5, #39]
        b       .LBB0_4
.LBB0_3:
        mov     r0, r5
        bl      _ZNKSt5ctypeIcE13_M_widen_initEv
        ldr     r0, [r5]
        mov     r1, #10
        ldr     r2, [r0, #24]
        mov     r0, r5
        mov     lr, pc
        mov     pc, r2
.LBB0_4:                                @ %_ZNKSt5ctypeIcE5widenEc.exit
        lsl     r0, r0, #24
        asr     r1, r0, #24
        mov     r0, r4
        bl      _ZNSo3putEc
        bl      _ZNSo5flushEv
        mov     r0, #0
        pop     {r4, r5, lr}
        mov     pc, lr
.LBB0_5:
        bl      _ZSt16__throw_bad_castv
        .align  2
@ BB#6:
Run Code Online (Sandbox Code Playgroud)

但是,说实话,这意味着什么,因为I/O无论如何都是瓶颈.我只是想表明它iostream不是更快,因为它是"类型安全".大多数C实现printf使用计算goto 实现格式,因此printf即使没有编译器意识到它也是如此printf(不是它们不是 - 某些编译器printf在某些情况下可以优化- 常量字符串结尾\n通常被优化为puts) .

遗产

我不知道你为什么要继承ostream,但我不在乎.它也有可能FILE.

class MyFile : public FILE {}
Run Code Online (Sandbox Code Playgroud)

类型安全

确实,可变长度参数列表没有安全性,但这并不重要,因为printf如果启用警告,流行的C编译器可以检测格式字符串的问题.事实上,Clang可以在不启用警告的情况下做到这一点.

$ cat safety.c

#include <stdio.h>

int main(void) {
    printf("String: %s\n", 42);
    return 0;
}

$ clang safety.c

safety.c:4:28: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
    printf("String: %s\n", 42);
                    ~~     ^~
                    %d
1 warning generated.
$ gcc -Wall safety.c
safety.c: In function ‘main’:
safety.c:4:5: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]
     printf("String: %s\n", 42);
     ^
Run Code Online (Sandbox Code Playgroud)

  • 你说I/O无论如何都是瓶颈.显然你从未[测试过这个假设.](http://stackoverflow.com/questions/4340396/does-the-c-standard-mandate-poor-performance-for-iostreams-or-am-i-just-deali)我引用自己的话说:"另一方面,iostreams版本的速度为75.3 MB/s,无法快速缓冲数据以跟上硬盘的速度.这很糟糕,甚至还没有做任何真正的工作.我不知道.当我说我的I/O库应该能够使我的磁盘控制器饱和时,我认为我的期望值太高了. (15认同)
  • 我喜欢这个答案的很多东西,但也许我最喜欢的部分是"每个人都认为他们关心性能,但没有人愿意测量它." (15认同)
  • 你的表现论证没有任何意义.程序中的更多程序集并不意味着程序会变慢,因为你不会考虑所有生成printf函数的代码,这是很多代码.在我看来,可以使用<<运算符比printf更好地优化cout,因为编译器可以更好地理解变量和格式. (9认同)
  • 在使用cout时,这里没有人提到并行环境中的问题. (3认同)
  • @BenVoigt:我承认,我会尽可能避免使用C ++。我尝试了很多次使用,但是比起我以前使用的其他编程语言,它更烦人且更难维护。这是我避免使用C ++的另一个原因-甚至还不够快(甚至没有iostream-整个C ++库在大多数实现中都很慢,也许是`std :: sort`例外),与之相比,它出奇的快qsort(2次),代价是可执行文件的大小。 (2认同)
  • @NicholasHamilton:只是不要在多个线程上打印。这适用于 `std::cout` 和 `printf`。如果您对非功能性代码(有副作用)使用多线程,您将处理其他更严重的问题。锁没有帮助,它们只会使代码比单线程版本的代码慢。不要在没有测量的情况下使用线程来提高性能。 (2认同)
  • @ Ignas2526:虽然如此,但值得注意的是C和C++标准库的实现可能缓存在内存中.但是,用C或C++实现的庞大程序可能不是.二进制大小[对性能有影响,尤其是启动性能](https://www.webkit.org/blog/2826/unusual-speed-boost-size-matters/).另外,`operator <<`是作为单独的函数调用实现的,编译器必须知道`operator <<对文件句柄对象做什么来优化任何东西,这很棘手.这就像编译器的写(写(写(写(输出,"0x"),十六进制),数字),endl)`. (2认同)
  • `c_str` 是 `std::string` 获取 C 字符串的方法,而不是 `to_c`。除此之外,这是一个踢屁股的答案。 (2认同)
  • cout 的内容几乎总是比较烦人,这很可能是我唯一关心的事情。如果我想混合大量变量,我需要大量的`&lt;&lt;`和大量的换行符。流语法也很奇怪;我发现除了 cout 之外,它几乎没有在任何地方使用过。不,谢谢。 (2认同)

Mik*_*age 198

C++ FAQ:

[15.1]为什么要使用<iostream> 而不是传统的<cstdio>

提高类型安全性,减少错误,允许可扩展性并提供可继承性.

printf()可以说它没有被破坏,scanf()尽管容易出错,但它可能是适合居住的,但两者在C++ I/O方面都有限.C++ I/O(使用<<>>)相对于C(使用printf()scanf()):

  • 更类型安全:有了<iostream>I/O的对象类型,编译器静态地知道.相反,<cstdio>使用"%"字段动态地计算出类型.
  • 不易出错:有了<iostream>,没有多余的"%"令牌必须与I/O的实际对象保持一致.删除冗余会消除一类错误.
  • 可扩展:C++ <iostream>机制允许新的用户定义类型进行I/O而不破坏现有代码.想象一下,如果每个人都同时添加新的不兼容的"%"字段,那么混乱 printf()scanf()?!
  • 可继承:C++ <iostream>机制是由真实的类构建的,例如std::ostreamstd::istream.与<cstdio>s 不同FILE*,这些是真正的类,因此是可继承的.这意味着你可以拥有其他用户定义的东西,它们看起来像溪流一样,但它可以做你想要的任何奇怪和奇妙的事情.您可以自动使用由您甚至不知道的用户编写的数以万计的I/O代码行,并且他们不需要了解您的"扩展流"类.

另一方面,printf明显更快,这可能有理由使用它优先cout非常具体和有限的情况.始终首先介绍.(例如,参见http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)

  • @MaximYegorushkin:标准`printf`没有这样的能力.非可移植库机制几乎与iostream的完全标准化可扩展性处于同一水平. (4认同)
  • 还有Boost.Format,想知道两者如何比较. (3认同)
  • @Marcelo可能因为这是一个很好的总结,引用了所有内容.格式化...是的,那很糟糕.我应该自己解决这个问题,但似乎其他人(包括你自己)会照顾它,当然,这比抱怨更具建设性. (3认同)
  • "另一方面,printf明显更快"printf也更清洁,更容易使用,这就是为什么我尽可能避免cout. (3认同)
  • 另一方面,还有 FastFormat 库 (http://www.fastformat.org),它同时提供类型安全性、表现力和性能。(我还没试过……) (2认同)
  • 最近`printf()`也应该是可扩展的.请参阅http://udrepper.livejournal.com/20948.html上的"printf hooks" (2认同)

Tho*_*mas 41

人们经常声称printf速度要快得多.这在很大程度上是一个神话.我刚测试了它,结果如下:

cout with only endl                     1461.310252 ms
cout with only '\n'                      343.080217 ms
printf with only '\n'                     90.295948 ms
cout with string constant and endl      1892.975381 ms
cout with string constant and '\n'       416.123446 ms
printf with string constant and '\n'     472.073070 ms
cout with some stuff and endl           3496.489748 ms
cout with some stuff and '\n'           2638.272046 ms
printf with some stuff and '\n'         2520.318314 ms
Run Code Online (Sandbox Code Playgroud)

结论:如果您只想换行,请使用printf; 否则,cout几乎一样快,甚至更快.更多细节可以在我的博客上找到.

要清楚,我并不想说iostreams总是好于printf; 我只是想说你应该根据真实数据作出明智的决定,而不是基于一些常见的误导性假设的疯狂猜测.

更新:这是我用于测试的完整代码.编译时g++没有任何其他选项(除了-lrt时间).

#include <stdio.h>
#include <iostream>
#include <ctime>

class TimedSection {
    char const *d_name;
    timespec d_start;
    public:
        TimedSection(char const *name) :
            d_name(name)
        {
            clock_gettime(CLOCK_REALTIME, &d_start);
        }
        ~TimedSection() {
            timespec end;
            clock_gettime(CLOCK_REALTIME, &end);
            double duration = 1e3 * (end.tv_sec - d_start.tv_sec) +
                              1e-6 * (end.tv_nsec - d_start.tv_nsec);
            std::cerr << d_name << '\t' << std::fixed << duration << " ms\n"; 
        }
};

int main() {
    const int iters = 10000000;
    char const *text = "01234567890123456789";
    {
        TimedSection s("cout with only endl");
        for (int i = 0; i < iters; ++i)
            std::cout << std::endl;
    }
    {
        TimedSection s("cout with only '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << '\n';
    }
    {
        TimedSection s("printf with only '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("\n");
    }
    {
        TimedSection s("cout with string constant and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789" << std::endl;
    }
    {
        TimedSection s("cout with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << "01234567890123456789\n";
    }
    {
        TimedSection s("printf with string constant and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("01234567890123456789\n");
    }
    {
        TimedSection s("cout with some stuff and endl");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << std::endl;
    }
    {
        TimedSection s("cout with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            std::cout << text << "01234567890123456789" << i << '\n';
    }
    {
        TimedSection s("printf with some stuff and '\\n'");
        for (int i = 0; i < iters; ++i)
            printf("%s01234567890123456789%i\n", text, i);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • `printf()`和`std :: ostream`之间的重要区别在于**前者在一次调用中输出所有参数**而`std :: ostream`对每个`<<`都会产生一个单独的调用.测试只输出一个参数和一个换行符,这就是为什么你看不出差异的原因. (29认同)
  • 编译器应该能够内联这些调用.另外,`printf`可能会为各种格式化说明符的辅助函数进行大量调用......或者它是一个可怕的单片函数.而且,由于内联,它根本不应该对速度产生影响. (10认同)
  • 在你的分数printf beats轻松cout(多数情况下).我想知道为什么你建议在使用cout时使用cout.虽然我同意perf在现实案例中并没有太大的不同. (5认同)
  • @ mishal153:我只是想说性能并没有太大的不同,所以常见的"永远不要使用cout,因为它的速度很慢"的建议是愚蠢的.请注意,cout具有类型安全性的明显优势,并且通常也具有可读性.(使用iostreams进行浮点格式化非常糟糕...) (3认同)
  • 你计时终端了.使用`sprintf`或`fprintf`和`stringstream`或`fstream`. (3认同)
  • @Thomas:"复制代码"不一定会重现你的结果.为了了解您的基准测试是否有效,我们需要能够查看代码.但是,谢谢你添加代码.:) (2认同)
  • @Thomas:它不能。我使用“-S -O3”选项检查了最新版本的 gcc 和 Clang。 (2认同)

Kyl*_*ndo 38

引述:

在高级方面,主要区别在于类型安全(cstdio没有它),性能(大多数iostream实现比cstdio慢)和可扩展性(iostream允许自定义输出目标和用户定义类型的无缝输出).


Mar*_*tos 30

一个是打印到stdout的函数.另一个是一个对象,它提供了几个成员函数以及operator<<该打印到stdout的重载.我可以列举更多的差异,但我不确定你追求的是什么.


mis*_*153 12

对我来说,真正的差异让我选择'cout'而不是'printf'是:

1)<<运算符可以为我的类重载.

2)cout的输出流可以很容易地更改为文件:(:copy paste :)

#include <iostream>
#include <fstream>
using namespace std;

int main ()
{
    cout << "This is sent to prompt" << endl;
    ofstream file;
    file.open ("test.txt");
    streambuf* sbuf = cout.rdbuf();
    cout.rdbuf(file.rdbuf());
    cout << "This is sent to file" << endl;
    cout.rdbuf(sbuf);
    cout << "This is also sent to prompt" << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

3)我发现cout更具可读性,特别是当我们有很多参数时.

一个问题cout的是格式选项.格式化数据(精度,对齐等)printf更容易.

  • 您也可以轻松地将“printf”更改为文件,只需将其替换为“fprintf”... (3认同)

小智 7

我不是程序员,但我一直是人因工程师。我觉得编程语言应该易于学习、理解和使用,这就要求它具有简单且一致的语言结构。尽管所有语言都是符号性的,因此其核心是任意的,但有一些约定,遵循这些约定可以使语言更容易学习和使用。

C++ 和其他语言中有大量函数被编写为函数(参数),这种语法最初用于前计算机时代数学中的函数关系。printf()遵循此语法,如果 C++ 的编写者想要创建任何逻辑上不同的方法来读取和写入文件,他们可以简单地使用类似的语法创建不同的函数。

在Python 中,我们当然可以使用相当标准的object.method语法进行打印,即variablename.print,因为变量是对象,但在C++ 中它们不是。

我不喜欢 cout 语法,因为 << 运算符不遵循任何规则。它是一个方法或函数,即它接受一个参数并对其执行某些操作。然而,它的编写方式就好像它是一个数学比较运算符。从人为因素的角度来看,这是一个糟糕的方法。

  • &lt;&lt; 不是比较运算符。它是位左移运算符。它需要两个整数参数(最初)。10 &lt;&lt; 1;这会将 10 个位向左移动 1 次;返回数字 20。但是 Cpp 允许运算符重载并决定将此运算符用于 stdout。cpp的比较运算符有小于(&lt;) 大于(&gt;) 小于等于(&lt;=) 大于等于(&gt;=) 等于(==) 不等于(!=)。这只是 cpp 特定的语法,而不是直接来自数学。注意赋值运算符是(=)。 (2认同)

Dan*_*iel 5

对于原语,您使用哪一种可能并不重要。我说当你想输出复杂的对象时它会有用处。

例如,如果你有一个班级,

#include <iostream>
#include <cstdlib>

using namespace std;

class Something
{
public:
        Something(int x, int y, int z) : a(x), b(y), c(z) { }
        int a;
        int b;
        int c;

        friend ostream& operator<<(ostream&, const Something&);
};

ostream& operator<<(ostream& o, const Something& s)
{
        o << s.a << ", " << s.b << ", " << s.c;
        return o;
}

int main(void)
{
        Something s(3, 2, 1);

        // output with printf
        printf("%i, %i, %i\n", s.a, s.b, s.c);

        // output with cout
        cout << s << endl;

        return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在上面的内容可能看起来不是那么好,但是假设您必须在代码的多个位置输出它。不仅如此,假设您添加了一个字段“int d”。使用 cout,您只需更改一次。但是,使用 printf 时,您可能需要在很多地方更改它,不仅如此,您还必须提醒自己要输出哪些地方。

话虽如此,使用 cout,您可以减少大量用于维护代码的时间,不仅如此,如果您在新应用程序中重新使用对象“Something”,您就不必担心输出。


Bil*_*man 5

这里没有另外提到的两点我觉得很重要:

1)cout如果您尚未使用STL,则需要携带大量行李.它为您的目标文件增加了两倍的代码printf.这也是如此string,这是我倾向于使用自己的字符串库的主要原因.

2)cout使用重载<<运算符,我觉得很不幸.如果您还将<<操作符用于其预期目的(向左移动),则会增加混淆.我个人不喜欢将操作员重载到与其预期用途相关的目的.

底线:如果我已经在使用STL,我将使用cout(和string).否则,我倾向于避免它.