C++中的“printf”与“cout”

'printf' vs. 'cout' in C++

提问人:hero 提问时间:5/20/2010 最后编辑:Deduplicatorhero 更新时间:10/16/2021 访问量:485295

问:

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

C++ printf IOSTREAM cout stdio

评论


答:

43赞 Kyle Rosendo 5/20/2010 #1

引用

概括地说,主要区别在于类型安全(cstdio 没有)、性能(大多数 IOTream 实现都是 比 CSTDIO 慢)和可扩展性(IOTroStream 允许 自定义输出目标和用户定义类型的无缝输出)。

评论

0赞 Lothar 2/6/2015
特别是在 unix 上,使用 POSIX,您永远不知道其中一个 typedef 的真正大小,因此您需要大量强制转换,或者 99% 的程序,您只需使用 %d 即可冒险。在 %z 随 C99 一起出现之前,甚至花了很长时间。但对于time_t/off_t,对正确格式指令的追求仍在继续。
31赞 Marcelo Cantos 5/20/2010 #2

一个是打印到 stdout 的函数。另一个对象提供多个成员函数和该打印的重载到 stdout。我可以列举更多的差异,但我不确定你在追求什么。operator<<

3赞 scatman 5/20/2010 #3
cout<< "Hello";
printf("%s", "Hello"); 

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

评论

20赞 xtofl 5/20/2010
...什么?你混淆了什么吗?
1赞 Yacoby 5/20/2010
修复了该问题。-1,因为它需要修复,答案还有很多不足之处。
3赞 Mahmoud Al-Qudsi 5/20/2010
函数名称被颠倒了:cout 与 printf 语法一起使用,printf 与 cout 语法一起使用。甚至不应该被接受!
2赞 jalf 5/20/2010
cout 的主要缺点是它使用 operator<<这是冗长和丑陋的,可以说是滥用 operator。:)
10赞 Jesse 9/5/2010
虽然这肯定不是最好的答案,但我不明白 scatman 是如何仅仅因为他的答案被选为最佳答案而受到惩罚的。xbit 有一个更糟糕的答案 IMO,但有 -1 票。我并不是说 xbit 应该再被否决了,但我认为因为 OP 的错误而投票给 scatman 是不公平的......
226赞 9 revs, 9 users 26%Marcelo Cantos #4

来自 C++ 常见问题解答

[15.1] 为什么我应该使用 <iostream>而不是传统的 <cstdio>

提高类型安全性、减少错误、允许扩展性并提供可继承性。

printf()可以说没有损坏,尽管容易出错,但也许是宜居的,但是两者在 C++ I/O 可以做什么方面都受到限制。 C++ I/O(使用 和 )相对于 C(使用 和 ):scanf()<<>>printf()scanf()

  • 类型更安全:使用 时,被 I/O 的对象类型是 编译器静态已知。在 对比度,使用“%”字段来 动态找出类型。<iostream><cstdio>
  • 更不容易出错:有了 ,就没有冗余 必须保持一致的“%”标记 实际对象是 I/O'd。 删除冗余会删除类 的错误。<iostream>
  • 可扩展:C++ 机制允许新的用户定义 类型是 I/O 而不中断 现有代码。想象一下混乱,如果 每个人都在同时添加 新的不兼容的“%”字段到 和 ?!<iostream>printf()scanf()
  • 可继承:C++ 机制是从真实类构建的 例如 和 。与 不同的是,这些是实数类和 因此是可继承的。这意味着您可以 具有其他用户定义的东西 看起来和行为都像溪流,但那 做任何奇怪和奇妙的事情 你想要的东西。你自动 开始使用数以百万计的行 由您不编写的用户编写的 I/O 代码 甚至知道,他们也不需要 了解您的“扩展流” 类。<iostream>std::ostreamstd::istream<cstdio>FILE*

另一方面,速度要快得多,这可能证明在非常具体和有限的情况下优先使用它是合理的。始终将配置文件放在首位。(例如,参见 http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-coutprintfcout/)

评论

2赞 xtofl 5/20/2010
另一方面,还有 FastFormat 库 (fastformat.org),它同时提供类型安全性、表现力和性能。(不是说我还没有尝试过......
3赞 Mikeage 5/21/2010
@Marcelo可能是因为它是一个很好的总结,引用了所有内容。格式...是的,这很糟糕。我应该自己解决这个问题,但似乎其他人(包括你自己)处理了它,当然,这比抱怨更有建设性。
2赞 Maxim Egorushkin 11/25/2011
最近也应该是可扩展的。请参阅 udrepper.livejournal.com/20948.html 中的“printf hooks”printf()
4赞 Ben Voigt 1/16/2014
@MaximYegorushkin:标准没有这样的能力。非可移植库机制几乎与 iostream 的完全标准化可扩展性处于同一水平。printf
7赞 FluorescentGreen5 11/11/2015
“另一方面,printf 明显更快” printf 也更干净、更易于使用,这就是为什么我尽可能避免 cout 的原因。
54赞 Thomas 5/20/2010 #5

人们经常声称这要快得多。这在很大程度上是一个神话。我刚刚测试了它,结果如下: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

结论:如果您只想要换行符,请使用 ;否则,几乎一样快,甚至更快。更多细节可以在我的博客上找到。printfcout

需要明确的是,我并不是想说 s 总是比 ;我只是想说,你应该根据真实数据做出明智的决定,而不是基于一些常见的、误导性的假设的疯狂猜测。iostreamprintf

更新:这是我用于测试的完整代码。编译时没有任何其他选项(除了时间)。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赞 mishal153 5/20/2010
在你的分数中,printf 很容易击败 cout(大多数情况下)。我想知道为什么你建议在性能方面使用 cout。虽然我同意 perf 在现实情况下并没有太大区别。.
3赞 Thomas 5/20/2010
@mishal153:我只是想说性能没有太大区别,所以经常听到的“永远不要使用 cout,因为它很慢”的建议是愚蠢的。请注意,cout 具有明显的类型安全优势,并且通常还具有可读性。(使用 iostreams 进行浮点格式化很糟糕......
47赞 Maxim Egorushkin 11/25/2011
和 之间的重要区别在于,前者在一次调用中输出所有参数,而对每个 .测试只输出一个参数和一个换行符,这就是为什么你看不到区别的原因。printf()std::ostreamstd::ostream<<
12赞 Thomas 11/26/2011
编译器应该能够内联这些调用。此外,可能会在后台对各种格式说明符的帮助程序函数进行大量调用......或者这是一个可怕的整体功能。同样,由于内联,它根本不应该对速度产生影响。printf
4赞 Ben Voigt 1/16/2014
您为终端计时。使用 或 和 或 。sprintffprintfstringstreamfstream
14赞 mishal153 5/20/2010 #6

对我来说,让我选择“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更具可读性,尤其是当我们有很多参数时。

一个问题是格式选项。格式化数据(精度、对齐等)更容易。coutprintf

评论

1赞 vp_arth 12/30/2016
这很好。我怎么知道没有人在一些外国库线程中以这种方式修改全局 cout?
9赞 CoffeeTableEspresso 1/11/2020
您也可以通过将其替换为...printffprintf
5赞 Daniel 5/21/2010 #7

对于基元,使用哪一个可能并不完全重要。我说它有用的地方是当你想要输出复杂的对象时。

例如,如果您有一个类,

#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”,您实际上不必担心输出。

评论

0赞 Daniel 5/21/2010
另外,为了补充性能问题,我想说的是,如果你的应用程序是为性能而生的,你根本不应该输出任何东西。任何一种输出到 std 都是相当昂贵和缓慢的。我说你应该避免它,只有在绝对必要时才输出。
0赞 hochl 8/4/2017
请记住,您的班级可能有私人成员,您无法从外部轻松访问。使用 output 运算符,您正好有一个位置需要成为类的友元,现在您可以在任何位置输出它,即使是在您不知道的代码中也是如此。
2赞 LuP 11/25/2011 #8

当然,你可以把“一些东西”写得更好一点,以保持维护:

#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 与 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

评论

0赞 Nicholas Hamilton 5/20/2014
哇,为什么效率比?endl'\n'
1赞 Caleb Xu 3/5/2015
我相信这是因为刷新了缓冲区,而不是,尽管我不确定这是否是最终的原因。endl\n
0赞 Fabio says Reinstate Monica 2/9/2016
这不是对这个问题的回答,它更像是对丹尼尔托马斯的回答。
0赞 avighnac 5/12/2022
@CalebXu,不,你是对的;这是唯一的原因。如果你看一下 std::endl 的定义,它实际上会插入一个换行符并刷新流。template <class _Elem, class _Traits> basic_ostream<_Elem, _Traits> &__CLRCALL_OR_CDECL endl(basic_ostream<_Elem, _Traits> &_Ostr) { // insert newline and flush stream _Ostr.put(_Ostr.widen('\n')); _Ostr.flush(); return _Ostr; }
1赞 bmorel 1/11/2012 #9

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

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

可以,如果 Foo 超载良好的操作员。或者如果你做了一个好方法。简而言之,对我来说是可扩展的。printfcout

我可以看到 C++ 流的技术论据(一般来说......不仅是 cout。是:

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

  • 更简单易学。(没有“复杂”的参数要学习,只是使用和运算符)<<>>

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

因为我明白了:printf

  • 更简单,或者至少更短(就所写的字符而言)复杂的格式。对我来说,可读性要强得多(我猜是品味问题)。

  • 更好地控制函数的内容(返回写入的字符数,并且有格式化程序:“未打印任何字符。该参数必须是指向有符号 int 的指针,其中存储了到目前为止写入的字符数。(来自 printf - C++ 参考%n)

  • 更好的调试可能性。原因与上一个论点相同。

我个人偏爱(和)函数,主要是因为我喜欢短行,而且我认为打印文本上的打字问题真的很难避免。 我对 C 样式函数唯一感到遗憾的是不支持它。在给它之前,我们必须经过一个(如果我们想阅读,但如何写?printfscanfstd::stringchar*printfstd::string::c_str()

评论

3赞 Ben Voigt 7/31/2012
编译器没有 varargs 函数的类型信息,因此它不会转换实际参数(默认参数升级除外,例如标准整数升级)。见5.2.2p7。不会使用用户定义的转换。char*
1赞 Marcelo Cantos 6/7/2015
即使这可行,它也不会成为 sprintf 可扩展性的例子,只是一个聪明的黑客,为 sprintf 提供它所期望的东西,并且它忽略了一些严重的问题,例如生存在哪里和多长时间,以及用户定义的隐式强制转换的危险。char*
2赞 skan 12/5/2012 #10

更多差异: “printf”返回一个整数值(等于打印的字符数),而“cout”不返回任何内容

和。

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

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

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

没有 iostream 等效项"% d"

评论

3赞 Keith Thompson 12/28/2012
cout不返回任何内容,因为它是一个对象,而不是一个函数。 确实返回某些内容(通常是它的左操作数,但如果有错误,则返回一个假值)。在什么意义上被称为“原子”?operator<<printf
10赞 artless noise 3/14/2013
这就像一颗原子弹。printf("%s\n",7);
0赞 Abhinav Gauniyal 1/18/2016
@artlessnoise等,为什么分段会出错? 是?%s
2赞 artless noise 1/18/2016
这就是“原子弹”声明的重点。%s 参数必须具有指向以 null 结尾的字符串的有效指针。内存范围“7”(指针)通常无效;分段故障可能是幸运的。在某些系统上,“7”可能会将大量垃圾打印到控制台,您必须在程序停止之前查看它一天。换句话说,这是一件坏事。静态分析工具可以捕获其中的许多问题。printfprintf
1赞 CoffeeTableEspresso 1/11/2020
虽然从技术上讲不进行类型检查,但我从未使用过没有警告我类型错误的编译器......printfprintf
518赞 Kamila Borowska 11/27/2013 #11

令我惊讶的是,这个问题中的每个人都声称这比 要好得多,即使这个问题只是要求差异。现在,有一个区别 - 是C++,是C(但是,您可以在C++中使用它,就像C中的几乎所有其他内容一样)。现在,我在这里说实话;两者都有其优势。std::coutprintfstd::coutprintfprintfstd::cout

真正的差异

扩展

std::cout是可扩展的。我知道人们也会说这也是可扩展的,但是 C 标准中没有提到这样的扩展(所以你必须使用非标准功能——但甚至不存在常见的非标准功能),而且这样的扩展是一个字母(所以很容易与已经存在的格式发生冲突)。printf

与 不同,完全依赖于运算符重载,因此自定义格式没有问题 - 您所做的只是定义一个子例程,将您的类型作为第一个参数,将您的类型作为第二个参数。因此,没有命名空间问题 - 只要你有一个类(不限于一个字符),你就可以为它工作重载。printfstd::coutstd::ostreamstd::ostream

但是,我怀疑很多人会想要扩展(老实说,我很少看到这样的扩展,即使它们很容易制作)。但是,如果您需要它,它就在这里。ostream

语法

很容易注意到,两者都使用不同的语法。 使用标准函数语法,使用模式字符串和可变长度参数列表。实际上,这是 C 拥有它们的一个原因——格式太复杂了,没有它们就无法使用。但是,使用不同的 API - 返回自身的 API。printfstd::coutprintfprintfprintfstd::coutoperator <<

一般来说,这意味着 C 版本会更短,但在大多数情况下这无关紧要。当您打印许多参数时,差异是显而易见的。如果您必须编写类似 ,假设错误号,并且其描述是占位符,则代码将如下所示。这两个示例的工作方式相同(嗯,有点,实际上刷新了缓冲区)。Error 2: File not found.std::endl

printf("Error %d: %s.\n", id, errors[id]);
std::cout << "Error " << id << ": " << errors[id] << "." << std::endl;

虽然这看起来并不太疯狂(它只是长了两倍),但当你实际格式化参数时,事情会变得更加疯狂,而不仅仅是打印它们。例如,打印类似的东西简直太疯狂了。这是由混合状态和实际值引起的。我从未见过一种语言将类似的东西变成一种类型(当然,C++除外)。 清楚地将参数和实际类型分开。与它的版本相比,我真的更愿意维护它的版本(即使它看起来有点神秘)(因为它包含太多噪音)。0x0424std::coutstd::setfillprintfprintfiostream

printf("0x%04x\n", 0x424);
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl;

译本

这才是真正的优势所在。格式字符串很好...一个字符串。与滥用 .假设函数进行转换,并且您想要显示 ,用于获取先前显示的格式字符串的转换的代码将如下所示:printfprintfoperator <<iostreamgettext()Error 2: File not found.

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

现在,让我们假设我们翻译成 Fictionish,其中错误号位于描述之后。转换后的字符串将类似于 。现在,如何在C++中做到这一点?好吧,我不知道。我想你可以为了翻译的目的而伪造哪些结构,你可以传递给 或其他东西。当然,不是 C 标准,但它是如此普遍,在我看来使用起来是安全的。%2$s oru %1$d.\niostreamprintfgettext$

不必记住/查找特定的整数类型语法

C 有很多整数类型,C++ 也是如此。 为您处理所有类型,但需要特定的语法,具体取决于整数类型(有非整数类型,但您在实践中将使用的唯一非整数类型是(C 字符串,可以使用 )方法获取)。例如,要打印,您需要使用 ,而需要使用 。这些桌子在 http://en.cppreference.com/w/cpp/io/c/fprintfhttp://en.cppreference.com/w/cpp/types/integer 有售。std::coutprintfprintfconst char *to_cstd::stringsize_t%zuint64_t%"PRId64"

您不能打印 NUL 字节,\0

因为使用 C 字符串而不是 C++ 字符串,所以如果没有特定的技巧,它无法打印 NUL 字节。在某些情况下,它可以用作论据,尽管这显然是一种黑客攻击。printf%c'\0'

没人关心的差异

性能

更新:事实证明,它太慢了,它通常比你的硬盘驱动器慢(如果你将程序重定向到文件)。如果您需要输出大量数据,禁用同步可能会有所帮助。如果性能是一个真正的问题(而不是将几行写入 STDOUT),只需使用 .iostreamstdioprintf

每个人都认为他们关心绩效,但没有人费心去衡量它。我的回答是,无论如何,I/O 都是瓶颈,无论您使用 or 还是 .我认为通过快速查看汇编(使用编译器选项使用 clang 编译)可能会更快。假设我的错误示例,示例所做的调用比示例少得多。这是与:printfiostreamprintf-O3printfcoutint 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:

你可以很容易地注意到两个字符串和 (number) 被作为参数推送。仅此而已;没有别的了。为了进行比较,它被编译为汇编。不,没有内联;每个调用都意味着另一个具有另一组参数的调用。2printfiostreamoperator <<

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 实现都使用计算的 goto 实现格式,因此即使编译器不知道,也尽可能快(并不是说它们不是 - 某些编译器可以在某些情况下进行优化 - 以 结尾的常量字符串通常优化为 )。iostreamprintfprintfprintfprintf\nputs

遗产

我不知道你为什么要继承,但我不在乎。这也是可能的。ostreamFILE

class MyFile : public FILE {}

类型安全

诚然,可变长度参数列表没有安全性,但这并不重要,因为如果您启用警告,流行的 C 编译器可以检测到格式字符串的问题。事实上,Clang 可以在不启用警告的情况下做到这一点。printf

$ 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);
     ^

评论

30赞 Ben Voigt 1/16/2014
无论如何,你说 I/O 是瓶颈。显然,你从未测试过这个假设。我引用自己的话:“另一方面,75.3 MB/s 的 iostreams 版本无法足够快地缓冲数据以跟上硬盘的步伐。这很糟糕,它甚至还没有做任何真正的工作。当我说我的 I/O 库应该能够使我的磁盘控制器饱和时,我认为我的期望并不高。
7赞 Kamila Borowska 1/16/2014
@BenVoigt:我承认,我尽量避免C++。我尝试了很多次使用它,但它比我使用的其他编程语言更烦人,而且更难以维护。这是我避免使用 C++ 的另一个原因 - 这甚至不快(它甚至不是 iostream - 整个 C++ 库在大多数实现中都很慢,也许例外,与(2 倍)相比,这在某种程度上令人惊讶地快,以可执行文件大小为代价)。std::sortqsort
3赞 Nicholas Hamilton 5/20/2014
这里没有人提到使用 cout 时并行环境中的问题。
11赞 Ignas2526 1/26/2015
你的性能论点没有任何意义。程序中的更多汇编并不意味着程序会变慢,因为您没有考虑制作 printf 函数的所有代码,这是大量代码。在我看来,使用 << 运算符优化 cout 比 printf 要好得多,因为编译器可以更好地理解变量和格式。
42赞 Kyle Strand 10/16/2015
我喜欢这个答案的很多东西,但也许我最喜欢的部分是“每个人都认为他们关心绩效,但没有人费心去衡量它。
-5赞 john 1/23/2014 #12

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

评论

10赞 Mark 7/16/2014
我做了一个回滚,因为虽然答案本身可能是错误的,但它仍然是一个真实的答案。如果您(正确地)认为答案是错误的,您有两种选择:1)添加评论或2)添加新答案(或两者兼而有之)。不要改变某人的答案,使其说出与作者意图完全不同的内容。
1赞 vp_arth 12/30/2016
printf是一个函数,但是一个函数调用 =)printf()
1赞 Lin 3/23/2020
cout 是一个对象,而不是一个变量。
10赞 Bill Weinman 12/6/2014 #13

这里没有提到的两点我认为很重要:

1) 如果您尚未使用 STL,请携带大量行李。它向目标文件添加的代码量是 的两倍多。对于 也是如此,这也是我倾向于使用自己的字符串库的主要原因。coutprintfstring

2)使用过载的运算符,我觉得这很不幸。如果您还将运算符用于其预期目的(左移),这可能会增加混淆。我个人不喜欢为了与预期用途相切的目的而使运算符过载。cout<<<<

底线:如果我已经在使用 STL,我将使用 (和 )。否则,我倾向于避免它。coutstring

4赞 Apollo 11/18/2015 #14

我想指出的是,如果你想在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

您可以使用 to get it right,也可以使用 .printfmutex

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

玩得愉快!

评论

2赞 Abhinav Gauniyal 1/18/2016
WTF 不会让输出发疯。我只是复制并在输出中找到了两者。没有 mangling b/w as .threadxyzABCABCABABAB
4赞 Fabio says Reinstate Monica 2/9/2016
我不知道如何使用线程,但我确信您显示的代码不是您用来获取这些输出的代码。代码传递线程 1 和线程 2 的字符串,但输出显示 和 。请修复它,因为现在它令人困惑。cout"ABC""xyz"AAABBB
1赞 caiohamamura 6/24/2022
cout使事情交错,因为正如其他答案所指出的,不会作为单个调用运行,而是在一连串调用中运行,因此它们最终会交错运行。但与@Apollo提到的不同的是,它永远不会交错,并且会在一次调用中打印。但它可以打印类似 .coutABmsg#2: AAABBB
1赞 Welgriv 6/29/2023
它不再相关。密钥在编译命令中:。使用不是 10 年的 c++ 标准,这个问题将消失。-std=c++0x
6赞 Daniel Woodard 3/7/2016 #15

我不是程序员,但我做过人因工程师。我觉得编程语言应该易于学习、理解和使用,这就要求它有一个简单而一致的语言结构。尽管所有语言都是符号性的,因此其核心是任意的,但还是有约定俗成的,遵循这些约定使语言更容易学习和使用。

C++和其他语言中有大量的函数被写成函数(参数),这种语法最初用于前计算机时代数学中的函数关系。 遵循此语法,如果 C++ 的作者想要创建任何逻辑上不同的方法来读取和写入文件,他们可以简单地使用类似的语法创建不同的函数。printf()

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

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

评论

2赞 user13947194 1/13/2023
<< 不是比较运算符。它是位左移运算符。它需要两个整数参数(最初)。10 << 1;这会将 10 个 1 次的位向左移动;返回数字 20。但 Cpp 允许运算符重载,并决定将此运算符用于 stdout。cpp 的比较运算符小于(<)、大于 (>)、小于或等于(<=)、大于或等于(>=)、等于(==)、不等于(!=)。这一切都只是特定于 cpp 的语法,而不是直接来自 Maths。请注意,赋值运算符为 (=)。
2赞 Wesley 4/1/2017 #16

TL的;DR:在信任网上随机评论之前,请务必对生成的机器代码大小性能可读性和编码时间进行自己的研究,包括这个评论。

我不是专家。我碰巧听到两位同事谈论我们应该如何避免在嵌入式系统中使用C++,因为性能问题。好吧,有趣的是,我根据真实的项目任务做了一个基准测试。

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

咖啡=热
糖=无
牛奶=乳房
mac=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();

在我把它们都循环 100,000 次之前,我尽了最大努力打磨它们。结果如下:

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 语言的占用空间很小,几乎没有任何意义,因为程序大小不是我们关心的问题。

Remeber,YMMV。

评论

3赞 Alcamtar 4/24/2018
我不同意C++在此示例中更具可读性,因为您的示例将多行打包到单个printf调用中。这自然不如你做C++代码的方式可读,而且很少在C语言中完成,因为它很难阅读和维护。一个公平的比较会将 C 分散到单独的 printfs 中,一个用于到达线。
1赞 Wesley 4/25/2018
@maharvey67 你说的是真的。但是,我在 C 语言中提供的示例是考虑性能的。对 fprintf 的打包调用已经比 C++ 等效项慢了两秒。如果我要使 C 代码可读,那么它可能会更慢。免责声明:这是一年前的事了,我记得我尽力打磨了 C 和 C++ 代码。我没有证据证明对 fprintf 的单独调用会比一个调用更快,但我这样做的原因可能表明事实并非如此。
0赞 Andrew Henle 6/9/2021
我想说C++方法更合适,因为它运行速度明显更快,可读性更好我不会。C++ 版本需要 3.18 秒的用户时间,而 C 版本只需要 2.37 秒的用户时间。这意味着 C 二进制文件在执行其用户空间工作时效率要高得多,并且整个性能差异是由于 C++ 版本的系统时间要短得多。由于您忽略了指定已编译二进制文件实际执行 IO 的方式,因此无法判断为什么 C++ 二进制文件使用的系统时间更少。
0赞 Andrew Henle 6/9/2021
这可能是一些平凡的事情,因为C++输出的缓冲方式不同。这可能是因为锁定了 C 调用。进行基准测试,然后使用“O_DIRECT”怎么样?sprintf()write()