蓝桥杯竞赛必备:C++输入输出全解析与性能优化实战
在算法竞赛的战场上,输入输出(I/O)是程序与外界沟通的唯一桥梁,其效率与正确性直接决定了代码的成败。无论是蓝桥杯、ACM-ICPC还是日常的OJ刷题,掌握C++中灵活高效的I/O处理技巧,是每一位选手从入门到精通的必修课。本文将从实战场景出发,深入剖析各类输入模式,并对比C风格与C++风格I/O的优劣,最后揭秘关键的性能优化秘籍,助你轻松应对海量数据挑战。
一、 算法竞赛中的四大输入场景精讲
面对千变万化的OJ题目,其输入格式可以归纳为几个经典模式。清晰识别这些模式,是写出鲁棒性代码的第一步。下图清晰地展示了这几种常见情况:

1. 单组测试用例:最简单的起点
这是最基础的输入形式,程序只需处理一组数据。例如,计算一个简单表达式:
#include
using namespace std;
int main()
{
int a, b, c;
cin >> a >> b >> c;
cout << (a + b) / c << endl;
return 0;
}
对应的输入输出示例如下:
本题目是单组测试用例的题目,代码首先创建了三个整型变量,分别作为计算式中的三个操作数。最后打印出计算结果即可。
再如判断“与7无关的数”,其核心逻辑在于数字的整除和数位判断:
#include
using namespace std;
int main()
{
int n = 0;
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;
}
输入输出为:
上述代码需要打印出一定范围的与7无关的数,本题目也是一个单组测试用例。首先需要我们遍历小于n的所有数,接下来我们要判断每次循环的数是否与7无关,如果无关则累加在创建的变量上即可。
2. 多组测试用例:循环的艺术
多数竞赛题包含多组数据,需要循环读取。这又细分为组数已知和组数未知两种情况。
- 组数已知:通常首先输入一个整数T,表示测试用例数量。例如多组A+B问题:
#include
using namespace std;
int main()
{
int n = 0;
int a = 0, b = 0;
cin >> n;
while (n--)
{
cin >> a >> b;
cout << a + b << endl;
}
return 0;
}
输入输出:
本题目是多组输入的测试用例,需要输入n次。所以利用while循环,将输入的a+b打印在屏幕上。最终只要输入n次,就会打印出n个结果。
斐波那契数列问题也属于此类,通常先输入需要计算的项数:
#include
using namespace std;
#include
using namespace std;
int main()
{
int n = 0;
int a = 0;
int i = 0;
//计算好前30个斐波那契数,存储到ret数组中
//需要第⼏个就从下标⼏的位置去取
int ret[40] = {0, 1, 1};
for(i = 3; i < 30; i++)
{
ret[i] = ret[i-1] + ret[i-2];
}
cin >> n;
int z = 0;
while (n--)
{
cin >> a;
cout << ret[a] << endl;
}
return 0;
}
输入输出:
本题目有多组测试用例,需要输入n次数据。首先斐波那契数列每位上的数都是前面两个数相加,所以需要先列出分段函数的表达式,通过递归的方式得到第n位上的数值。因为本题有多组测试用例,所以需要用到while循环,每次输入一个数,都对应一个斐波那契数被打印出来。
- 组数未知:输入直到文件结束(EOF)。这是非常常见的模式,需要熟练掌握。例如持续读取A+B:
#include
using namespace std;
int a, b;
int main()
{
while (cin >> a >> b)
{
cout << a + b << endl;
}
return 0;
}
输入输出:
上述代码遇到的是多组测试用例且测试用例数未知的情况,上述代码将cin>>a作为while循环判断的条件原因是:cin函数会返回一个流对象的引用,即cin函数本身。在C++语言中,流对象cin可以作为布尔值来检查流的状态。如果流的状态良好(没有发生错误),流的布尔值就为true。否则布尔值为false。在while循环中,循环的条件部分检查cin流的状态。如果成功读取到值就会返回true循环继续。如果读取失败(遇到输入结束符或无法读取目标次数),cin将会返回false,循环终止。
对于这类问题,while(cin >> a >> b)或while(scanf("%d%d", &a, &b) != EOF)是标准写法。
3. 特殊值终止:优雅的结束信号
当输入遇到某个特定值(如0, -1)时终止,而非EOF。例如,输入一系列数字,遇0停止并统计:
//代码1
#include
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
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;
}
输入输出示例:
本题目具有多组测试数据,当输入?符号时,停止测试数据。所以需要用到循环,当输入函数读取到字符?时,循环终止。最终返回每种字符的数量。下面代码是另一种处理形式:
本题目也可以用上一章节学到的函数getline,将结束标志设置成问号即可。或者像问题这样尾删问号,随后读取一行的所有数据也可以达到此题的多组测试用例的目的。
多组A+B以0 0结束也是典型例子:
#include
using namespace std;
int main()
{
int a = 0, b = 0;
while (cin >> a >> b, a && b)
{
cout << a + b << endl;
}
return 0;
}
输入输出:
本题目规定的测试用例结束的标志是两数字同时为0,所以在while循环中带上这个条件即可应对多组测试用例。
二、 输入处理中的两大实用技巧
技巧一:含空格字符串的读取
使用普通的cin >> str遇到空格会停止,这对于需要读取整行(包括空格)的场景是个障碍。C++提供了getline(cin, str)函数来解决。
编程小技巧:根据我们现在掌握的知识,含有空格的字符串,如果读取就有fgets、scanf、getchar、getline函数来应对。但是有时候根据题目的情况:不一定非要完整的带空格的字符串,而是想要处理用空格隔开的字符。为了更加方便,也避免处理空格字符的问题,我们就需要类似于下方代码的解法:
例如,统计一行字符串中数字字符的个数:
解法一(推荐):使用getline读取整行
#include
#include
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;
}
输入输出:
上述代码通过我们学习的getline函数读取一行输入的数据,随后遍历字符串进行下一步的判断。因为这个函数并没有忽略空白字符,所以并没有达到简便的目的,所以更改为下方代码:
解法二:使用cin逐个单词读取(适用于空格分隔的多个单词)
#include
#include
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;
}
输入输出:
利用cin函数的返回值,当读取失败时循环终止。每次循环统计字符是否为数字,是的话将计数器加一,否则进入下次循环。
技巧二:数字的“字符串视角”处理
数字在输入输出流中本质是字符序列。有时,将数字视为字符串处理会更方便,尤其是涉及数位操作时。编译器会根据变量类型自动完成转换。
int num = 0;
cin >> num;//输⼊123, 就被解析成整数
string s;
cin >> s; //输⼊123, 就被解析成字符串
经典例题“小乐乐改数字”:将一个整数的每一位,如果是奇数则改为1,偶数则改为0。

解法一:数学方法(整除和取模)
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
int ret = 0;
int i = 0; // 标记⼀下此时处理到多少位
while (n)
{
if (n % 10 % 2 == 1) // 如果这⼀位是奇数
{
ret += pow(10, i); // pow(a, b) 求 a的b次⽅
}
n /= 10; // 把最后⼀位⼲掉
i++; // 去判断下⼀位
}
cout << ret << endl;
return 0;
}
输入输出:
当我们将123当作整数来解析时,需要我们利用循环得到每一位的数字,随后将其改变后乘对应的权。最后将所有的权进行相加。得到的就是题目要求的结果。
解法二:字符串方法(直接操作每一位字符)
#include
#include
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;
}
输入输出:
但是当我们将123当作字符串来接受,我们只需要根据下标进行更改每一位上的值,随后打印的时候将字符串类型强制类型转换位整型即可。
这种“字符串视角”在Java(使用`String`)、JavaScript/TypeScript(使用`toString`和`split`)以及Go(使用`strconv`包)中同样是非常实用的技巧。
三、 C风格 vs C++风格:输入输出的终极对决
scanf/printf(C风格)与cin/cout(C++风格)是C++程序员的两大武器,各有优劣。
1. 格式控制与易用性对比
- C风格 (
printf/scanf):需要显式指定格式符(如%d, %lf, %s),格式化能力强,但容易因类型不匹配出错。 - C++风格 (
cout/cin):类型安全,编译器自动推导类型,使用方便,但复杂格式控制稍显繁琐。
例如,控制浮点数输出精度和宽度:
#include
#include
using namespace std;
int main()
{
float a = 3.50;
double d = 16.50;
cout << "cout: " <
输出结果对比:

根据上面的代码可以看出:
- cout默认不会输出六位小数,自动忽略小数后多余的0;printf函数打印浮点数时,会默认打印小数点后6位。
- cout函数在输出的时候不需要指定格式,printf函数则需要指定具体的格式
2. ⚠️ 性能差异:竞赛中的关键考量
核心结论:C风格的scanf/printf通常比C++的cin/cout更快。在数据量极大(如10^5以上)时,这种差异可能导致超时(TLE)。
案例一:数字游戏
使用cin/cout可能导致效率瓶颈:
#include
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;
}
运行结果可能因平台而异,但趋势一致:

换用scanf/printf后:
#include
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;
}

案例二:求第k小的数
同样,大数据量下cin/cout表现不佳:
#include
#include
#include
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;
}

使用scanf/printf则能高效通过:
#include
#include
#include
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函数就能正确的通过。这是因为两函数的性能差异导致的。接下来我们讲解下C++语言的输入输出函数的性能优化:
[AFFILIATE_SLOT_2]
四、 C++ I/O性能优化两大神器
如果你偏爱cin/cout的简洁,又担心性能,这两行代码可以拯救你。
1. ios::sync_with_stdio(false)
作用:关闭C++标准流与C标准流的同步。默认同步保证了混用cin/cout和scanf/printf时的安全性,但带来了开销。
效果:关闭后,cin/cout速度可大幅提升,接近scanf/printf。
⚠️ 警告:一旦关闭,切勿混用C和C++的I/O函数,否则会导致输出顺序混乱或未定义行为。
2. cin.tie(0)
作用:解除cin和cout之间的绑定。默认情况下,每次cin操作前都会刷新cout的缓冲区,以确保提示信息能先显示。解除绑定后,减少了不必要的刷新。
效果:进一步提升输入效率。
优化代码通常放在main函数开头:
#include
using namespace std;
int main()
{
ios::sync_with_stdio(false); // 取消C风格I/O的同步
cin.tie(0); // 解除cin与cout的绑定
int number;
cout << "请输入一个数字: ";
cin >> number;
cout << "您输入的数字是: " << number << endl;
return 0;
}
五、 优化实战与场景选择指南
让我们通过一个具体案例,看看优化前后的巨大差异。
未优化的C++代码(可能较慢):
#include
#include
#include
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;
}
运行时间可能较长:

使用C风格I/O(速度快):
#include
#include
#include
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;
}

优化后的C++代码(速度媲美C风格):
#include
#include
#include
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;
}
优化后性能显著提升:

根据上面的优化可以看出,优化后的C++语言的输入输出函数竟然比C语言的输入输出函数效率还高。
所以在今后在使用C还是C++输入输出函数抉择时,如果需要追求性能就用C语言的输入输出函数,或者优化版本的C++语言的输入输出函数;如果不追求性能,直接使用C++语言的输入输出函数即可。
场景选择建议
- 小数据量(
10^4以内):随意选择,cin/cout的便利性优先。
- 大数据量(
10^5 ~ 10^6):
— 首选:scanf/printf。
— 次选:使用ios::sync_with_stdio(false); cin.tie(0);优化后的cin/cout。
- 海量数据(
10^6以上):可能需要更快的“快速读入”函数(基于getchar),这在后续算法专题中会涉及。
理解这些I/O背后的机制,不仅能帮助你在蓝桥杯等竞赛中游刃有余,也是深入理解C++、Java(Scanner vs BufferedReader)、Go(bufio包)等语言I/O模型的重要基础。掌握它们,让你的代码在效率与优雅之间找到最佳平衡点。
浙公网安备 33010602011771号