引言:两个字符的战争
如果你曾经用 C++ 编写过代码并向控制台输出文本,你肯定遇到过这样的两难选择:使用 std::endl 还是 ""?乍一看,区别微乎其微——两者都能换行。但在这个简单的选择背后,隐藏着关于性能、缓冲以及程序中意外卡顿的完整故事。
在 Stack Overflow 上,这个问题获得了 数千次浏览,并引发了激烈的讨论。今天,我们将一劳永逸地分析这个主题:哪个更快,为什么,以及每种方式何时是合理的。如果你正在编写 C++ 代码,并希望你的程序尽可能高效地运行——这篇文章就是为你准备的。
简短回答(剧透)
"" 总是比 std::endl 更快。但这并不是因为 std::endl 本身很慢,而是因为它做了 额外的工作。std::endl 不仅仅添加一个换行符——它还会 刷新输出缓冲区(flush)。而这是一个可能耗费你宝贵毫秒数的操作。
让我们看看这在实践中意味着什么,并查看代码。
C++ 中的输出是如何工作的
当你编写 std::cout << "Hello"; 时,数据并不会立即发送到屏幕。它们会累积在 缓冲区 中——内存中的一个临时存储区域。这样做是为了提高效率:逐个字符地将数据发送到屏幕太慢了,因此系统会等待积累足够的数据,然后一次性发送它们。
刷新缓冲区(flush)就是强制发送所有累积的数据。这正是 std::endl 所做的。
底层发生了什么?
// 使用 std::endlstd::cout << "字符串 1" << std::endl;// 等同于:std::cout << "字符串 1" << '' << std::flush;
// 使用 ""std::cout << "字符串 1";// 仅仅向缓冲区添加一个换行符看到区别了吗?std::endl 做了两件事:添加 '' 并调用 std::flush。"" 只做了一件事。
实验:测量性能
让我们用一个真实的例子来验证。编写一个小程序,用两种方式输出 100,000 行并测量时间。
#include <iostream>#include <chrono>
int main() { const int N = 100000; // 使用 std::endl 的测试 auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N; ++i) { std::cout << "行号 " << i << std::endl; } auto end = std::chrono::high_resolution_clock::now(); auto duration_endl = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << "使用 endl 的时间: " << duration_endl.count() << " 毫秒"; // 使用 "" 的测试 start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < N; ++i) { std::cout << "行号 " << i << ""; } end = std::chrono::high_resolution_clock::now(); auto duration_n = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << "使用 \\ 的时间: " << duration_n.count() << " 毫秒"; return 0;}结果(典型值):
std::endl: ~350-400 毫秒"": ~5-10 毫秒
相差 30-70 倍!这可不是开玩笑。每次调用 std::endl 都会迫使系统与操作系统交互以刷新缓冲区。而这是一个相对昂贵的系统调用。
何时需要 flush?
但不要急于完全放弃 std::endl。有时刷新缓冲区至关重