最佳答案
我很惊讶这个问题中的每个人都声称 std::cout
比 printf
好得多,即使问题只是要求差异。现在,有一个区别 - std::cout
是 C++,而 printf
是 C(但是,您可以在 C++ 中使用它,就像 C 中的几乎任何其他东西一样)。现在,我会在这里说实话; printf
和 std::cout
各有优势。
真正的差异
可扩展性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.
的内容,假设错误编号,并且其描述是占位符,则代码将如下所示。两个示例都是 work identically(嗯,差不多,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
构造 printf
,你可以传递给 gettext
或其他东西,用于翻译。当然,$
不是 C 标准,但它很常见,我认为使用它是安全的。不必记住/查找特定的整数类型语法
C 有很多整数类型,C++ 也是如此。
std::cout
为您处理所有类型,而 printf
需要取决于整数类型的特定语法(有非整数类型,但您在实践中使用 printf
的唯一非整数类型是 const char *
(使用 C 字符串,可以得到 to_c
std::string
)) 的方法。例如,要打印 size_t
,您需要使用 %zd
,而 int64_t
将需要使用 %"PRId64"
。这些表位于 http://en.cppreference.com/w/cpp/io/c/fprintf 和 http://en.cppreference.com/w/cpp/types/integer 。你不能打印 NUL 字节,
\0
因为
printf
使用 C 字符串而不是 C++ 字符串,所以它不能在没有特定技巧的情况下打印 NUL 字节。在某些情况下,可以使用 %c
和 '\0'
作为参数,尽管这显然是一种黑客行为。没人关心的差异
表现
更新:事实证明
iostream
太慢了,它通常比你的硬盘慢(如果你将你的程序重定向到文件)。如果您需要输出大量数据,禁用与 stdio
的同步可能会有所帮助。如果性能是一个真正的问题(而不是将几行写入 STDOUT),只需使用 printf
。每个人都认为他们关心性能,但没有人费心去衡量它。我的答案是无论如何 I/O 都是瓶颈,无论您使用
printf
还是 iostream
。我认为 printf
可以通过快速查看汇编更快(使用 -O3
编译器选项使用 clang 编译)。假设我的错误示例,printf
示例的调用次数比 cout
示例少。这是 int main
和 printf
: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 都是瓶颈。我只是想表明
iostream
并不快,因为它是“类型安全的”。大多数 C 实现使用计算的 goto 实现 printf
格式,因此 printf
尽可能快,即使编译器不知道 printf
(并不是说它们不是 - 有些编译器可以在某些情况下优化 printf
- 以 0x1045 结尾的常量字符串通常优化为 \n
)。遗产
我不知道你为什么要继承
puts
,但我不在乎。 ostream
也是可能的。class MyFile : public FILE {}
类型安全
确实,可变长度参数列表没有安全性,但这并不重要,因为如果启用警告,流行的 C 编译器可以检测到
FILE
格式字符串的问题。事实上,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++ - 'printf' 与 C++ 中的 'cout',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2872543/