在算法竞赛中,输入输出(I/O)是程序与外部交互的基础。C 和 C++ 提供了强大的 I/O 机制,从 C 风格的 scanf/printf 到 C++ 的流式操作 cin/cout,看似简单,实则暗藏玄机。许多开发者在使用过程中可能会遇到缓冲区问题、格式控制陷阱或性能瓶颈。本文将结合实际 OJ 题目,梳理常见输入场景,并深入探讨性能差异与优化方案。
OJ 题目输入情况汇总
在竞赛环境中,输入场景通常可以归纳为以下几类,理解这些模式有助于快速构建解题框架。
单组测试用例
这类题目通常只包含一组数据,读取后计算并输出即可。
示例:计算 (a+b)/c 的值
#include<iostream>
using namespace std;
int main(){
int a, b, c;
cin >> a >> b >> c;
cout << (a + b) / c << endl;
return 0;
}
示例:与 7 无关的数
思路是先找出与 7 相关的正整数(被 7 整除、个位是 7、十位是 7),然后取反得到无关的数。由于 n < 100,只需遍历判断。
#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;
}
多组测试用例
测试数据组数已知
当题目明确告知有 N 组数据时,先读入 N,再循环处理。
示例:多组输入 a+b II
#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~30),预处理数组直接查表效率更高;若范围较大,则需动态计算。
#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;
}
测试数据组未知
当不知道有多少组数据时,通常利用输入流的返回值作为循环条件。
示例:多组输入 a+b
cin >> a >> b 会返回流对象引用,成功读取时为 true,遇到文件结束符或错误时为 false。
#include<iostream>
using namespace std;
int main(){
int a, b;
while(cin >> a >> b){
cout << a + b << endl;
}
return 0;
}
特殊值结束测试数据
有时题目会以特定数值(如 0 0)作为结束标志。
示例:多组数据 a+b III
利用逗号表达式,当且仅当 a 和 b 都不为 0 时继续循环。
#include<iostream>
using namespace std;
int main(){
int a = 0, b = 0;
while(cin >> a >> b, a && b){
cout << a + b << endl;
}
return 0;
}
输入时的特殊技巧
含空格字符串的处理
读取带空格的字符串时,cin 默认以空格为分隔符。如果需要完整读取一行,可使用 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;
}
数字的特殊处理
输入的数字既可以是整数类型,也可以是字符串。根据需求选择处理方式。
示例:小乐乐改数字
法一:当做整数读取,通过 % 10 和 / 10 逐位处理。
#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;
}
法二:当做字符串处理,利用 ASCII 码特性判断奇偶。
#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;
}
scanf/printf 和 cin/cout 的对比
scanf/printf 是 C 语言标准库函数,cin/cout 是 C++ 流对象。两者各有优劣。
格式控制差异
scanf/printf需要手动指定格式字符串,容易出现类型不匹配导致的未定义行为,但格式化输出更精确直观。cin/cout会根据变量类型自动处理,避免格式错误,易用性更强。
#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 默认忽略多余小数位,而 printf 默认打印 6 位小数。
性能差异
在数据量较大时,scanf/printf 通常比 cin/cout 快。这是因为 C++ 流对象为了兼容 C 语言,默认开启了同步机制,导致每次操作都涉及缓冲区刷新。
案例演示:
在处理大量数据时,直接使用 cin 可能导致超时(TLE)。例如在排序或位运算题目中,输入规模达到百万级时,性能差异尤为明显。
优化方案:
可以通过以下代码取消同步并解绑流,使 cin/cout 的性能接近 scanf/printf。
ios::sync_with_stdio(false); // 取消 C 语言输入输出缓冲区的同步
cin.tie(0); // 取消 cin 和 cout 的绑定
总结建议:
- 数据量较小(10^6 以内):使用习惯即可,推荐
cin/cout以保持代码简洁。 - 数据量较大(10^9 左右或严格时限):推荐使用
scanf/printf或优化后的cin/cout。 - 极端情况下:可考虑手写快速读写模板。
掌握输入输出的底层机制和陷阱,才能写出健壮高效的代码。从缓冲策略到格式控制,每个细节都可能成为调试时的关键。


