算法竞赛C++常用技巧——输入输出优化(防止TLE)
cin、cout优化
在默认情况下,std::cin/std::cout
是极为迟缓的输入/输出方式,而 scanf/printf
比 std::cin/std::cout
快得多。
可是为什么会这样呢?如果我更习惯使用C++的输出方式,那么有没有什么办法解决输入输出缓慢的问题呢?
这是因为在默认情况下,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输出顺序错乱。由于这个特性,所以导致cin和cout有许多额外的开销。
那么我们如何禁用这个特性呢?
关闭同步/解除绑定
std::ios::sync_with_stdio(false)
这个函数是一个“是否兼容 stdio”的开关,C++ 为了兼容 C,保证程序在使用了 printf 和 std::cout 的时候不发生混乱,将输出流绑到了一起。
这其实是 C++ 为了兼容而采取的保守措施。我们可以在进行 IO 操作之前将 stdio 解除绑定,但是在这样做之后要注意不能同时使用 std::cin/std::cout
和 scanf/printf
。更严格的来说:关闭之后C++ IO和C IO 两者不能混用,否则会造成IO混乱。
tie函数加速
还有一种影响速度的原因是:在默认的情况下std::cin
绑定的是 std::cout
,每次执行 << 操作符的时候都要调用 flush()
,这样会增加 IO 负担。
tie 是将两个 stream 绑定的函数,空参数的话返回当前的输出流指针。 可以通过std::cin.tie(0) 和std:cout.tie(0)(0 表示 NULL)
来解除 std::cin
与 std::cout
的绑定,进一步加快执行效率。
在这两种情况优化下,std::cin和std::cout
的速度就和scanf和printf
基本一样了,甚至更快。
代码实现
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
//如果编译开启了 C++11 或更高版本,建议使用 std::cin.tie(nullptr);
注意:
scanf 和 printf 依然有优化的空间,这就是本章所介绍的内容——输入和输出优化。
注意,接下来介绍的输入和输出优化均针对整型数据(函数实现读写),若要支持其他类型的数据(如浮点数),可自行按照本页面介绍的优化原理来编写代码。
更快的输入优化getchar()
原理
众所周知, getchar 是用来读入 1 byte 的数据并将其转换为 char 类型的函数,且速度很快,故可以用“读入字符——转换为整型”来代替缓慢的读入
每个整数由两部分组成——符号和数字,整数的 ‘+’ 通常是省略的,且不会对后面数字所代表的值产生影响,而 ‘-’ 不可省略,因此要进行判定。
10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入不应存在于整数中的字符(通常为空格)时,就可以判定已经读入结束
C 和 C++ 语言分别在 ctype.h 和 cctype 头文件中,提供了函数 isdigit , 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true ,否则返回 false 。对应的,在下面的代码中,可以使用 isdigit(ch) 代替 ch >= ‘0’ && ch <= ‘9’ ,而可以使用 !isdigit(ch) 代替 ch <‘0’ || ch> ‘9’。
代码实现输入函数read()
int read() {
int x = 0, w = 1;
char ch = 0;
//我们判断是否为十进制数字字符可以使用isdight()函数来判断,这里不再展示。
while (ch < '0' || ch > '9') { // ch 不是数字时
if (ch == '-') w = -1; // 判断是否为负
ch = getchar(); // 继续读入
}
while (ch >= '0' && ch <= '9') { // ch 是数字时,我们输入结束标志为空格或回车。
x = x * 10 + (ch - '0'); // 将新读入的数字’加’在 x 的后面
// 此处也可以使用 (x<<3)+(x<<1) 的写法来代替 x*10 即:x=(x<<3)+(x<<1)+(ch-'0');用位运算效率更高,这里相当于x*8+x*2+(ch-'0');
//利用ASCII码转换为对应数字。
ch = getchar(); // 继续读入
}
return x * w; // 数字 * 正负号 = 实际数值
}
- 举例 :我们输入数据时可以这样调用read函数:
num=read()
。
更快的输出优化putchar()
原理
同样是众所周知, putchar
是用来输出单个字符的函数,因此将数字的每一位转化为字符输出以加速,要注意的是,负号要单独判断输出,并且每次 %(mod)取出的是数字末位,因此要倒序输出。
代码实现
递归版
void write(int x) {
if (x < 0) { // 判负 + 输出负号 + 变原数为正数
x = -x;
putchar('-');
}
if (x > 9) write(x / 10); // 递归,将除最后一位外的其他部分放到递归中输出
putchar(x % 10 + '0'); // 已经输出(递归)完 x 末位前的所有数字,输出末位
}
由于递归的任务量比较大,我们可以利用栈来实现非递归。
非递归版
inline void write(int x) {
static int sta[35];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;//将取出的元素入栈。
} while (x);
while (top) putchar(sta[--top] + 48); //由于我们要输出字符,这里进行转换: 48 是 '0'
}
OK,这就是经典的输入输出的优化做法,通常cf榜上的大佬用的都是后面这种方法,这确实很快,比scanf和printf
快了约
1
/
3
1/3
1/3了吧。我自己常用的就是第一种优化方法,通常我会利用宏定义来避免这么繁琐,如:#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
那么,在主函数直接使用IOS;
语句即可实现优化功能。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!