'printf' vs. 'cout' in C++
C ++中
我很惊讶这个问题中的每个人都声称
真正的差异
可扩展性
与
但是,我怀疑很多人会想要扩展
句法
由于很容易注意到,
通常,这意味着C版本会更短,但在大多数情况下它并不重要。打印多个参数时,差异很明显。如果您必须编写类似
1 2 3 | printf("Error %d: %s. ", id, errors[id]); std::cout ;lt;;lt;"Error" ;lt;;lt; id ;lt;;lt;":" ;lt;;lt; errors[id] ;lt;;lt;"." ;lt;;lt; std::endl; |
虽然这看起来并不太疯狂(只是它的两倍),但实际设置参数格式时,事情变得更加疯狂,而不仅仅是打印它们。例如,打印像
1 2 3 | printf("0x%04x ", 0x424); std::cout ;lt;;lt;"0x" ;lt;;lt; std::hex ;lt;;lt; std::setfill('0') ;lt;;lt; std::setw(4) ;lt;;lt; 0x424 ;lt;;lt; std::endl; |
翻译
这就是
1 2 | printf(gettext("Error %d: %s. "), id, errors[id]); |
现在,让我们假设我们翻译为Fictionish,其中错误编号在描述之后。翻译后的字符串看起来像
不必记住/查找特定的整数类型语法
C有很多整数类型,C ++也是如此。
您无法打印NUL字节
因为
无人问津的差异
性能
更新:事实证明
每个人都认为他们关心表现,但没有人愿意测量它。我的答案是无论如何,无论你使用
1 2 3 4 5 6 7 8 9 10 11 12 | 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: |
您可以很容易地注意到两个字符串,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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无论如何都是瓶颈。 我只是想表明
遗产
我不知道你为什么要继承
1 | class MyFile : public FILE {} |
类型安全
确实,可变长度参数列表没有安全性,但这并不重要,因为如果启用警告,流行的C编译器可以检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $ cat safety.c #include ;lt;stdio.h;gt; int main(void) { printf("String: %s ", 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 ", 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 ", 42); ^ |
好。
从C ++ FAQ:
[15.1] Why should I use
instead of the traditional ? Increase type safety, reduce errors, allow extensibility, and provide inheritability.
printf() is arguably not broken, andscanf() is perhaps livable despite being error prone, however both are limited with respect to what C++ I/O can do. C++ I/O (using<< and>> ) is, relative to C (usingprintf() andscanf() ):
- More type-safe: With
, the type of object being I/O'd is
known statically by the compiler. In
contrast,uses"%" fields to
figure out the types dynamically.- Less error prone: With
, there are no redundant
"%" tokens that have to be consistent
with the actual objects being I/O'd.
Removing redundancy removes a class
of errors.- Extensible: The C++
mechanism allows new user-defined
types to be I/O'd without breaking
existing code. Imagine the chaos if
everyone was simultaneously adding
new incompatible"%" fields to
printf() andscanf() ?!- Inheritable: The C++
mechanism is built from real classes
such asstd::ostream and
std::istream . Unlike's
FILE* , these are real classes and
hence inheritable. This means you can
have other user-defined things that
look and act like streams, yet that
do whatever strange and wonderful
things you want. You automatically
get to use the zillions of lines of
I/O code written by users you don't
even know, and they don't need to
know about your"extended stream"
class.
另一方面,
人们经常声称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | cout with only endl 1461.310252 ms cout with only ' ' 343.080217 ms printf with only ' ' 90.295948 ms cout with string constant and endl 1892.975381 ms cout with string constant and ' ' 416.123446 ms printf with string constant and ' ' 472.073070 ms cout with some stuff and endl 3496.489748 ms cout with some stuff and ' ' 2638.272046 ms printf with some stuff and ' ' 2520.318314 ms |
结论:如果只需要换行符,请使用
要清楚,我并不是说
更新:这是我用于测试的完整代码。用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | #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 "; } }; 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 '\ '"); for (int i = 0; i < iters; ++i) std::cout << ' '; } { TimedSection s("printf with only '\ '"); for (int i = 0; i < iters; ++i) printf(" "); } { 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 '\ '"); for (int i = 0; i < iters; ++i) std::cout <<"01234567890123456789 "; } { TimedSection s("printf with string constant and '\ '"); for (int i = 0; i < iters; ++i) printf("01234567890123456789 "); } { 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 '\ '"); for (int i = 0; i < iters; ++i) std::cout << text <<"01234567890123456789" << i << ' '; } { TimedSection s("printf with some stuff and '\ '"); for (int i = 0; i < iters; ++i) printf("%s01234567890123456789%i ", text, i); } } |
我引述:
In high level terms, the main differences are type safety (cstdio
doesn't have it), performance (most iostreams implementations are
slower than the cstdio ones) and extensibility (iostreams allows
custom output targets and seamless output of user defined types).
一个是打印到stdout的函数。另一个是提供打印到stdout的
对我来说,真正的差异让我选择'cout'而不是'printf'是:
1)<<运算符可以为我的类重载。
2)cout的输出流可以很容易地更改为文件:
(: 复制粘贴 :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #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更具可读性,特别是当我们有很多参数时。
这里没有另外提到的两点我觉得很重要:
1)如果你还没有使用STL,
2)
底线:如果我已经在使用STL,我将使用
对于原语,使用哪一个并不重要。我说当你想要输出复杂的对象时它的用处。
例如,如果你有一个班级,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #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 ", s.a, s.b, s.c); // output with cout cout << s << endl; return 0; } |
现在上面看起来似乎并不那么好,但我们假设您必须在代码中的多个位置输出它。不仅如此,我们假设您添加了一个字段"int d"。有了cout,你只需要在一个地方改变它。但是,使用printf,你必须在很多地方改变它,不仅如此,你必须提醒自己输出哪些。
有了这个说法,使用cout,您可以减少维护代码所花费的大量时间,而不仅仅是如果您在新应用程序中重新使用"Something"对象,您实际上不必担心输出。
当然,你可以写一些更好的东西来保持维护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #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 ", 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; } |
并且对cout与printf进行了一些扩展测试,添加了一个'double'测试,如果有人想做更多的测试(VisualStudio2008,可执行文件的发布版本):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #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 "; } }; 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 '\ '"); for (int i = 0; i < iters; ++i) std::cout << ' '; } { TimedSection s("printf with only '\ '"); for (int i = 0; i < iters; ++i) printf(" "); } { 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 '\ '"); for (int i = 0; i < iters; ++i) std::cout <<"01234567890123456789 "; } { TimedSection s("printf with string constant and '\ '"); for (int i = 0; i < iters; ++i) printf("01234567890123456789 "); } { 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 '\ '"); for (int i = 0; i < iters; ++i) std::cout << text <<"01234567890123456789" << i << ' '; } { TimedSection s("printf with some stuff and '\ '"); for (int i = 0; i < iters; ++i) printf("%s01234567890123456789%i ", 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 << ' '; } { 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 << ' '; } } { TimedSection s("printf with formatted double"); for (int i = 0; i < iters; ++i) printf("%8.3f%i ", 8.315, i); } } |
结果是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | cout with only endl 6453.000000 ms cout with only ' ' 125.000000 ms printf with only ' ' 156.000000 ms cout with string constant and endl 6937.000000 ms cout with string constant and ' ' 1391.000000 ms printf with string constant and ' ' 3391.000000 ms cout with some stuff and endl 9672.000000 ms cout with some stuff and ' ' 7296.000000 ms printf with some stuff and ' ' 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 |
我想指出,如果你想在C ++中使用线程,如果使用
考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include ;lt;string;gt; #include ;lt;iostream;gt; #include ;lt;thread;gt; using namespace std; void task(int taskNum, string msg) { for (int i = 0; i ;lt; 5; ++i) { cout ;lt;;lt;"#" ;lt;;lt; taskNum ;lt;;lt;":" ;lt;;lt; msg ;lt;;lt; 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 |
现在,输出全部改组了。它也可以产生不同的结果,尝试多次执行:
1 2 3 4 5 6 7 8 9 | ##12:: ABABAB ##12:: ABABAB ##12:: ABABAB ##12:: ABABAB ##12:: ABABAB |
您可以使用
1 2 3 4 5 6 7 8 9 10 | #1: AAA #2: BBB #1: AAA #2: BBB #1: AAA #2: BBB #1: AAA #2: BBB #1: AAA #2: BBB |
玩得开心!
TL; DR:在信任在线随机评论(包括此评论)之前,始终对所生成的机器代码大小,性能,可读性和编码时间进行自己的研究。
我不是专家。我碰巧听到两位同事在谈论我们应该如何避免因为性能问题而在嵌入式系统中使用C ++。好吧,有趣的是,我根据真正的项目任务做了一个基准测试。
在上述任务中,我们必须将一些配置写入RAM。就像是:
coffee=hot
sugar=none
milk=breast
mac=AA:BB:CC:DD:EE:FF
这是我的基准程序(是的,我知道OP询问了printf(),而不是fprintf()。尝试捕获本质,顺便说一句,OP的链接指向fprintf()。)
C程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 sugar=%s milk=%s mac=%02X:%02X:%02X:%02X:%02X:%02X ", coffee, sugar, milk, mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]); fclose(f); |
C ++程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //Everything else is identical except: std::ofstream f("a.txt", std::ios::out); f ;lt;;lt;"coffee=" ;lt;;lt; coffee ;lt;;lt;" "; f ;lt;;lt;"sugar=" ;lt;;lt; sugar ;lt;;lt;" "; f ;lt;;lt;"milk=" ;lt;;lt; milk ;lt;;lt;" "; f ;lt;;lt;"mac=" ;lt;;lt; (int)mac[0] ;lt;;lt;":" ;lt;;lt; (int)mac[1] ;lt;;lt;":" ;lt;;lt; (int)mac[2] ;lt;;lt;":" ;lt;;lt; (int)mac[3] ;lt;;lt;":" ;lt;;lt; (int)mac[4] ;lt;;lt;":" ;lt;;lt; (int)mac[5] ;lt;;lt; endl; f.close(); |
在我将它们环绕100,000次之前,我尽力擦亮它们。结果如下:
C程序:
1 2 3 | real 0m 8.01s user 0m 2.37s sys 0m 5.58s |
C ++程序:
1 2 3 | real 0m 6.07s user 0m 3.18s sys 0m 2.84s |
对象文件大小:
1 2 | C - 2,092 bytes C++ - 3,272 bytes |
结论:在我非常具体的平台上,使用一个非常特定的处理器,运行一个非常特定的Linux内核版本,运行一个用特定版本的GCC编译的程序,为了完成一个非常具体的任务,我会说C ++方法更合适,因为它运行速度更快,并提供更好的可读性。另一方面,在我看来,C占用的空间很小,几乎没有任何意义,因为程序大小不是我们关心的问题。
记住,YMMV。
更多差异:
"printf"返回一个整数值(等于打印的字符数),"cout"不返回任何内容
和。
cout执行类型检查,printf没有。
没有相当于
我不是程序员,但我是一名人工因素工程师。我觉得编程语言应该易于学习,理解和使用,这要求它具有简单一致的语言结构。虽然所有的语言都是象征性的,因此,在其核心,任意的,有一些惯例,并遵循它们使语言更容易学习和使用。
C ++和其他语言中有大量函数作为函数(参数)编写,这种语法最初用于计算机时代的数学函数关系。
在Python中,我们当然可以使用相当标准的
我不喜欢cout语法,因为; lt ;; lt;运营商不遵守任何规则。它是一种方法或功能,即它需要一个参数并对其做一些事情。然而,它被写成好像是一个数学比较运算符。从人为因素的角度来看,这是一种糟糕的方法。
我想说
在C中,这是事实。但在C语言中,没有真正的课程。
在C ++中,可以重载强制转换运算符,因此,重载
1 2 3 | Foo bar; ...; printf("%s",bar); |
如果Foo超载好运算符,则是可能的。或者如果你做了一个好方法。简而言之,对于我来说,
我可以看到C ++流的技术论点(通常......不仅仅是cout。)是:
-
类型安全。 (顺便说一下,如果我要打印一个
' 我使用
'putchar(' ......我不会用核弹来杀死昆虫。)
') -
更容易学习。 (没有"复杂"的参数可供学习,只需使用
<< 和>> 运算符) -
使用
std::string 本地工作(printf 有std::string::c_str() ,但scanf ?)
对于
-
更容易,或至少更短(在写字符方面)复杂的格式。对我来说更具可读性(我猜的是味道问题)。
-
更好地控制函数的作用(返回写入的字符数和
%n 格式化程序:"没有打印。参数必须是指向signed int的指针,其中存储了到目前为止写入的字符数。" (来自printf - C ++参考) -
更好的调试可能性出于与上一个论点相同的原因。
我的个人喜好转到
我唯一对C风格函数感到遗憾的是不支持
1 2 | cout<<"Hello"; printf("%s","Hello"); |
两者都用于打印值。它们的语法完全不同。 C ++有两个,C
只有printf。