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

标签 c++ printf iostream cout stdio

printf() 有什么区别和 cout 在 C++ 中?

最佳答案

我很惊讶这个问题中的每个人都声称 std::coutprintf 好得多,即使问题只是要求差异。现在,有一个区别 - 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. 的内容,假设错误编号,并且其描述是占位符,则代码将如下所示。两个示例都是 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/fprintfhttp://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 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(数字)作为 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/

相关文章:

c++ - 为什么这会因对 0xcccccc 的访问冲突而崩溃...?

c++ - 解锁被另一个线程锁定的互斥锁

c - 通过头文件使用时 C 中源文件的奇怪输出

c - printf float 的精度

java - 是否有用于解析 printf 格式字符串的 Java 库?

C++ 在 .txt 文件中搜索所有出现的字符串并列出所有结果

c++ - 暂时覆盖输出流行为

c++ - XCode 中的调试 View 中出现奇怪的值

c++ - FSCTL_LOOKUP_STREAM_FROM_CLUSTER 返回 ERROR_INVALID_PARAMETER

c++ - 返回对原始参数的引用