【算法竞赛】C/C++ 的输入输出你真的玩会了吗?

【算法竞赛】C/C++ 的输入输出你真的玩会了吗?
在这里插入图片描述
🔭 个人主页:散峰而望

《C语言:从基础到进阶》《编程工具的下载和使用》《C语言刷题》《算法竞赛从入门到获奖》《人工智能AI学习》《AI Agent》

愿为出海月,不做归山云


🎬博主简介

在这里插入图片描述
请添加图片描述

文章目录


前言

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

1. OJ(online judge)题目输入情况汇总

在竞赛的 OJ 题目中,一般关于输入场景总结为下面四类:

在这里插入图片描述

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

1.1 单组测试用例

  1. 计算 (a+b)/c 的值
在这里插入图片描述

参考代码:

#include<iostream>usingnamespace std;intmain(){int a, b, c; cin >> a >> b >> c; cout <<(a + b)/ c << endl;return0;}
  1. 与 7 无关的数
在这里插入图片描述
思路
先输入 n,之后循环产生 1~n 的数字,接着找出与 7 无关的正整数,最后求平方和。
那么如何产生与 7 无关的正整数呢?
我们不如先找与 7 相关的正整数,然后取反就能得到与 7 无关的正整数。
又由题目知道 n < 100,所以只需在 100 内的数字进行判断。
即与 7 相关的条件有:被 7 整除,%10 == 7(个位),/10 == 7(十位)

参考代码:

#include<iostream>usingnamespace std;intmain(){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;return0;}

1.2 多组测试用例

1.2.1 测试数据组数已知

  1. 多组输入a+b II
在这里插入图片描述

参考代码:

#include<iostream>usingnamespace std;intmain(){int n;//表示数据组数 cin >> n;int a, b;//每行输入的a,b值 while(n--){ cin >> a >> b; cout << a + b << endl;}return0;}
  1. 斐波那契数列
斐波那契数列:1 1 2 3 5 8 13 21 34 55 …
特点:前两个数的和是第三个数
在这里插入图片描述
在这里插入图片描述

斐波那契有多种做题办法

参考代码:

  • 正常的循环嵌套:
#include<iostream>usingnamespace std;intmain(){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;}}
  • 由题目可知,题目把所求范围限制在 1~30 之间,所以提前算出前 30 个斐波那契数,存储下来输入 Q 后,直接拿取第 a 个斐波那契数:
#include<iostream>usingnamespace std;intmain(){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;}return0;}
  • 还有一种方法是递归,后面会进行讲解
  1. 制糊串
在这里插入图片描述
在这里插入图片描述
思路:输入 s,t,qq 次询问
while(q–)
{ }
处理一组数据
输入 l1,r1,l2,r2取出子串 s1,t1比较子串的大小,并输出
字符串的比较 - 支持关系运算的比较

参考代码:

#include<iostream>#include<string>usingnamespace std;intmain(){ 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;}elseif(s1 > t1){ cout <<"erfusuer"<< endl;}else{ cout <<"ovo"<< endl;}}return0;}
编程技巧:
题目中说"有 q 次询问",意思是程序要处理q组测试数据,(也就是对应 q 次循环),我们要针对每次询问,给出一个结果。
其实就是之前的单组测试变成了 q 组测试,在之前的代码上套一层 while 循环即可。当有 q 次询问的时候,while(q–) 是非常方便的方式。然后就按照单组输入的方式处理每组输入的数据就好了。

1.2.2 测试数据组未知

  1. 多组输入a+b
在这里插入图片描述

参考代码:

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

参考代码:

#include<iostream>usingnamespace std;intmain(){int n;while(cin >> n){for(int i =1; i <= n; i++){for(int j =1; j <= i; j++){ cout << j <<" ";} cout << endl;}}}
  1. 定位查找
在这里插入图片描述
思路:先输入 n,指明要存储多少个数
2.再输入 m,并和输入的数组中的数据进行比较查找
3.只要查找的一出现就输出并跳出循环,如果没有就输出 No

参考代码:

#include<iostream>usingnamespace std;constint N =25;int arr[N];intmain(){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;}}return0;}

1.2.3 特殊值结束测试数据

  1. 字符统计
在这里插入图片描述

参考代码:

//代码1#include<iostream>usingnamespace std;intmain(){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++;}elseif(ch >='0'&& ch <='9'){ Digits++;}else{ Others++;}} cout <<"Letters="<< Letters << endl; cout <<"Digits="<< Digits << endl; cout <<"Others="<< Others << endl;return0;}
//代码2#include<iostream>usingnamespace std;intmain(){ 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++;elseif(ch >='0'&& ch <='9') Digits++;else Others++;} cout <<"Letters="<< Letters << endl; cout <<"Digits="<< Digits << endl; cout <<"Others="<< Others << endl;return0;}

这里判断大小写字母数字可以用一种简单的方法来判断,运用函数:

if(islower(ch)||isupper(ch)) Letters++;elseif(isdigit(ch)) Digits++;else Others++;

当然判断字母还能再简化一下

if(isalpha(ch)) Letters++;elseif(ch >='0'&& ch <='9') Digits++;else Others++;

可以拓展了解一下 :isalphaisdigit

  1. 多组数据a+b III
在这里插入图片描述

参考代码:

#include<iostream>usingnamespace std;intmain(){int a =0, b =0;while(cin >> a >> b, a && b){ cout << a + b << endl;}return0;}
这里的逗号表达式简单介绍一下:从左往右依次计算整个表达式的结果是最后一个表达式的结果

OK 了,有关OJ(online judge)题目输入情况的所有情况都讲完了,希望各位友友们在做题中能够有着清晰的判断。

2. 输入时特殊技巧

2.1 含空格字符串的特殊处理方式

根据我们现在掌握的知识,含空格的字符串,如要读取有 fgets、scanf、getchar、getline 四种方式解决,但是有时候,根据题目的情况,不一定非要完整的读取这个带空格的字符串,而是将字符串中空格隔开的每一个字符串,当做一个单词处理更方便,也避免了读取带空格字符串的各种问题。

练习:统计数字字符个数

在这里插入图片描述

这里我们有两种做法:

整个字符串一次性读取,然后处理字符串,使用 getline使用逐个单词的方式处理 cin
  1. 法一:读取整个带空格的字符串分析
#include<iostream>#include<string>usingnamespace std;intmain(){ string s;getline(cin, s);int ret =0;for(auto ch : s){if(ch >='0'&& ch <='9'){ ret++;}} cout << ret << endl;return0;}
  1. 法二:按照多个单词分析
#include<iostream>#include<string>usingnamespace std;intmain(){ string s;int cnt =0;while(cin >> s){for(auto c : s){if(c >='0'&& c <='9') cnt++;}} cout << cnt << endl;return0;}

同时由上面的部分介绍,我们可以把

if(ch >='0'&& ch <='9')

修改为

if(isdigit(ch))

2.2 数字的特殊处理方式

当我们程序运行的时候,在控制台输入123 的时候,这时的 123 是三个字符,123 是一个字符序列,程序会根据代码中的数据类型,可能将123解析成整型,也可能将 123 解析成字符串。

比如:

int num =0; cin >> num;//输入123, 就被解析成整数  string s; cin >> s;//输入123, 就被解析成字符串

这里的解析的方式,主要是依赖编译器对变量类型的识别,根据类型再将读取字符串数据转化成对应类型的数据。

我们在写代码的时候,应该根据实际的情况,来决定如何处理输入的内容。

练习:小乐乐改数字

在这里插入图片描述
在这里插入图片描述
  1. 法一:当做整数读取
输入一个整数 n获取 n 的每一位
% 10,判断是奇数(1)还是偶数(2)
/ 10,求后面的数字
3.输出转换后的整数
#include<iostream>#include<cmath>usingnamespace std;intmain(){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;return0;}
  1. 法二:当做字符串处理
由于读取的是字符,所以 1,2,3 不能当作字符来判断,而是用 ASCII 判断
又 ‘0’ = 48,‘1’ = 49,‘2’ = 50,‘3’ = 51 … 发现 ASCII 为偶数时对应的正好为偶数,奇数时正好为奇数,故可以 %2 判断。
#include<iostream>#include<string>usingnamespace std;intmain(){ 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;// 转换成数字输出 return0;}
这里说一下代码的一些函数pow(a, b) 求 a 的 b 次方stoi 是 C++11 标准引入的字符串转换函数,用于将字符串转换为整数

3. scanf/printf 和 cin/cout的对比

scanf 和 printf 是 C 语言中的标准输入输出函数,而 cin 和 cout 是 C++ 语言中的标准输入输出流对象。它们各自有优缺点,整体上来说 cin 和 cout 会更加方便,但有时候我们也不得不使用 scanf 和 printf。

3.1 格式控制差异

  • scanf 和 printf 不能自动识别输入数据的类型,需要手动指定格式字符串,容易出现格式错误。开发者需要确保格式字符串与变量类型匹配,否则会导致未定义行为。
  • cin 和 cout 会根据变量类型自动处理输入输出,避免格式化错误。相对 scanf 和printf 而且,C++的 cin 和 cout 更加易用。
  • scanf 和 printf:格式化输出更精确直观,特别适合复杂格式的输入输出,比如:在要求指定格式输出的时候,printf 函数就比 cout 更加方便和灵活。
#include<cstdio>#include<iostream>usingnamespace std;intmain(){float a =3.50;double d =16.50; cout <<"cout: "<<a <<" "<< d <<endl;printf("printf: %f %lf\n", a, d);return0;}

输入结果如下:

在这里插入图片描述
区别:cout 默认不会输出六位小数,自动忽略小数点后多余的 0,printf 函数打印浮点
数的时候,小数点默认打印 6 位。cout 在输出的时候不需要指定格式,printf 则需要明确的格式。

3.2 性能差异

3.2.1 案例演示

结论: scanf 和 printf 通常比 cin 和 cout 快。
原因: cin 和 cout 由于要考虑兼容 C 语言的输入和输出,封装实现的更加复杂,通常比 scanf 和 printf 稍慢,但这种差异在大多数应用场景中可以忽略不计。但是在竞赛的题目中,尤其是当输入、输出数据量较大时,使用 cin 和 cout 完成输入输出,经常会出现 Time Limit Exceeded 的情况。而 scanf 和 printf 就不存在类似的问题。下面给大家准备了两个案例。

案例一:数字游戏

代码展示:

  1. 使用 cin 和 cout:
#include<iostream>usingnamespace std;int t, x;intmain(){ 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;}return0;}

运行结果展示:

在这里插入图片描述
  1. 使用 scanf 和printf:
#include<iostream>usingnamespace std;int t, x;intmain(){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);}return0;}

运行结果展示:

在这里插入图片描述

案例 2:求第 k 小的数

  1. 使用 cin 和 cout:
#include<iostream>#include<cstdio>#include<algorithm>usingnamespace std;constint N =5000010;int arr[N];intmain(){int n, k; cin >> n >> k;for(int i =0; i < n; i++){ cin >> arr[i];}sort(arr, arr + n); cout << arr[k]<< endl;return0;}

运行结果展示:

在这里插入图片描述
  1. 使用 scanf 和printf:
#include<iostream>#include<cstdio>#include<algorithm>usingnamespace std;constint N =5000010;int arr[N];intmain(){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;return0;}

运行结果展示:

在这里插入图片描述

我们可以看到这两个案例中,输入的数据量都比较大,在输入数据的时候如果使用cin,都会出现超时的问题,但是换成是 scanf 的方式就能正确的通过。这就是因为两者性能上的差异导致的。那为什么 cin/cout 的性能要低于 scanf/printf 呢?有什么办法能优化吗?

后面会单独对这个问题进行讲解。

总结一下其实就是 2 个点:

  1. C++ 中为了支持混合使用 cin/cout 和 scanf/printf,C++ 标准库默认会将 cin、cout
    等 C++ 流对象与 stdin、stdout 等 C 标准库的流对象同步在一起。这种同步操作意味着每次使用 cin或cout 时,都会自动刷新 C 标准库的缓冲区,以保 C++ 和 C 的 I/O 是一致的。这就导致了性能的下降。
  2. 在默认情况下,cin 和 cout 之间存在一种绑定关系。这种绑定意味着,每当从 cin 读取数据时,任何之前通过 cout 输出的内容都会被强制刷新到屏幕上。这种绑定也可能导致性能问题,特别是在需要频繁读取大量数据的情况下。

3.2.2 优化方案和演示

通过下面这个案例我们看一下 scanf 和 cin 的差异,然后再看看如何优化。

#include<iostream>#include<ctime>#include<cstdio>usingnamespace std;constint num =10000000;intmain(){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;return0;}

运行演示结果:

在这里插入图片描述
#include<iostream>#include<ctime>#include<cstdio>usingnamespace std;constint num =10000000;intmain(){//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;return0;}

运行演示结果:

在这里插入图片描述

如果对 cin 进行优化呢?

#include<iostream>#include<ctime>#include<cstdio>usingnamespace std;constint num =10000000;intmain(){ 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;return0;}

运行演示结果:

在这里插入图片描述

所以未来我们在使用 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++ 的输入输出有着不一样的理解。

同时愿诸君能一起共渡重重浪,终见缛彩遥分地,繁光远缀天



Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk