我很惊讶这个问题中每个人都声称std::cout
比printf
更好,即使问题只是问有什么区别。现在,确实存在一个区别-std::cout
是C ++,printf
是C(但是您可以像使用C中的几乎任何其他东西一样在C ++中使用它)。现在,我要诚实地说; both printf
and std::cout
have their advantages。
std::cout
是可扩展的。我知道人们会说printf
也是可扩展的,但在C标准中没有提到这样的扩展(因此您必须使用非标准功能-但甚至没有常见的非标准功能存在),而且这样的扩展只有一个字母(因此很容易与已经存在的格式发生冲突)。
与printf
不同,std::cout
完全依赖于运算符重载,因此没有自定义格式的问题-所有您需要做的就是定义一个子程序,将std::ostream
作为第一个参数,将您的类型作为第二个参数。因此,没有命名空间问题-只要您有一个类(不局限于一个字符),就可以为其具有工作的std::ostream
重载。
然而,我怀疑许多人会想要扩展ostream
(说实话,即使很容易制作这样的扩展,我也很少看到这样的扩展)。但是,如果需要,它在这里。
可以轻松注意到,printf
和std::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
的格式化字符串是一个字符串,这使得它相对于iostream
的operator <<
滥用来说,翻译起来非常容易。假设gettext()
函数已经翻译好了,并且你想显示Error 2: File not found.
,获取先前显示的格式化字符串的翻译代码如下:
printf(gettext("Error %d: %s.\n"), id, errors[id]);
现在,假设我们翻译成 Fictionish,其中错误数字在描述之后。翻译的字符串将如下所示:%2$s oru %1$d.\n
。现在,如何在 C++ 中实现呢?嗯,我不知道。我想你可以制作一个假的 iostream
,它构造了一个你可以传递给 gettext
的 printf
,或者其他用于翻译的内容。当然,$
不是 C 标准,但它很常见,在我看来使用它是安全的。
C 有很多整数类型,C++ 也是如此。 std::cout
会处理所有类型,而 printf
则需要根据整数类型使用特定的语法(虽然有非整数类型,但实际上您将仅使用 const char *
(C 字符串),可以使用 std::string
的 to_c
方法获得)。例如,要打印 size_t
,您需要使用 %zu
,而 int64_t
将需要使用 %"PRId64"
。表格可在http://en.cppreference.com/w/cpp/io/c/fprintf和http://en.cppreference.com/w/cpp/types/integer上找到。
\0
由于 printf
使用 C 字符串而不是 C++ 字符串,因此它不能打印 NUL 字节而不进行特定的技巧。在某些情况下,可以使用 %c
和 '\0'
作为参数,尽管这显然是一个 hack。
更新:事实证明,iostream
如此缓慢,以至于它通常比您的硬盘更慢(如果将程序重定向到文件中)。关闭与 stdio
的同步可能有所帮助,如果需要输出大量数据。如果性能是真正的问题(而不是向 STDOUT 写入几行),只需使用 printf
。
每个人都认为他们关心性能,但没有人费心去衡量它。我的答案是,无论是使用 printf
还是 iostream
,I/O 都是瓶颈。我认为,从快速查看汇编(使用 -O3
编译器选项使用 clang 编译)的角度来看,printf
可能会更快。假设错误示例,printf
示例比 cout
示例需要更少的调用。这是带有 printf
的 int main
:
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
被推入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本来就是瓶颈。我只是想表明并不更快,因为它是“类型安全”的。大多数C实现使用计算跳转来实现格式,所以尽可能地快,即使编译器不知道(他们并不是不知道-某些编译器可以在某些情况下优化-常量字符串以<\n>结尾通常会优化为)。
我不知道你为什么想继承,但我不在乎。用也是可能的。
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);
^
来自C++ FAQ:
[15.1] 为什么我应该使用<iostream>
而不是传统的 <cstdio>
?
增加类型安全性,减少错误,允许可扩展性,并提供继承性。
printf()
可能并没有问题,而且尽管易错,scanf()
也许还可以接受,但两者在 C++ I/O 所能做的方面都有限制。与 C(使用 printf()
和 scanf()
)相比,C++ I/O(使用 <<
和 >>
)具有以下优点:
<iostream>
,编译器静态知道正在进行 I/O 的对象的类型。相反,<cstdio>
使用“%”字段动态地确定类型。<iostream>
,不存在必须与实际进行 I/O 的对象一致的冗余“%”标记。消除冗余会消除一类错误。<iostream>
机制允许对新的用户定义类型进行 I/O,而不会破坏现有代码。想象一下,如果每个人都同时向 printf()
和 scanf()
添加新的不兼容“%”字段,会发生什么混乱?!<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 /)printf()
也应该是可扩展的。请参阅http://udrepper.livejournal.com/20948.html上的“printf hooks”。 - Maxim Egorushkinprintf
函数没有这种能力。非可移植的库机制很难与iostreams的完全标准化扩展相提并论。 - Ben Voigt人们经常声称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
几乎和printf
一样快,甚至更快。更多细节可在我的博客中找到。iostream
总是比printf
好; 我只是想说,你应该根据真实数据做出知情决策,而不是基于一些常见、误导性的假设的猜测。-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);
}
}
printf()
和std::ostream
的重要区别在于,前者可以在一次函数调用中输出所有参数,而std::ostream
需要为每个<<
操作单独调用一次函数。由于测试只输出一个参数和一个换行符,这就是为什么您看不到这种区别的原因。 - Maxim Egorushkinprintf
在底层可能会对各种格式说明符调用多个辅助函数... 或者它是一个庞大的单块函数。由于内联,这在速度上并不会有任何差异。 - Thomassprintf
或fprintf
以及stringstream
或fstream
。 - Ben Voigt我引用以下内容:
从高层次来看,主要区别在于类型安全性(cstdio没有它)、性能(大多数iostreams实现比cstdio慢)和可扩展性(iostreams允许自定义输出目标并无缝输出用户定义的类型)。
一个是打印到标准输出的函数,另一个是提供多个成员函数和重载operator<<
用于打印到标准输出的对象。我还可以列举更多差异,但我不确定你需要什么。
对我而言,让我选择“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
中格式化数据(精度、对齐等)更容易。
printf
替换为 fprintf
来将其轻松地转换成写入文件的形式,不改变原意。 - CoffeeTableEspresso这里有两点我认为很重要,但未被提及:
1)如果您还没有使用STL,cout
会增加大量代码到您的目标文件中,比printf
多出一倍以上。对于string
也是如此,这是我倾向于使用自己的字符串库的主要原因。
2)cout
使用了重载的<<
运算符,我认为这是不幸的。如果您还要使用<<
运算符进行其预期用途(左移),这可能会增加混淆。我个人不喜欢为与预期用途有关的目的重载运算符。
总结:如果我已经使用STL,则会使用cout
(和string
)。否则,我倾向于避免使用它。
对于基本数据类型,使用哪种可能并不完全重要。我认为它的有用之处在于当您想要输出复杂对象时。
例如,如果您有一个类,
#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;
}
看起来可能并不是很好,但是假设你必须在代码中的多个地方输出这个结果。使用cout,你只需在一个地方修改。然而,如果使用printf,则需要在可能很多的地方进行修改,并且你还必须自己想起要输出哪些东西。
因此,使用cout可以减少代码维护所需的时间,而且如果在新应用程序中重复使用“Something”对象,则不必担心输出问题。
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
玩得开心!
thread
不会让输出变得疯狂。我刚刚复制并发现输出中有 xyz
和 ABC
。在 ABC
之间没有混淆成 ABABAB
。 - Abhinav Gauniyalcout
如何与线程一起使用,但我确定你展示的代码不是用于产生那些输出的代码。你的代码将字符串“ABC”传递给线程1,将“xyz”传递给线程2,但你的输出显示为“AAA”和“BBB”。请修正它,因为现在它很令人困惑。 - Fabio says Reinstate Monicacout
会使事情交错,因为正如其他答案所指出的那样,cout
不会作为单个调用运行,而是会在一系列调用中运行,因此它们最终会交错。但与 @Apollo 所提到的不同的是,它永远不会交错 A
和 B
,因为 msg
将在单个调用中打印。但它可以打印类似于 #2: AAABBB
的东西。 - caiohamamura-std=c++0x
。使用一个不是十年前的C++标准,这个问题就会消失。 - Welgriv
std::sort
之外,后者比qsort
快两倍,代价是可执行文件大小)。 - Konrad Borowski