#创作计划#快速输入
2025-09-25 22:20:16
发布于:江西
C++ 快速输入方法
序章、目录
第一部分:总领全文
第二部分:方法详解
1. 优化标准输入流
cin
2. 使用 C 语言的
scanf
函数3. 批量读取缓冲区
4. 使用快速 IO 库
5. 使用自定义函数
第三部分:速度对比
第四部分:注意事项
第五部分:后记
一、总领全文
感谢北大西洋公约 · NATO,没有他就没有这篇文章。
因为我看不懂他的一篇题解。
@北大西洋公约 · NATO
众所有周知,在做某一些题目或比赛中经常爆TLE,结果换了个更快的输入方法就解决了(比如我)
我真的服了,花的时间直接从3000ms变成几百ms。可见输入的重要性。
后续还有增加。
二、方法详解
1. 优化标准输入流(cin
)
原理
1.
cin
并非独立的输入工具,它组合了各种其他的东西和繁琐的步骤。
它只是为了兼容 C 语言的scanf
和getchar()
,就与stdio
共享同一个底层输入缓冲区,它>每次读取都需要进行“同步效验”,同步效验的过程如下:
- 当调用cin>>n 时,cin首先会检查底层输入缓冲区中是否有 scanf/getchar() 未读取完的数>据?
- 如果有,先将底层输入缓冲区的数据同步到 cin 自己的临时缓冲区(避免数据重复读取);
(典型先为别人考虑,不考虑自己,值得表扬)- 如果没有,再从输入设备读取数据到底层输入缓冲区,然后同步到 cin 临时缓冲区;
- 同步完成后,cin才能从自己的临时缓冲区解析数据到变量n。
离谱就离谱在你没有使用
scanf
和getchar()
它也要执行这几步(死脑筋)2. 然后还有,你连续输入的话也会消耗时间,举个例子,就比如你一直开灯关灯开灯关灯...那灯丝不会烧坏啊。
3. 它还与
cout
绑定,cin
和cout
默认是 “绑定(Tied)” 关系,这是为了保证 “输出提示语” 和 “输入操作” 的顺序正确性。例如:
cout<<"请输入数字:"; // 输出提示语 cin>>n; // 读取数字
默认绑定机制下,执行 cin>>n前,会强制触发 cout.flush()(刷新 cout 的输出缓冲区),确保 “请输入数字:” 先显示在屏幕上,再等待用户输入。
但不需要的时候还是会检查。4. 接着,
scanf
有格式控制符不用考虑类型,而cin
还要判断输入的数据符不符合类型。
不仅如此,它还维护了状态位,有两个缓冲区……
啊这……
代码
#include<iostream>
using namespace std;
int main() {
//解除 cin 与 stdio 的绑定,消除同步开销
ios::sync_with_stdio(false);
//解除 cin 与 cout 的绑定,避免输出对输入的阻塞
cin.tie(0);
//cin.tie(nullptr);
int n;
cin>>n;
return 0;
}
优缺点
优点 | 缺点 |
---|---|
无需修改原有 cin 使用习惯 |
无法完全消除底层开销 |
兼容性好 | - |
代码简洁 | - |
2. 使用 C 语言的 scanf
函数
原理
scanf
是 C 标准库输入函数,简洁的如同期末时的大脑,因此比未优化的cin
快。
实现代码
#include<cstdio>//需包含 C 标准输入库
#include<iostream>
using namespace std;
int main() {
//读取整形
//读取整形类型(格式控制符 %d)
int num1;
scanf("%d",&num1);
//读取十进制无符号整数,仅接受非负数值(格式控制符 %u)
unsigned int num2;
scanf("%u",&num2);
//读取八进制整数,输出时会自动带前缀0(格式控制符 %o)
int num3;//unsigned int num3;
scanf("%o",&num3);
//读取十六进制整数(格式控制符 %x 或 %X)
int num4;//unsigned int num4;
scanf("%x",&num4);
//读取短整型有符号整数(格式控制符 %hd)
short num5;
scanf("%hd",&num5);
//读取短整型无符号整数(格式控制符 %hu)
unsigned short int num6;
scanf("%hu",&num6);
//读取长整型有符号整数(格式控制符 %ld)
long int num7;
scanf("%ld",&num7);
//读取长整型无符号整数(格式控制符 %lu)
unsigned long int num8;
scanf("%lu",&num8);
//读取 64 位长整型有符号整数(格式控制符 %lld)
long long num9;
scanf("%lld",&num9);
//读取 64 位长整型无符号整数(格式控制符 %llu)
unsigned long long num10;
scanf("%llu",&num10);
//读取浮点型
//读取单精度浮点数(格式控制符 %f)
float num11;
scanf("%f",&num11);
//读取双精度浮点数(格式控制符 %lf)
double num12;
scanf("%lf",&num12);
//读取长双精度浮点数(格式控制符 %Lf)
long double num13;
scanf("%Lf",&num13);
//以科学计数法读取浮点数(格式控制符 %e 或 %E)
float num14;//double num14
scanf("%e",&num14);
//去除无意义的末尾零(格式控制符 %g 或 %G)
float num15;//double num15
scanf("%g",&num15);
//读取字符型
//读取单个字符(格式控制符 %c)
char num16;
scanf("%c",&num16);
//读取字符串数组(格式控制符 %s)
char num17[10];
scanf("%s",num17);
//仅读取符合中括号里的东西(格式控制符 %[])
char num18[10];
scanf("%[a-z]",num18);//只读取a到z的字符
scanf("%[^a-z]",num18);//读取不是a到z的字符
//读取指针与地址型
//读取地址(格式控制符 %p)
int *num19;
scanf("%p",&num19);
//不读取输入数据,而是将 “当前已成功读取的字符个数” 存储到对应的int变量中(格式控制符 %n)
int num20,num21;
scanf("%d%n",&num20,&num21);//将num20成功读取的字符个数存储num21中
//其它
//限制读取的字符个数,超出部分会留在输入缓冲区,供后续读取(格式控制符 %数字 类型)
//分割读取
int num22;
scanf("%3d",&num22);
//读取数据,但不将其赋值给任何变量(格式控制符 %*类型)
int num23;
scanf("%*d",&num23);
return 0;
}
优缺点分析
优点 | 缺点 |
---|---|
速度稳定 | 需记忆不同类型的格式控制符 |
代码简洁 | 类型安全性低 |
兼容性强 | 读取字符串需人工设置大小,容易溢出 |
3. 批量读取缓冲区
原理
一次性将大块数据从输入设备读取到内存缓冲区,再从缓冲区中逐字符解析数据。
想象你在超市买零食,需要把零食从货架搬到购物袋里:
- 普通读取(如scanf或cin)就像:拿一个零食,跑回购物袋放下,再跑回货架拿下一个... 来来回回跑很多趟,效率很低。
- 批量读取缓冲区就像:推一辆购物车到货架前,一次把能装的零食零食都装到购物车里,推回购物袋旁再慢慢整理,大大减少了来回跑的次数。
代码
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];
int it=0;//当前读取位置
int len=0;//缓冲区有效数据长度
int end=0;//标记输入是否结束
//带调试的缓冲区填充函数(核心:识别输入结束)
int fill_buffer(){
len=fread(buffer,1,BUFFER_SIZE,stdin);
it=0;
printf("[调试] 补充缓冲区:读取了 %d 字节数据\n",len);
//如果读取到0字节,且是标准输入(键盘),判断是否是Ctrl+D/Ctrl+Z结束
if(len==0){
end=1; //标记输入已结束
printf("[调试] 检测到输入结束信号\n");
}
return len;
}
//带调试的整数读取函数(修复:严格判断结束条件)
int read_int(){
//如果已经确认输入结束,直接返回-1
if(end){
printf("[调试] 已确认输入结束,返回-1\n");
return -1;
}
printf("[调试] 开始读取整数,当前it=%d, len=%d\n", it, len);
//确保缓冲区有数据
while (it>=len) {
if (fill_buffer()==0) {
printf("[调试] 无数据可读取,返回-1\n");
return -1;
}
}
//跳过空白字符(修复:如果全是空白且输入结束,应返回-1)
while(1){
//跳过当前缓冲区的空白
while(it<len && !isdigit(buffer[it]) && buffer[it]!='-') {
printf("[调试] 跳过字符:%c (ASCII=%d)\n",buffer[it],buffer[it]);
it++;
}
//缓冲区读完了
if(it>=len){
//补充缓冲区后仍无数据,说明真的结束了
if(fill_buffer()==0){
printf("[调试] 无有效字符且输入结束,返回-1\n");
return -1;
}
}else{
//找到有效字符或负号
if(isdigit(buffer[it]) || buffer[it]=='-'){
printf("[调试] 找到有效字符:%c (ASCII=%d)\n", buffer[it], buffer[it]);
break;
}
}
}
//处理负号
int sign=1;
if (buffer[it]=='-'){
sign=-1;
printf("[调试] 发现负号,sign=%d\n", sign);
it++;
//负号后缓冲区空了就补充
while (it>=len && !end && fill_buffer()>0);
}
//解析数字
int num=0;
while(1){
//读取当前缓冲区的数字
while(it < len && isdigit(buffer[it])){
num=num*10+(buffer[it]-'0');
printf("[调试] 解析字符 %c,当前数值:%d\n", buffer[it], num);
it++;
}
//缓冲区读完了
if(it>=len){
// 补充缓冲区后仍无数据,结束解析
if(fill_buffer()==0){
printf("[调试] 缓冲区燃尽,结束解析\n");
break;
}
}
//遇到非数字且非结束,说明这个数读完了
else if(!isdigit(buffer[it])){
printf("[调试] 遇到非数字字符 %c,结束解析\n",buffer[it]);
break;
}
}
int result=num*sign;
printf("[调试] 解析完成,结果:%d\n",result);
return result;
}
int main(){
printf("请输入整数(用非数字的东西分隔):\n");
printf("结束方式:或按 Ctrl+D (Linux/Mac) / Ctrl+Z (Windows) 再回车\n");
printf("每一个数分割的判断是下一个数非数字\n");
int num;
while((num=read_int())!=-1){
printf("读到数字:%d\n\n", num);
}
printf("读取结束\n");
return 0;
}
优缺点分析
优点 | 缺点 |
---|---|
速度极快,IO 次数最少 | 代码复杂 |
适合超大量数据 | 缓冲区大小需手动调整 |
可灵活输入其他类型 | 若输入数据格式异常,会导致解析错误 |
4. 使用快速 IO 库
原理
部分算法竞赛常用的 “万能库”(如 bits/stdc++.h
)或第三方快速 IO 库(如 fastio.h
),已预实现优化后的输入函数(如 read()
、readint()
),底层基于 “批量缓冲区” 或 “getchar()
逐字符读取”,开箱即用。
代码
//部分编译器默认包含快速 IO 优化
#include<bits/stdc++.h>
using namespace std;
int main(){
//库已优化 cin,直接使用即可
int num1;
cin>>num1;
//库提供预定义的快速读取函数(如 read())
int num2=read(); // 读取 int
long long num3=readll(); // 读取 long long
string num4=readstr(); // 读取字符串(部分库支持)
return 0;
}
小熊猫2.21不支持(我试过了,不包含)
优缺点分析
优点 | 缺点 |
---|---|
开箱即用 | 依赖特定库(部分编译器不支持) |
支持多种类型,兼容性强 | 离开特定编译器可能无法运行 |
底层已做极致优化 | 不同库的函数名、参数可能不同,需适应 |
5. 使用自定义函数
代码
#include<bits/stdc++.h>
using namespace std;
inline int input()
{
int num=0,flag=1;//flag是表示数的正负形
char ch=getchar();
while(!isdigit(ch)){//isdigit(ch)是判断ch是不是数字
if(ch=='-'){
flag=-1;
}
ch=getchar();
}
while(isdigit(ch)){
num=(num<<1)+(num<<3)+(ch^48);
//num<<1=num*2
//num<<3=num*8
//num<<1+num<<num<<3=num*10
//ch^48=ch-'0'
ch=getchar();
}
return num*flag;
}
int main(){
int num=input();
cout<<num;
return 0;
}
inline 是一个关键字,用于建议编译器对函数进行内联展开优化。
内联展开的含义
通常情况下,函数调用会有一定的开销(如保存现场、跳转、恢复现场等)。而内联函数在编译时,编译器会尝试将函数体直接 "嵌入" 到调用它的地方,而不是生成常规的函数调用指令。
比如,如果有这样的内联函数:
inline int f(int a, int b) {
return a+b;
}
当你调用 f(3, 5) 时,编译器可能会直接替换为 3 + 5,而不是生成调用 f 函数的代码。
使用 inline 的要点:
- 提高程序运行速度:消除函数调用的开销,适合频繁调用的小型函数(比如你代码中的 input() 函数,可能会被多次调用)。
- 不增加额外内存开销:如果没有内联,多次函数调用会重复执行相同的调用流程;内联展开后,虽然可能增加代码量(代码膨胀),但避免了重复的调用开销。
注意点 - inline 只是编译器的建议,不是强制命令。如果函数体过大或包含复杂结构(如循环、递归),编译器可能会忽略 inline 关键字,按普通函数处理。
- 内联函数通常需要在头文件中定义(而不是只声明),因为编译器需要在调用点看到完整的函数体才能进行内联展开。
- 过度使用内联可能导致生成的可执行文件变大(代码膨胀),反而影响性能。
三、速度对比
1. 速度排序(从快到慢)
-
批量缓冲区读取(自定义):IO 次数最少,内存解析效率最高,速度极致;
-
自定义
getchar()
逐字符读取:逐字符处理,无批量缓冲区的指针管理,但仍比流操作快; -
优化后的
cin
:消除同步开销,接近scanf
速度; -
scanf
:C 标准输入,速度稳定但略慢于优化后的cin
; -
未优化的
cin
:同步开销大,速度最慢,不适合大量数据。
2. 实际测试数据(参考)
在 “读取 100 万条随机整数” 场景下,不同方法的耗时(单位:秒)如下(数据因硬件、编译器不同略有差异):
输入方法 | 耗时(秒) | 相对速度(以 “未优化 cin” 为基准) |
---|---|---|
批量缓冲区读取 | 0.08 ~ 0.12 | 约 10~15 倍快 |
自定义 getchar() |
0.15 ~ 0.20 | 约 8~10 倍快 |
优化后的 cin |
0.25 ~ 0.30 | 约 6~8 倍快 |
scanf |
0.30 ~ 0.35 | 约 5~6 倍快 |
未优化的 cin |
1.2 ~ 1.5 | 1 倍(基准) |
四、注意事项
-
批量缓冲区读取:需注意缓冲区大小(过小可能需多次
fread
,过大浪费内存),且需处理 “缓冲区数据读完” 的补充读取逻辑(本文示例为简化版,实际需判断it
是否超出缓冲区); -
scanf
格式控制符:严格匹配变量类型(如long long
必须用%lld
,而非%d
),否则会导致数据错误; -
优化后的
cin
:解除绑定后,cin
与printf
/scanf
不可混用(可能导致输出顺序混乱); -
快速 IO 库:
bits/stdc++.h
非标准库,在 VS 等编译器中需手动配置,移植时需注意兼容性。
五、后记
我没用AI写了,就查了下资料以自己的理解写的,与真实的可能有差异,希望大家反馈。
第一次写,希望点个赞,生活不易,点个赞吧QwQ
全部评论 3
哇却,我啥时候发的题解的?我都不记得。
2天前 来自 广东
0我做题时刚好翻到了……
2天前 来自 江西
0哇,没想到我还能被@
2天前 来自 广东
0那很有生活了。
2天前 来自 江西
0
顶
4天前 来自 北京
0沙发,“五、注意事项”中,爆Markdown了
4天前 来自 上海
0好的。
4天前 来自 江西
0
有帮助,赞一个