C ++中的'printf'与'cout'


Answers:


332

令我惊讶的std::coutprintf,即使这个问题只是要求有所不同,这个问题中的每个人都认为它比更好。现在,有一个区别std::cout-C ++和printfC(不过,您可以在C ++中使用它,就像C中的几乎所有其他东西一样)。现在,我将在这里诚实。既printfstd::cout有自己的优势。

真正的差异

可扩展性

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

与不同printfstd::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;

尽管这看起来并不太疯狂(只是更长的两倍),但是当您实际格式化参数而不是仅打印参数时,事情变得更加疯狂。例如,打印类似的东西简直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;

翻译

这就是printf谎言的真正优势所在。该printf格式字符串是很好...字符串。与operator <<滥用相比,翻译起来真的很容易iostream。假设该gettext()函数进行了翻译,并且您想显示Error 2: File not found.,则获取先前显示的格式字符串的翻译代码如下所示:

printf(gettext("Error %d: %s.\n"), id, errors[id]);

现在,假设我们转换为Fictionish,错误号在描述之后。转换后的字符串看起来像%2$s oru %1$d.\n。现在,如何用C ++做到这一点?好吧,我不知道。我猜您可能会伪造可传递给或出于翻译目的之类的iostream构造。当然,printfgettext$它不是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字节。在某些情况下,可以将%cwith '\0'用作参数,尽管这显然是黑客。

没人关心的差异

性能

更新:事实证明,它iostream是如此之慢,以至于通常比硬盘驱动器慢(如果将程序重定向到文件)。stdio如果需要输出大量数据,则禁用与的同步可能会有所帮助。如果性能是真正的问题(与向STDOUT写几行相对),请使用printf

每个人都认为他们关心性能,但是没有人去评估它。我的回答是,无论您使用printf还是,I / O都是瓶颈iostream。我认为,printf 可以从快速查看组件(使用铿锵编译更快-O3编译器选项)。假设我的错误示​​例,printfexample的调用方式少于该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:

您可以很容易地注意到,两个字符串和2(number)被作为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:

但是,说实话,这没有任何意义,因为I / O仍然是瓶颈。我只是想表明这样iostream做并不快,因为它是“类型安全的”。大多数C实现printf使用计算后的goto 实现格式,因此printf即使没有编译器意识到,其速度也要尽可能快printf(不是,它们不是-有些编译器可以printf在某些情况下进行优化-以结尾的常量字符串\n通常会被优化为puts) 。

遗产

我不知道您为什么要继承ostream,但是我不在乎。也有可能FILE

class MyFile : public FILE {}

类型安全

诚然,可变长度参数列表没有安全性,但这无关紧要,因为流行的C编译器可以printf在启用警告的情况下检测格式字符串问题。实际上,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);
     ^

18
您说I / O仍然是瓶颈。显然,您从未检验过该假设。 我引用自己的话:“另一方面,iostream版本的速度为75.3 MB / s,无法足够快地缓冲数据以跟上硬盘的速度。这很糟糕,甚至还没有做任何实际工作。我不知道当我说我的I / O库应该能够使我的磁盘控制器饱和时,我认为我并没有抱太大的期望。”
Ben Voigt 2014年

4
@BenVoigt:我承认,我会尽可能避免使用C ++。我尝试了很多次使用,但是比起我以前使用的其他编程语言,它更烦人且更难维护。这是我避免使用C ++的另一个原因-甚至还不够快(甚至不是iostream-整个C ++库在大多数实现中都很慢,也许是例外std::sort,它在某种程度上比qsort(2倍)快得令人惊讶可执行文件大小的费用)。
Konrad Borowski14年

3
在这里,没有人提到使用cout时在并行环境中遇到的问题。
尼古拉斯·汉密尔顿

9
您的性能论点毫无意义。程序中更多的汇编程序并不意味着程序会变慢,因为您没有考虑使printf函数起作用的所有代码,这是很多代码。我认为,用<<运算符优化cout可能比printf更好,因为编译器可以更好地理解变量和格式。
Ignas2526

18
我很喜欢这个答案,但也许我最喜欢的部分是“每个人都认为他们在乎绩效,但是没人愿意去衡量它。”
Kyle Strand

203

C ++常见问题解答

[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::ostream和的 真实类构建的std::istream。不同<cstdio>FILE*,这些都是真正的类,因此可继承。这意味着您可以拥有其他用户定义的外观和行为类似于流的东西,却可以完成您想要的任何奇妙而奇妙的事情。您会自动使用甚至不认识的用户编写的不计其数的I / O代码行,他们也不需要了解您的“扩展流”类。

另一方面,printf它明显更快,这可能证明cout非常特定和有限的情况下优先使用它是合理的。始终先分析。(例如,请参阅http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)


2
另一方面,还有FastFormat库(fastformat.org),可同时提供类型安全性,表现力和性能。(不是我还没有尝试过...)
xtofl 2010年

3
@Marcelo可能是因为它是一个很好的摘要,并引用了所有内容。格式...是的,这很糟糕。我本来应该确定自己的身份,但是似乎其他人(包括您自己)已经照顾了它,这当然比仅仅抱怨还更具建设性。
Mikeage

2
到最近printf()也应该是可扩展的。参见udrepper.livejournal.com/20948.html上的
Maxim Egorushkin 2011年

4
@MaximYegorushkin:Standard printf没有这种能力。不可移植的库机制几乎与完全标准化的iostream可扩展性处于同一级别。
Ben Voigt 2014年

4
“另一方面,printf明显要快得多”,printf更加清洁且易于使用,这就是为什么我尽可能避免使用cout的原因。
FluorescentGreen5

43

人们经常声称这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

结论:如果只需要换行符,请使用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);
    }
}

5
在您的得分中,printf轻而易举地击败了cout(大多数情况下)。我不知道为什么您推荐在性能方面使用cout。虽然我同意PERF是不是在现实情况下,太不一样了..
mishal153

3
@ mishal153:我只是想说一下性能并没有太大不同,因此,“从不使用cout,因为它速度缓慢”这一常见的建议是愚蠢的。请注意,cout具有类型安全性的明显优势,并且通常还具有可读性。(使用iostream进行浮点格式化是可怕的……)
托马斯(Thomas

35
printf()和之间的重要区别std::ostream是,前者在一个调用中输出所有参数,std::ostream每个调用则产生一个单独的调用<<。该测试仅输出一个参数和换行符,这就是为什么看不到差异的原因。
Maxim Egorushkin 2011年

12
编译器应该能够内联这些调用。同样,printf可能会在幕后大量调用各种格式说明符的辅助函数……或者这是一个巨大的整体函数。同样,由于内联,它根本不应该改变速度。
Thomas

4
您为终端计时。使用sprintffprintfstringstreamfstream
Ben Voigt 2014年

41

引用

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


尤其是在使用POSIX的Unix上,您永远都不知道typedef真正具有什么大小,因此您需要大量强制转换,或者作为99%的程序,您只需要使用%d冒险。%z附带C99之前花了很长时间。但是对于time_t / off_t,继续寻找正确格式的指令。
Lothar'2

30

一种是打印到标准输出的功能。另一个是一个对象,它提供一些成员函数以及operator<<该打印输出到stdout的重载。我可以列举更多的区别,但是我不确定您追求的是什么。


12

对我来说,使我喜欢“ cout”而不是“ printf”的真正区别是:

1)<<操作符可以在我的课程中重载。

2)cout的输出流可以轻松更改为文件:(:复制粘贴:)

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

3)我发现cout更具可读性,尤其是当我们有很多参数时。

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


1
这真好。我怎么知道在某些外部库线程中没有人以此方式修改全局cout?
vp_arth's

1
您可以轻松更改printf到一个文件,以及通过替换它fprintf...
CoffeeTableEspresso

5

我发现这里没有另外提及的两点很重要:

1)cout如果您尚未使用STL,则会带来很多负担。它为您的目标文件添加了两倍多的代码printf。对于string,也是如此,这是我倾向于使用自己的字符串库的主要原因。

2)cout使用重载<<运算符,我觉得很不幸。如果您还将<<操作符用于其预期目的(向左移动),这可能会增加混乱。我个人不希望过载运算符以达到与其预期用途相关的目的。

底线:如果已经在使用STL,我将使用cout(和string)。否则,我倾向于避免这种情况。


4

对于基元,使用哪一个基本无关紧要。我说它有用的地方是当您要输出复杂的对象时。

例如,如果您有一堂课,

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

现在,上面的内容似乎并不那么好,但是让我们假设您必须在代码中的多个位置输出它。不仅如此,假设您添加了一个字段“ int d”。使用cout,您只需一次更改即可。但是,使用printf,您可能不得不在很多地方进行更改,不仅如此,还必须提醒自己要输出的内容。

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


另外,关于性能的事情,我想说如果您的应用程序是为提高性能而开发的,那么您根本不应该输出任何东西。std的任何输出都相当昂贵且缓慢。我说您应该避免使用它,而仅在绝对必要时才输出。
丹尼尔(Daniel)2010年

请记住,您的班级可能有私人成员,您无法从外部轻松访问。使用输出运算符,您只有一个位置需要与您的班级成为朋友,并且现在您可以将其输出到任何地方,即使您不知道的代码也是如此。
hochl

2

当然,可以编写一些更好的东西来保持维护:

#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&);

        void print() const { printf("%i, %i, %i\n", a, b, c); }
};

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
    s.print(); // Simple as well, isn't it?

    // Output with cout
    cout << s << endl;

    return 0;
}

如果有人想进行更多测试(Visual Studio 2008,可执行文件的发行版),则对cout vs. printf进行了扩展测试,添加了“ double”测试:

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

class TimedSection {
    char const *d_name;
    //timespec d_start;
    clock_t d_start;

    public:
        TimedSection(char const *name) :
            d_name(name)
        {
            //clock_gettime(CLOCK_REALTIME, &d_start);
            d_start = clock();
        }
        ~TimedSection() {
            clock_t end;
            //clock_gettime(CLOCK_REALTIME, &end);
            end = clock();
            double duration = /*1e3 * (end.tv_sec - d_start.tv_sec) +
                              1e-6 * (end.tv_nsec - d_start.tv_nsec);
                              */
                              (double) (end - d_start) / CLOCKS_PER_SEC;

            std::cerr << d_name << '\t' << std::fixed << duration * 1000.0 << " ms\n";
        }
};


int main() {
    const int iters = 1000000;
    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);
    }
    {
        TimedSection s("cout with formatted double (width & precision once)");
        std::cout << std::fixed << std::scientific << std::right << std::showpoint;
        std::cout.width(8);
        for (int i = 0; i < iters; ++i)
            std::cout << text << 8.315 << i << '\n';
    }
    {
        TimedSection s("cout with formatted double (width & precision on each call)");
        std::cout << std::fixed << std::scientific << std::right << std::showpoint;

        for (int i = 0; i < iters; ++i)
            { std::cout.width(8);
              std::cout.precision(3);
              std::cout << text << 8.315 << i << '\n';
            }
    }
    {
        TimedSection s("printf with formatted double");
        for (int i = 0; i < iters; ++i)
            printf("%8.3f%i\n", 8.315, i);
    }
}

结果是:

cout with only endl    6453.000000 ms
cout with only '\n'    125.000000 ms
printf with only '\n'    156.000000 ms
cout with string constant and endl    6937.000000 ms
cout with string constant and '\n'    1391.000000 ms
printf with string constant and '\n'    3391.000000 ms
cout with some stuff and endl    9672.000000 ms
cout with some stuff and '\n'    7296.000000 ms
printf with some stuff and '\n'    12235.000000 ms
cout with formatted double (width & precision once)    7906.000000 ms
cout with formatted double (width & precision on each call)    9141.000000 ms
printf with formatted double    3312.000000 ms

哇,为什么endl效率这么低'\n'
尼古拉斯·汉密尔顿

1
我相信这是因为endl刷新缓冲区,而\n不是,尽管我不确定这是否一定是刷新的原因。
Caleb Xu

这不是解决问题的方法,更像是对DanielThomas的答案。
Fabio说恢复莫妮卡

2

我想指出的是,如果您想使用C ++中的线程进行操作,则cout可以得到一些有趣的结果。

考虑以下代码:

#include <string>
#include <iostream>
#include <thread>

using namespace std;

void task(int taskNum, string msg) {
    for (int i = 0; i < 5; ++i) {
        cout << "#" << taskNum << ": " << msg << endl;
    }
}

int main() {
    thread t1(task, 1, "AAA");
    thread t2(task, 2, "BBB");
    t1.join();
    t2.join();
    return 0;
}

// g++ ./thread.cpp -o thread.out -ansi -pedantic -pthread -std=c++0x

现在,输出全部改组。它也会产生不同的结果,请尝试执行几次:

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

##12::  ABABAB

您可以使用printf使其正确,也可以使用mutex

#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB
#1: AAA
#2: BBB

玩得开心!


2
wtf thread不会使输出变得疯狂。我只是转载,发现两者xyzABC输出。有没有重整的B / W ABCABABAB
Abhinav Gauniyal,2016年

1
我不知道cout线程如何工作,但是我可以肯定地知道所显示的代码不是用来获取这些输出的代码。您的代码"ABC"为线程1和"xyz"线程2 传递了字符串,但是输出显示为AAABBB。请修复它,因为现在令人困惑。
Fabio说恢复莫妮卡

1
cout<< "Hello";
printf("%s", "Hello"); 

两者都用于打印值。它们具有完全不同的语法。C ++兼有,C只有printf。


19
... 什么?你有事吗
xtofl 2010年

1
解决了该问题。-1,因为它需要修复,而答案仍然有很多不足之处。
Yacoby

3
函数名称已颠倒:cout与printf语法一起使用,而printf与cout语法一起使用。甚至不应该被接受!
Mahmoud Al-Qudsi'5

2
而cout的主要缺点是它使用了operator <<,这很冗长,丑陋,并且可以说是滥用操作员。:)
jalf

8
尽管这肯定不是最佳答案,但我不理解仅仅因为将scatman作为最佳答案而受到惩罚。xbit回答IMO的方法较差,但得票率为-1。我并不是说xbit应该不再被否决,但是我不认为因为OP的错误而不再是臭名昭著的臭名昭著的人
杰西(Jesse)2010年

1

我想说缺乏可扩展性printf并不是完全正确的:
在C语言中,它是正确的。但是在C语言中,没有真正的类。
在C ++中,可以重载强制转换运算符,因此,可以重载char*运算符并printf像这样使用:

Foo bar;
...;
printf("%s",bar);

如果Foo重载了好的运算符,则可能是可行的。或者,如果您做了一个好方法。简而言之,对我来说printf可扩展cout

我可以从C ++流中看到的技术论点(通常……不仅是cout。)是:

  • 类型安全。(顺便说一句,如果我想打印一张,我会'\n'使用putchar('\n')...我不会使用核弹来杀死昆虫。)

  • 简单易学。(无需学习“复杂”参数,只需使用<<>>操作即可)

  • 本机工作std::string(因为printfstd::string::c_str(),但有scanf?)

对于printf我来说:

  • 更简单或更复杂的格式(至少在书写字符方面)。对我而言,可读性要高得多(我猜是口味的问题)。

  • 更好地控制函数的功能(返回写入的字符数和%n格式化程序:“什么都没有打印。参数必须是一个指向带符号的int的指针,该指针存储了到目前为止写入的字符数。”(摘自printf -C ++参考

  • 更好的调试可能性。出于与上一个参数相同的原因。

我个人偏爱使用printf(和scanf)函数,这主要是因为我喜欢短行,并且因为我认为打印文字方面的类型问题真的很难避免。我不赞成使用C样式的功能,这std::string是唯一不受支持的功能。我们必须char*先将其交给printfstd::string::c_str()如果要阅读,但要如何书写,请加上)。


3
编译器没有varargs函数的类型信息,因此它不会转换实际参数(默认参数提升除外,例如标准整数提升)。参见5.2.2p7。char*不会使用用户定义的转换。
Ben Voigt 2012年

即使这行得通,也不会成为sprintf可扩展性的一个例子,它只是一个巧妙的技巧,可以使sprintf达到预期的效果,而忽略了一些严重的问题,例如char*寿命,寿命长短以及用户定义的危险隐式强制转换。
Marcelo Cantos 2015年

1

更多区别:“ printf”返回一个整数值(等于打印的字符数),“ cout”不返回任何值

和。

cout << "y = " << 7; 不是原子的。

printf("%s = %d", "y", 7); 是原子的。

cout执行类型检查,printf不执行。

没有iostream等效于 "% d"


3
cout不返回任何东西,因为它是一个对象,而不是一个函数。operator<<确实会返回某些内容(通常是其左操作数,但是如果有错误,则返回假值)。在什么意义上,printf“原子”是什么?
基思·汤普森

9
就像原子弹。printf("%s\n",7);
无声的噪音,

@artlessnoise等待为什么分段错误?%s是?
Abhinav Gauniyal '16

1
这就是“原子弹”声明的重点。一printf %s的参数必须有一个有效的指针为空终止字符串。内存范围“ 7”(指针)通常无效;细分错误可能是幸运的。在某些系统上,“ 7”可能会在控制台上打印大量垃圾,您必须在程序停止前检查它一天。换句话说,这是一件坏事printf。静态分析工具可以捕获许多此类问题。
artless噪音

尽管从技术上讲printf不会进行类型检查,但我从未使用过不会向我警告类型错误的编译器printf...
CoffeeTableEspresso

1

TL; DR:在信任在线随机注释(包括此注释)之前,请始终对生成的机器代码大小性能可读性编码时间进行自己的研究。

我不是专家。我只是偶然听到两个同事谈论由于性能问题应如何避免在嵌入式系统中使用C ++。好吧,很有趣,我根据实际项目任务做了一个基准测试。

在上述任务中,我们必须将一些配置写入RAM。就像是:

咖啡=热
糖=无
牛奶=母乳
= AA:BB:CC:DD:EE:FF

这是我的基准程序(是的,我知道OP询问过关于printf()而不是fprintf()的问题。尝试抓住本质,顺便说一下,OP的链接始终指向fprintf()。)

C程序:

char coffee[10], sugar[10], milk[10];
unsigned char mac[6];

/* Initialize those things here. */

FILE * f = fopen("a.txt", "wt");

fprintf(f, "coffee=%s\nsugar=%s\nmilk=%s\nmac=%02X:%02X:%02X:%02X:%02X:%02X\n", coffee, sugar, milk, mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]);

fclose(f);

C ++程序:

//Everything else is identical except:

std::ofstream f("a.txt", std::ios::out);

f << "coffee=" << coffee << "\n";
f << "sugar=" << sugar << "\n";
f << "milk=" << milk << "\n";
f << "mac=" << (int)mac[0] << ":"
    << (int)mac[1] << ":"
    << (int)mac[2] << ":"
    << (int)mac[3] << ":"
    << (int)mac[4] << ":"
    << (int)mac[5] << endl;
f.close();

在将它们都循环十万次之前,我已尽力抛光它们。结果如下:

C程序:

real    0m 8.01s
user    0m 2.37s
sys     0m 5.58s

C ++程序:

real    0m 6.07s
user    0m 3.18s
sys     0m 2.84s

目标文件大小:

C   - 2,092 bytes
C++ - 3,272 bytes

结论:在我特定的平台上,使用特定的处理器,运行特定版本的Linux内核,以运行用特定版本的GCC编译的程序,以完成特定任务。 C ++方法更合适,因为它运行速度显着提高并且可读性更好。另一方面,我认为C占用空间很小,因为程序大小与我们无关,几乎没有任何意义。

记住,YMMV。


我不同意C ++在此示例中更具可读性,因为您的示例将多行代码打包到一个printf调用中。这自然比您编写C ++代码的方式可读性差,并且很少在C中完成,因为它难以阅读且难以维护。公平的比较会将C分散到单独的printfs中,一个用于到达行。
maharvey67 '18

1
@ maharvey67你说的是真的。但是,我在C语言中提供的示例考虑了性能。对fprintf的打包调用比同等的C ++慢了两秒钟。如果我要使C代码可读,那么它可能会更慢。免责声明:这是一年前的事,我记得我曾尽力完善C和C ++代码。我没有证据表明单独调用fprintf会比单个调用更快,但是我这样做的原因可能表明事实并非如此。
韦斯利

0

我不是程序员,但我曾是人为因素工程师。我认为编程语言应该易于学习,理解和使用,这要求它具有简单且一致的语言结构。尽管所有语言都是象征性的,因此从本质上讲是任意的,但仍有约定俗成,遵循这些约定可使该语言更易于学习和使用。

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

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

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


-1

printf是一个函数,cout而是一个变量。


6
我回滚是因为尽管答案本身可能是错误的,但它仍然是一个真实的答案。如果您(正确)认为答案是错误的,则有两个选择:1)添加评论或2)添加新答案(或同时执行两个操作)。请勿更改某人的答案,以使其说出的内容与作者的意图完全不同。
2014年

1
printf是一个函数,但是printf()一个函数调用=)
vp_arth

cout是一个对象,而不是变量。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.