有人可以解释一下(最好用简单的英语)是如何std::flush
工作的吗?
- 它是什么?
- 你什么时候刷新一个流?
- 它为什么如此重要?
谢谢你。
由于没有回答发生了什么 std::flush
,这里有一些关于它实际上是什么的细节。std::flush
是一个操纵器,即具有特定签名的函数。开始简单,你可以考虑std::flush
有签名
std::ostream& std::flush(std::ostream&);
不过,实际情况要复杂一些(如果您有兴趣,下面也会解释)。
流类重载输出运算符采用这种形式的运算符,即有一个成员函数以操纵器为参数。输出运算符使用对象本身调用操纵器:
std::ostream& std::ostream::operator<< (std::ostream& (*manip)(std::ostream&)) {
(*manip)(*this);
return *this;
}
也就是说,当你std::flush
用 to an “输出”时std::ostream
,它只是调用了相应的函数,即以下两条语句是等价的:
std::cout << std::flush;
std::flush(std::cout);
现在,std::flush()
它本身相当简单:它所做的只是调用std::ostream::flush()
,也就是说,您可以设想它的实现看起来像这样:
std::ostream& std::flush(std::ostream& out) {
out.flush();
return out;
}
该std::ostream::flush()
函数在技术上调用std::streambuf::pubsync()
与流相关联的流缓冲区(如果有):流缓冲区负责缓冲字符,并在使用的缓冲区溢出或内部表示应与外部目的地,即何时刷新数据。在与外部目标同步的顺序流上,仅意味着立即发送任何缓冲的字符。也就是说, usingstd::flush
会导致流缓冲区刷新其输出缓冲区。例如,当数据写入控制台时,刷新会导致字符出现在控制台上。
这可能会提出一个问题:为什么不立即写入字符?简单的答案是写字符通常相当慢。但是,编写合理数量的字符所花费的时间基本上与仅在其中编写一个字符所花费的时间相同。字符的数量取决于操作系统、文件系统等的许多特性,但通常多达 4k 个字符与一个字符几乎同时写入。因此,根据外部目标的详细信息,在发送字符之前使用缓冲区缓冲字符可以极大地提高性能。
以上应该回答你三个问题中的两个。剩下的问题是:什么时候刷新流?答案是:何时应该将字符写入外部目的地!这可能是在写入文件结束时(尽管关闭文件会隐式刷新缓冲区)或在要求用户输入之前(请注意,从as is 'd tostd::cout
读取时会自动刷新)。尽管在某些情况下您可能明确想要刷新流,但我发现它们相当罕见。std::cin
std::cout
std::istream::tie()
std::cin
最后,我承诺给出一个完整的std::flush
实际情况:流是类模板,能够处理不同的字符类型(实际上它们与char
and一起使用wchar_t
;让它们与另一个字符一起使用是相当复杂的,但如果你真的下定决心是可行的)。为了能够std::flush
与流的所有实例一起使用,它恰好是一个具有如下签名的函数模板:
template <typename cT, typename Traits>
std::basic_ostream<cT, Traits>& std::flush(std::basic_ostream<cT, Traits>&);
当std::flush
立即使用它的实例时std::basic_ostream
并不重要:编译器会自动推导出模板参数。但是,如果没有提及此函数以及促进模板参数推导的东西,编译器将无法推导模板参数。
默认情况下,std::cout
是缓冲的,并且只有在缓冲区已满或发生其他一些刷新情况(例如流中的换行符)时才会打印实际输出。有时您想确保打印立即发生,您需要手动刷新。
例如,假设您想通过打印一个点来报告进度报告:
for (;;)
{
perform_expensive_operation();
std::cout << '.';
std::flush(std::cout);
}
如果没有冲洗,您将在很长一段时间内看不到输出。
请注意,std::endl
将换行符插入流中并使其刷新。由于冲洗费用略高,std::endl
如果不是特别需要冲洗,则不应过度使用。
这是一个简短的程序,您可以编写它来观察刷新正在做什么
#include <iostream>
#include <unistd.h>
using namespace std;
int main() {
cout << "Line 1..." << flush;
usleep(500000);
cout << "\nLine 2" << endl;
cout << "Line 3" << endl ;
return 0;
}
运行这个程序:你会注意到它打印第 1 行,暂停,然后打印第 2 行和第 3 行。现在删除刷新调用并再次运行程序 - 你会注意到程序暂停,然后打印所有 3 行同时。第一行在程序暂停之前被缓冲,但由于缓冲区从不刷新,所以直到第 2 行的 endl 调用才输出第 1 行。
流与某物相连。在标准输出的情况下,它可以是控制台/屏幕,也可以重定向到管道或文件。在你的程序和存储文件的硬盘之间有很多代码。例如,操作系统正在处理任何文件,或者磁盘驱动器本身可能正在缓冲数据以便能够将其写入固定大小的块或只是为了提高效率。
当您刷新流时,它会告诉语言库、操作系统和硬件,您希望到目前为止输出的任何字符都被强制存储。从理论上讲,在“冲洗”之后,您可以将绳索踢出墙壁,并且这些字符仍然可以安全地存储。
我应该提一下,编写操作系统驱动程序的人或设计磁盘驱动器的人可能会随意使用“刷新”作为建议,他们可能不会真正写出字符。即使输出关闭,他们也可能会等待一段时间来保存它们。(请记住,操作系统一次会执行各种操作,等待一两秒来处理您的字节可能更有效。)
所以刷新是一种检查点。
再举一个例子:如果输出到控制台显示,则刷新将确保字符实际上一直到达用户可以看到的位置。当您期待键盘输入时,这是一件很重要的事情。如果你认为你已经向控制台写了一个问题并且它仍然停留在某个内部缓冲区中,那么用户不知道要输入什么答案。因此,这是冲洗很重要的情况。