蓝桥杯竞赛必备: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)是标准写法。

[AFFILIATE_SLOT_1]

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: " <

输出结果对比:

        根据上面的代码可以看出:

  1. cout默认不会输出六位小数,自动忽略小数后多余的0;printf函数打印浮点数时,会默认打印小数点后6位。
  2. 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/coutscanf/printf时的安全性,但带来了开销。
效果:关闭后,cin/cout速度可大幅提升,接近scanf/printf
⚠️ 警告:一旦关闭,切勿混用C和C++的I/O函数,否则会导致输出顺序混乱或未定义行为。

2. cin.tie(0)

作用:解除cincout之间的绑定。默认情况下,每次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^{6}
  • 大数据量(10^5 ~ 10^6
    — 首选:scanf/printf
    — 次选:使用ios::sync_with_stdio(false); cin.tie(0);优化后的cin/cout10^{9}
  • 海量数据(10^6以上):可能需要更快的“快速读入”函数(基于getchar),这在后续算法专题中会涉及。

理解这些I/O背后的机制,不仅能帮助你在蓝桥杯等竞赛中游刃有余,也是深入理解C++Java(Scanner vs BufferedReader)、Go(bufio包)等语言I/O模型的重要基础。掌握它们,让你的代码在效率与优雅之间找到最佳平衡点。

posted on 2026-02-27 11:15  blfbuaa  阅读(37)  评论(0)    收藏  举报