#AB01. 输入输出加速
输入输出加速
当前没有测试数据。
为什么需要输入输出加速?
默认情况下,C++ 的 cin
和 cout
的速度一般。问题主要有三个:
-
与 C 语言 I/O 的同步
默认情况下,C++ 的cin
,cout
与 C 语言的scanf
,printf
是同步的。这让我们可以混合使用这两种 I/O 方式,但这种同步机制会带来显著的性能开销,因为它需要在两种不同的I/O库之间进行协调。 -
cin
与cout
的绑定
默认情况下,cin
与cout
是绑定的。每次在执行输入操作之前,程序会自动刷新(flush)输出缓冲区。这样做是为了确保在请求用户输入之前,所有之前的输出都已经被显示在屏幕上。但在大量输入时,频繁刷新缓冲区会大大降低效率。 -
endl
的使用
endl
不仅仅是一个换行符,它还会强制刷新输出缓冲区。在需要输出很多行时,每次都使用 endl 会导致频繁刷新缓冲区,从而严重影响性能。
如何输入输出加速?
在仅使用 C++ 风格输入输出(cin/cout)的前提下,我们一般会使用下面两行代码,并且,在输出时仅使用 '\n' 来进行换行,而不是 endl。
ios::sync_with_stdio(false);
cin.tie(0);// 或者 cin.tie(nullptr)
代码解释
- ios::sync_with_stdio(false); 这条语句的作用是解除 C++ 和 C 的 I/O 同步。一旦关闭同步,C++ 的输入输出流就可以使用自己独立的缓冲区,从而省去了与 C 标准库同步所需的时间,运行效率得到大幅提升。需要注意的是,一旦使用了这行代码,就不能在程序中混用 cin/cout 和 scanf/printf,否则可能会导致输入输出顺序混乱。
- cin.tie(0);
cin
和cout
之间的绑定。tie的作用是确保在一个流上进行操作前,会先刷新其绑定的另一个流。默认cin
绑定了cout
,将其参数设为0或nullptr
后,cin
就不会再强制刷新cout
了。 很多同学喜欢再写一个cout.tie(0)
;,但实际上这没有意义,因为cout 没有绑定其他流。使用 '\n' 替代 - endl
endl
会刷新缓冲区,而 '\n' 只是个换行符。程序结束时会自动刷新缓冲区,所以使用 '\n' 可以避免多次刷新缓冲区。
注意事项!
文件输入输出
不要手动关闭文件输入输出,即不要进行 fclose
等操作。程序结束时会自动刷新缓冲区并关闭你打开的文件。如果你提前手动关闭了并且在那之前没有刷新缓冲区,那你的输出就不会进入文件了。
交互题的强制刷新缓冲区
在交互题中,你的程序需要和评测程序进行交互通信。在这种情况下,你必须确保你的输出被立即发送给评测程序,而不是停留在缓冲区里。由于我们已经通过 cin.tie(0)
解除了绑定,cout
不会自动刷新。因此,在输出查询后,你需要使用下面的代码手动强制刷新缓冲区:
cout.flush();
调试出问题了?
输入输出加速后,你在本地调试时可能会觉得出 bug
了。实际上是因为解除了 cin
和 cout
的绑定,cout
不再自动刷新,导致在你输入所有数据之前,可能看不到任何程序输出,这让你没法实时监控程序。
一般我们有下面几种解决方法:
- 临时注释加速代码,但最后别忘了取消注释。
- 使用
cerr
进行调试输出cerr
是一个标准错误流,它是非缓冲的。发送到cerr
的输出会立即被显示出来,而不会被暂存在缓冲区。因此,你可以用 cerr 来输出调试信息,它不会受到I/O加速的影响。 一般情况下, cerr 的输出会被 OJ 和比赛的评测忽略,不会影响评测结果。但为了以防万一,还是建议你最后注释掉cerr
语句再提交。(不然你可能误以为cerr
的输出是常规的输出,导致错误。) - 在调试点手动刷新
cout
,即在调试输出后执行cout.flush();
语句手动刷新缓冲区。