#TL;DR
使用下面的这段代码可以为标准 C++ 流的输入输出提速。
1 | |
使用时有几个注意事项:
- 关闭了流同步后,切莫将 C++ 风格的输入输出与 C 风格的输入输出(如
scanf和printf)混用,否则会造成意料之外的错误。 - 如果需要清空缓冲区,请使用
std::flush或者std::endl。 cout.tie(nullptr)在一般情况下是非必要的,因为其在初始化时并没有绑定其他流。
#原理
#关闭与标准 C 流之间的同步
根据 C++ Reference 上的描述:
std::ios_base::sync_with_stdio
C++
1static bool sync_with_stdio( bool sync = true );设置标准 C++ 流是否与标准 C 流在每次输入/输出操作后同步。
标准 C++ 流为下列者:
std::cin、std::cout、std::cerr、std::clog、std::wcin、std::wcout、std::wcerr和std::wclog。标准 C 流为下列者:
stdin、stdout和stderr。对于与 C 流 f 同步的标准流
str,下列函数对拥有等同的效果:
std::fputc(f, c)和str.rdbuf()->sputc(c)std::fgetc(f)和str.rdbuf()->sbumpc()std::ungetc(c, f)和str.rdbuf()->sputbackc(c)实践中,这表示同步的 C++ 流为无缓冲,而每次 C++ 流上的 I/O 都立即应用到对应 C 流的缓冲区。这使得能自由地混合 C++ 与 C I/O。
另外,同步的 C++ 流保证为线程安全(从多个线程输出的单独字符可能交错,但无数据竞争)。
若关闭同步,则允许 C++ 标准流独立地缓冲其 I/O ,可认为这在某些情况下更快。
所有八个标准 C++ 流默认与其相应的 C 流同步。
若在标准流上已出现 I/O 后调用此函数,则行为是实现定义的:有的实现无效果,有的实现销毁读取缓冲区。
可以得知以下信息:
- C++ 为了保证兼容性,默认将标准 C++ 流的数据与标准 C 流同步,并将缓冲数据放置到标准 C 流的缓冲区中,但这样会减慢速度。
- 当关闭同步时会导致程序的不同线程之间的 I/O 冲突,不过 OI 中并不涉及。
- 在程序运行到产生首次输入输出后关闭同步的行为是由实现定义的。
那么可以通过关闭流同步的方式来解决第一点问题:
1 | |
将这行代码放到主函数的起始位置即可。
由第三点信息可知,不能在运行时反复开启/关闭流同步,防止出现错误。
#使用 \n 代替 std::endl
根据 C++ Reference 上的描述:
std::endl
C++
1
2template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );插入换行符到输出序列
os并冲入它,如同调用os.put(os.widen('\n'))后随os.flush()。这是仅输出的 I/O 操纵符,可对任何
std::basic_ostream类型的out以表达式out << std::endl调用它。
可以得知,在调用 std::endl 时不仅输出了换行符,而且还清空了缓冲区。众所周知,频繁清空缓冲区会导致程序运行速度的下降,所以应该尽量避免使用 std::endl 作为换行符。
那么可以通过使用 \n 来替代 std::endl 的做法来提升速度:
1 | |
不推荐使用 endl 的原因是,如果遇到特殊情况需要使用 std::endl 时,define 会将 std::endl 中的 endl 替换为 \n 导致编译错误。
#取消 cin 与 cout 之间的联系
根据 C++ Reference 上的描述:
std::basic_ios
:: tieC++
1
2std::basic_ostream<CharT,Traits>* tie() const; // (1)
std::basic_ostream<CharT,Traits>* tie( std::basic_ostream<CharT,Traits>* str ); // (2)管理联系流。联系流是输出流,它与流缓冲(
rdbuf())所控制的输出序列同步,即在任何*this上的输入/输出操作前,在联系流上调用flush()。
- 返回当前联系流。若无联系流,则返回空指针。
- 设置当前联系流为 str 。返回操作前的联系流。若无联系流,则返回空指针。
可以得知,如果一个流设置了联系流,在它进行输入/输出操作前会清除其联系流的缓冲区。众所周知,频繁清空缓冲区会导致程序运行速度的下降,所以如非必要,在竞赛中可以取消 std::cin 和 std::cout 间的联系以获得更快的输入输出速度:
1 | |
#无需使用 cout.tie(nullptr)
查看 ISO C++ 14 标准(ISO/IEC 14882:2014(E),原文见文末附件):

此处(第 999 页)并没有对 std::cout 的 tie() 赋初值。

在第 1012 页中,指明了 tie() 的初值为 0,即未绑定任何流。
所以,std::cout 在初始时并未与任何流产生绑定,所以无需取消 std::cout 与其他流的绑定。cout.tie(nullptr) 是多余的,可以删去。
#测试
输入数据为 LibreOJ 7. Input Test 的测试点 5,大小为 59.7 MB。测试选择在 LibreOJ 评测量较为平稳时进行。不用洛谷测试的原因是怕被管理封号,我自己的 LibreOJ 账号有管理权限不怕被封。
#结果
| 用时 | O2 优化 | cin.tie(nullptr) | 与标准 C 流同步 | 换行符 |
|---|---|---|---|---|
| 1820 ms | 是 | 是 | 是 | std::endl |
| 1041 ms | \n | |||
| 1107 ms | 否 | std::endl | ||
| 335 ms | \n | |||
| 1803 ms | 是 | std::endl | ||
| 1053 ms | \n | |||
| 1100 ms | 否 | std::endl | ||
| 335 ms | \n | |||
| 1877 ms | 否 | 是 | std::endl | |
| 1801 ms | \n | |||
| 1117 ms | 否 | std::endl | ||
| 1160 ms | \n | |||
| 1810 ms | 是 | std::endl | ||
| 1826 ms | \n | |||
| 1093 ms | 否 | std::endl | ||
| 1159 ms | \n | |||
| 1810 ms | 否 | 是 | 是 | std::endl |
| 1020 ms | \n | |||
| 1095 ms | 否 | std::endl | ||
| 347 ms | \n | |||
| 1808 ms | 是 | std::endl | ||
| 1045 ms | \n | |||
| 1105 ms | 否 | std::endl | ||
| 355 ms | \n | |||
| 1837 ms | 否 | 是 | std::endl | |
| 1832 ms | \n | |||
| 1095 ms | 否 | std::endl | ||
| 1133 ms | \n | |||
| 1872 ms | 是 | std::endl | ||
| 1845 ms | \n | |||
| 1098 ms | 否 | std::endl | ||
| 1130 ms | \n |
#代码
1 | |
#实际测试
#7. Input Test
#10145. 「一本通 4.6 练习 2」郁闷的出纳员
#结语
感谢你完整阅读了本文。
通过本文,相信读者对 C++ 的输入/输出流又有了更深的认识。按照本文所叙述的方法去操作,你就可以获得一个媲美 scanf 和 printf 的速度的 C++ 风格的输入了。
如果本文中有笔者遗漏、编写错误的地方,欢迎指正。
#参考资料
- std::basic_ios<CharT,Traits>::tie,C++ Reference,2021 年 1 月 1 日。
- std::ios_base::sync_with_stdio,2017 年 11 月 23 日。
- std::flush,2017 年 11 月 18 日。
- 27.4 Standard iostream objects,ISO/IEC 14882:2014(E)。
感谢 LibreOJ 提供的高效、稳定的测评服务。