C/C++ 输入输出技巧与性能优化
C/C++ 在算法竞赛中的输入输出处理。涵盖 OJ 题目常见输入场景(单组、多组已知/未知、特殊值结束),含空格字符串及数字的特殊处理方式。对比了 scanf/printf 与 cin/cout 在格式控制和性能上的差异,分析了同步机制导致的性能瓶颈,并提供了 ios::sync_with_stdio(false) 等优化方案。旨在帮助开发者掌握高效健壮的 I/O 代码编写技巧。

C/C++ 在算法竞赛中的输入输出处理。涵盖 OJ 题目常见输入场景(单组、多组已知/未知、特殊值结束),含空格字符串及数字的特殊处理方式。对比了 scanf/printf 与 cin/cout 在格式控制和性能上的差异,分析了同步机制导致的性能瓶颈,并提供了 ios::sync_with_stdio(false) 等优化方案。旨在帮助开发者掌握高效健壮的 I/O 代码编写技巧。

在编程的世界中,输入输出(I/O)是与用户或外部系统交互的基础。C 和 C++ 提供了强大的 I/O 机制,从简单的 printf 和 scanf 到 C++ 的流式操作 cin 和 cout,看似简单,实则暗藏玄机。许多开发者在使用过程中可能会遇到缓冲区问题、格式控制陷阱、性能瓶颈,甚至安全性隐患。 本文将对 C/C++ 一些关于在输入输出的问题进行一一打击。
在竞赛的 OJ 题目中,一般关于输入场景总结为下面四类:

由于单独讲解可能体验不是那么深,所以接下来,我们就结合题目,给大家分别介绍。

参考代码:
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
cout << (a + b) / c << endl;
return 0;
}

思路: 先输入 n,之后循环产生 1~n 的数字,接着找出与 7 无关的正整数,最后求平方和。 那么如何产生与 7 无关的正整数呢? 我们不如先找与 7 相关的正整数,然后取反就能得到与 7 无关的正整数。 又由题目知道 n < 100,所以只需在 100 内的数字进行判断。 即与 7 相关的条件有:被 7 整除,%10 == 7(个位),/10 == 7(十位)
参考代码:
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int i = 1;
int sum = 0;
while(i <= n) {
if(i % 7 != 0 && i % 10 != 7 && i / 10 != 7) {
sum += (i * i);
}
i++;
}
cout << sum << endl;
return 0;
}

参考代码:
#include <iostream>
using namespace std;
int main() {
int n; //表示数据组数
cin >> n;
int a, b; //每行输入的 a,b 值
while(n--) {
cin >> a >> b;
cout << a + b << endl;
}
return 0;
}
斐波那契数列:1 1 2 3 5 8 13 21 34 55 … 特点:前两个数的和是第三个数


斐波那契有多种做题办法:
参考代码:
#include <iostream>
using namespace std;
int main() {
int n, a;
cin >> n;
while(n--) {
cin >> a;
int x = 1;
int y = 1;
int z = 1;
while(a > 2) {
z = x + y;
x = y;
y = z;
a--;
}
cout << z << endl;
}
}
#include <iostream>
using namespace std;
int main() {
int n, a, i;
int ret[35] = {0, 1, 1};
for(i = 3; i < 30; i++) {
ret[i] = ret[i - 1] + ret[i - 2];
}
cin >> n;
while(n--) {
cin >> a;
cout << ret[a] << endl;
}
return 0;
}


思路:输入 s,t,qq 次询问 while(q–) { } 处理一组数据 输入 l1,r1,l2,r2 取出子串 s1,t1 比较子串的大小,并输出 字符串的比较 - 支持关系运算的比较
参考代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
string s, t;
cin >> s >> t;
int q;
cin >> q;
int l1, r1, l2, r2;
while(q--) // 这种写法是常?的处理 q 次询问的方式
{
cin >> l1 >> r1 >> l2 >> r2;
string s1 = s.substr(l1 - 1, r1 - l1 + 1); // 注意这道题的字符串是从 1 开始计数的
string t1 = t.substr(l2 - 1, r2 - l2 + 1);
if(s1 < t1) {
cout << "yifusuyi" << endl;
} else if(s1 > t1) {
cout << "erfusuer" << endl;
} else {
cout << "ovo" << endl;
}
}
return 0;
}
⭐编程技巧: 题目中说"有 q 次询问",意思是程序要处理 q 组测试数据,(也就是对应 q 次循环),我们要针对每次询问,给出一个结果。 其实就是之前的单组测试变成了 q 组测试,在之前的代码上套一层 while 循环即可。当有 q 次询问的时候,while(q–) 是非常方便的方式。然后就按照单组输入的方式处理每组输入的数据就好了。

参考代码:
#include <iostream>
using namespace std;
int main() {
int a, b;
while(cin >> a >> b) {
cout << a + b << endl;
}
return 0;
}
📌cin >> a; 会返回一个流对象的引用,即 cin 本身。在 C++ 中,流对象 cin 可以被用作布尔值来检查流的状态。如果流的状态良好(即没有发生错误),流对象的布尔值为 true。如果发生错误(如遇到输入结束符或类型不匹配),布尔值为 false。在 while (cin >> a >> b) 语句中,循环的条件部分检查 cin 流的状态。如果流成功读取到 2 个值,cin >> a >> b 返回的流对象 cin 将被转换为 true,循环将继续。如果读取失败(例如遇到输入结束符或无法读取到 2 个值),cin >> a >> b 返回的流对象 cin 将被转换为 false,循环将停止。
参考代码:
#include <iostream>
using namespace std;
int main() {
int n;
while(cin >> n) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= i; j++) {
cout << j << " ";
}
cout << endl;
}
}
}

思路:先输入 n,指明要存储多少个数 2.再输入 m,并和输入的数组中的数据进行比较查找 3.只要查找的一出现就输出并跳出循环,如果没有就输出 No
参考代码:
#include <iostream>
using namespace std;
const int N = 25;
int arr[N];
int main() {
int n, m;
while(cin >> n) {
for(int i = 0; i < n; i++) {
cin >> arr[i];
}
cin >> m;
int i = 0;
for(i = 0; i < n; i++) {
if(m == arr[i]) {
cout << i << endl;
break;
}
}
if(i == n) {
cout << "No" << endl;
}
}
return 0;
}

参考代码:
//代码 1
#include <iostream>
using namespace std;
int main() {
int ch = 0;
int Letters = 0;
int Digits = 0;
int Others = 0;
while((ch = getchar()) != '?') {
if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
Letters++;
} else if(ch >= '0' && ch <= '9') {
Digits++;
} else {
Others++;
}
}
cout << "Letters=" << Letters << endl;
cout << "Digits=" << Digits << endl;
cout << "Others=" << Others << endl;
return 0;
}
//代码 2
#include <iostream>
using namespace std;
int main() {
string s;
int Letters = 0;
int Digits = 0;
int Others = 0;
getline(cin, s);
s.pop_back(); //去掉?
for(auto ch : s) {
if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) Letters++;
else if(ch >= '0' && ch <= '9') Digits++;
else Others++;
}
cout << "Letters=" << Letters << endl;
cout << "Digits=" << Digits << endl;
cout << "Others=" << Others << endl;
return 0;
}
这里判断大小写字母和数字可以用一种简单的方法来判断,运用函数:
if(islower(ch) || isupper(ch)) Letters++;
else if(isdigit(ch)) Digits++;
else Others++;
当然判断字母还能再简化一下
if(isalpha(ch)) Letters++;
else if(ch >= '0' && ch <= '9') Digits++;
else Others++;

参考代码:
#include <iostream>
using namespace std;
int main() {
int a = 0, b = 0;
while(cin >> a >> b, a && b) {
cout << a + b << endl;
}
return 0;
}
这里的逗号表达式简单介绍一下:从左往右依次计算整个表达式的结果是最后一个表达式的结果
OK 了,有关OJ(online judge)题目输入情况的所有情况都讲完了,希望各位友友们在做题中能够有着清晰的判断。
根据我们现在掌握的知识,含空格的字符串,如要读取有 fgets、scanf、getchar、getline 四种方式解决,但是有时候,根据题目的情况,不一定非要完整的读取这个带空格的字符串,而是将字符串中空格隔开的每一个字符串,当做一个单词处理更方便,也避免了读取带空格字符串的各种问题。
练习:统计数字字符个数

这里我们有两种做法:
整个字符串一次性读取,然后处理字符串,使用 getline 使用逐个单词的方式处理 cin
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
getline(cin, s);
int ret = 0;
for(auto ch : s) {
if(ch >= '0' && ch <= '9') {
ret++;
}
}
cout << ret << endl;
return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
int cnt = 0;
while(cin >> s) {
for(auto c : s) {
if(c >= '0' && c <= '9') cnt++;
}
}
cout << cnt << endl;
return 0;
}
同时由上面的部分介绍,我们可以把
if(ch >= '0' && ch <= '9')
修改为
if(isdigit(ch))
当我们程序运行的时候,在控制台输入 123 的时候,这时的 123 是三个字符,123 是一个字符序列,程序会根据代码中的数据类型,可能将 123 解析成整型,也可能将 123 解析成字符串。
比如:
int num = 0;
cin >> num; //输入 123, 就被解析成整数
string s;
cin >> s; //输入 123, 就被解析成字符串
这里的解析的方式,主要是依赖编译器对变量类型的识别,根据类型再将读取字符串数据转化成对应类型的数据。
我们在写代码的时候,应该根据实际的情况,来决定如何处理输入的内容。
练习:小乐乐改数字


输入一个整数 n 获取 n 的每一位 % 10,判断是奇数(1)还是偶数(2) / 10,求后面的数字 3.输出转换后的整数
#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n;
int i = 0, ret = 0;
cin >> n;
while(n) {
if(n % 10 % 2 == 1) {
ret += pow(10, i);
}
n /= 10;
i++;
}
cout << ret << endl;
return 0;
}
由于读取的是字符,所以 1,2,3 不能当作字符来判断,而是用 ASCII 判断 又'0' = 48,'1' = 49,'2' = 50,'3' = 51 … 发现 ASCII 为偶数时对应的正好为偶数,奇数时正好为奇数,故可以 %2 判断。
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
cin >> s;
for(int i = 0; i < s.size(); i++) // 数字字符与对应的数的奇偶一致
{
if(s[i] % 2) {
s[i] = '1';
} else {
s[i] = '0';
}
}
cout << stoi(s) << endl; // 转换成数字输出
return 0;
}
这里说一下代码的一些函数 pow(a, b) 求 a 的 b 次方 stoi 是 C++11 标准引入的字符串转换函数,用于将字符串转换为整数
scanf 和 printf 是 C 语言中的标准输入输出函数,而 cin 和 cout 是 C++ 语言中的标准输入输出流对象。它们各自有优缺点,整体上来说 cin 和 cout 会更加方便,但有时候我们也不得不使用 scanf 和 printf。
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
float a = 3.50;
double d = 16.50;
cout << "cout: " << a << " " << d << endl;
printf("printf: %f %lf\n", a, d);
return 0;
}
输入结果如下:

**区别:**cout 默认不会输出六位小数,自动忽略小数点后多余的 0,printf 函数打印浮点 数的时候,小数点默认打印 6 位。cout 在输出的时候不需要指定格式,printf 则需要明确的格式。
结论: scanf 和 printf 通常比 cin 和 cout 快。 原因: cin 和 cout 由于要考虑兼容 C 语言的输入和输出,封装实现的更加复杂,通常比 scanf 和 printf 稍慢,但这种差异在大多数应用场景中可以忽略不计。但是在竞赛的题目中,尤其是当输入、输出数据量较大时,使用 cin 和 cout 完成输入输出,经常会出现 Time Limit Exceeded 的情况。而 scanf 和 printf 就不存在类似的问题。下面给大家准备了两个案例。
案例一:数字游戏
代码展示:
#include <iostream>
using namespace std;
int t, x;
int main() {
cin >> t;
while(t--) {
cin >> x;
int ret = 0;
while(x) {
int count = 0, high = 0;
int tmp = x;
while(tmp) {//计算最右边的 1 代表的值
int low = tmp & -tmp;//如果 low 中剩余的 1 就是最后一个 1
//就是最左边的 1
if(tmp == low) {
high = low;
}
//去掉最右边的 1
tmp -= low;
count++;
}
if(count % 2 == 0) {
x -= high;
} else {
x ^= 1;
}
ret++;
}
cout << ret << endl;
}
return 0;
}
运行结果展示:

#include <iostream>
using namespace std;
int t, x;
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &x);
int ret = 0;
while(x) {
int count = 0, high = 0;
int tmp = x;
while(tmp) {//计算最右边的 1 代表的值
int low = tmp & -tmp;//如果 low 中剩余的 1 就是最后一个 1
//就是最左边的 1
if(tmp == low) {
high = low;
}
//去掉最右边的 1
tmp -= low;
count++;
}
if(count % 2 == 0) {
x -= high;
} else {
x ^= 1;
}
ret++;
}
printf("%d\n", ret);
}
return 0;
}
运行结果展示:

案例 2:求第 k 小的数
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000010;
int arr[N];
int main() {
int n, k;
cin >> n >> k;
for(int i = 0; i < n; i++) {
cin >> arr[i];
}
sort(arr, arr + n);
cout << arr[k] << endl;
return 0;
}
运行结果展示:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000010;
int arr[N];
int main() {
int n, k;
cin >> n >> k;
for(int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
sort(arr, arr + n);
cout << arr[k] << endl;
return 0;
}
运行结果展示:

我们可以看到这两个案例中,输入的数据量都比较大,在输入数据的时候如果使用 cin,都会出现超时的问题,但是换成是 scanf 的方式就能正确的通过。这就是因为两者性能上的差异导致的。那为什么 cin/cout 的性能要低于 scanf/printf 呢?有什么办法能优化吗?
后面会单独对这个问题进行讲解。
总结一下其实就是 2 个点:
通过下面这个案例我们看一下 scanf 和 cin 的差异,然后再看看如何优化。
#include <iostream>
#include <ctime>
#include <cstdio>
using namespace std;
const int num = 10000000;
int main() {
int i, x;
//freopen 是将 stdin 重定向到文件
//意思是 scanf 可以文件中读取数据
freopen("data.txt", "r", stdin);
clock_t t1, t2;
t1 = clock();
for(i = 0; i < num; i++) {
scanf("%d", &x);
}
t2 = clock();
cout << "Runtime of scanf: " << t2 - t1 << " ms" << endl;
return 0;
}
运行演示结果:

#include <iostream>
#include <ctime>
#include <cstdio>
using namespace std;
const int num = 10000000;
int main() {
//freopen 是将 stdin 重定向到文件
//意思是 cin 可以文件中读取数据
freopen("data.txt", "r", stdin);
int i, x;
clock_t t1, t2;
t1 = clock();
for(i = 0; i < num; i++) {
cin >> x;
}
t2 = clock();
cout << "Runtime of cin: " << t2 - t1 << " ms" << endl;
return 0;
}
运行演示结果:

如果对 cin 进行优化呢?
#include <iostream>
#include <ctime>
#include <cstdio>
using namespace std;
const int num = 10000000;
int main() {
ios::sync_with_stdio(false); //取消给 C 语言输入输出缓冲区的同步
cin.tie(0); //取消了 cin 和 cout 的绑定
freopen("data.txt", "r", stdin);
int i, x;
clock_t t1, t2;
t1 = clock();
for(i = 0; i < num; i++) {
cin >> x;
}
t2 = clock();
cout << "Runtime of cin: " << t2 - t1 << " ms" << endl;
return 0;
}
运行演示结果:

所以未来我们在使用 scanf / printf 和 cin / cout 抉择的时候,如果要追求性能那就使用 scanf / printf,或者优化版的 cin / cout,如果不追求性能,直接使用 cin / cout 就行。
如果输入的数据量比较小(10^6 以内)的话,用 cin 和 cout 或 scanf 和 printf 都行;但是输入的数据量比较大(10^9 左右)的话,更推荐使用 scanf 和 printf,避免因为输入输出的开销,导致代码超时;在大多数场景下 printf / scanf 和 cin / cout 的使用根据个人习惯进行选择 即可。
这里我们讨论了 scanf/printf 和 cin/cout 性能的差异,我们知道 scanf/printf 是更快 的,其实有一些极端情况下,输入输出的规模非常大的时候,scanf 和 printf 也不能满足的时候,会使用快速读写的方式,这个后期会讲。
掌握输入输出的底层机制和陷阱,才能写出健壮高效的代码。从缓冲策略到格式控制,每个细节都可能成为调试时的关键。
希望这篇文章能给各位朋友对 C/C++ 的输入输出有着不一样的理解。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online