我们都知道,C++里的输入输出方式有很多。一般OI比赛里用C的scanf/printf,它们效率高。新手入门用cin/cout,它们简单易用。也可以自己做快速读入/输出,效率更高。下面我们通过效率对比来看看效率差距。
效率对比
输入
输入方式 | 1 0 6 10^6 106 整数 |
---|---|
cin | 12.789 sec 12.789 \sec 12.789sec |
scanf | 3.615 sec 3.615 \sec 3.615sec |
快速读入(输入优化) | 2.025 sec 2.025\sec 2.025sec |
生成随机整数函数代码
inline void create(){
srand(time(NULL));
FILE *fp=fopen("in.txt","w");
register int i=1e7;
while(i--)
fprintf(fp,"%d\n",rand()%static_cast<int>(1e7));
fclose(fp);
}
完整测试代码
#include<iostream>
using namespace std;
#include<ctime>
#include<cstdio>
#include<cstdlib>
inline int R(){
register int x=0;register char c=getchar(),f=1;
for(;c<48||c>57;c=getchar())if(c==45)f=-1;
for(;c>47&&c<58;c=getchar())x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
const int hint=1e6;
int main(){
// create();
srand(time(NULL));
clock_t beg;
register int i=hint,n;
puts("输入速度测试:");
freopen("in.txt","r",stdin);
beg=clock();
while(i--)
cin>>n;
printf("cin speed: %f sec\n",(clock()*1.0f-beg*1.0f)/static_cast<float>(CLOCKS_PER_SEC));
i=hint;
beg=clock();
while(i--)
scanf("%d",&n);
printf("scanf speed: %f sec\n",(clock()*1.0f-beg*1.0f)/static_cast<float>(CLOCKS_PER_SEC));
i=hint;
beg=clock();
while(i--)
n=R();
printf("fast_in speed: %f sec\n",(clock()*1.0f-beg*1.0f)/static_cast<float>(CLOCKS_PER_SEC));
return 0;
}
运行结果:
输入速度测试:
cin speed: 12.789000 sec
scanf speed: 3.615000 sec
fast_in speed: 2.025000 sec
输出
输出方式 | 1 0 6 10^6 106 整数 |
---|---|
cout | 11.503 sec 11.503\sec 11.503sec |
printf | 3.582 sec 3.582\sec 3.582sec |
快速输出(输出优化) | 1.611 sec 1.611\sec 1.611sec |
完整测试代码
#include<iostream>
using namespace std;
#include<ctime>
#include<cstdio>
#include<cstdlib>
const int hint=1e6;
int a[hint*10];
inline void create(){
srand(time(NULL));
for(register int i=0;i<=hint;i++)
a[i]=rand()%static_cast<int>(1e7);
}
inline void W(register int x){
if(x<0)putchar(45),x=-x;
if(x>9)W(x/10);
putchar(x%10^48);
}
char s[3][50];
int main(){
create();
srand(time(NULL));
clock_t beg,end;
register int i=hint;
freopen("out.txt","w",stdout);
puts("输出速度测试:");
beg=clock();
for(i=0;i<hint;i++)
cout<<a[i];
end=clock();
sprintf(s[0],"cout speed: %f sec\n",(end*1.0f-beg*1.0f)/static_cast<float>(CLOCKS_PER_SEC));
i=hint;
beg=clock();
for(i=0;i<hint;i++)
printf("%d",a[i]);
end=clock();
sprintf(s[1],"printf speed: %f sec\n",(end*1.0f-beg*1.0f)/static_cast<float>(CLOCKS_PER_SEC));
i=hint;
beg=clock();
for(i=0;i<hint;i++)
W(a[i]);
end=clock();
sprintf(s[2],"fast_out speed: %f sec\n",(end*1.0f-beg*1.0f)/static_cast<float>(CLOCKS_PER_SEC));
FILE *fp=fopen("ans.txt","w");
for(i=0;i<3;i++)
fputs(s[i],fp);
fclose(fp);
return 0;
}
运行结果
打开ans.txt,可以看到:
cout speed: 11.503000 sec
printf speed: 3.582000 sec
fast_out speed: 1.611000 sec
输入/输出优化
可以看到,无论是输入还是输出,三者的效率差距也是惊人。我们可注意到cin和cout的速度非常慢,有人可能说要关闭同步,cin.tie(0),cout.tie(0),我又去测试了一下,但开了这些后,虽能提速,却还是没有scanf/printf快。那么,比它们更快的 快读/写 怎么实现呢?
首先得明白一点,用getchar/putchar比scanf/printf快。但getchar/putchar是针对字符的,整数怎么用getchar/putchar呢?
以下代码均针对整数,由于实数的 快读/写 不常用且效率低,这里不谈。
思路
整数的范围:ASCII码 [ 48 , 57 ] [48,57] [48,57]
输入
可以先循环跳过非整数字符(空格),再用getchar“组装”成字符。
代码
inline int read(){
register int x=0;register char c=getchar(); // 用register提高效率
for(;c<48||c>57;c=getchar()); // 跳过非整数字符
for(;c>47&&c<58;c=getchar())x=(x<<3)+(x<<1)+(c^48); /*
x=x*10+(c^48);
x=x*10+(c-48);
三者是等效的,用位运算("<<",">>","^","|","&","~")
可以加快速度,提高效率,具体规则可以参考
https://baike.baidu.com/item/%E4%BD%8D%E8%BF%90%E7%AE%97/6888804?fr=aladdin
*/
return x;
}
直接赋值版:
inline void read(register int &x){
x=0;register char c=getchar(); // x=0很重要!
for(;c<48||c>57;c=getchar());
for(;c>47&&c<58;c=getchar())x=(x<<3)+(x<<1)+(c^48);
}
输出
可以利用 栈/递归函数 来输出整数,一般先把数分解成若干位。
代码
这里提供递归版本:
inline void write(register int x){
if(x>9)write(x/10);
putchar(x%10^48); // 同理,用位运算加快速度
}
负数情况
如果遇到了负数,可以先检测负号(ASCII码:45)。
输入
inline int read(){
register int x=0;register char c=getchar(),f=1;
for(;c<48||c>57;c=getchar())if(c==45)f=-1;
for(;c>47&&c<58;c=getchar())x=(x<<3)+(x<<1)+(c^48);
return x*f;
}
inline int read(register int &x){
x=0;register char c=getchar(),f=1;
for(;c<48||c>57;c=getchar())if(c==45)f=-1;
for(;c>47&&c<58;c=getchar())x=(x<<3)+(x<<1)+(c^48);
x*=f;
}
输出
inline void write(register int x){
if(x<0)putchar(45),x=-x;
if(x>9)write(x/10);
putchar(x%10^48);
}
文件输入输出(fread、fwrite)版
如果你不满足 速读/写 的速度怎么办?没关系,还有更快的文件(fread、fwrite)版! fread/fwrite 是一种比 getchar/putchar 还快的输入输出函数,这里涉及到文件流、指针等知识,不了解可以参考:
fread_百度百科
fwrite_百度百科
文件方式暂时没有负数版本。
输入
#define getc() (_b1==_b2?fread(_b,1,100000,stdin),_b2=_b+100000,*((_b1=_b)++):*(_b1++))
char _b[100000],*_b1,*_b2;
inline void read(register int &x){
x=0;register char c=getc();
for(;c<48||c>57;c=getc());
for(;c>47&&c<58;c=getc())x=(x<<3)+(x<<1)+(c^48);
}
输出
char _d[100000],*_p=_d;
inline void write(register int x){
static int _q[35];register char _t=0; // _q 是一个栈
do{_q[_t++]=x%10,x/=10;}while(x);
while(_t){
register char c=_q[--_t]+48;
if(_p-_d==100000)fwrite(_d,1,100000,stdout),_p=_d;
*_p++=c;
}
}
输出记得在main函数结尾加上:
fwrite(_d,1,_p-_d,stdout);
效率
将文件输入套进上面的测试代码,得到的结果是:
输入速度测试:
cin speed: 12.689000 sec
scanf speed: 3.082000 sec
fast_in speed: 0.174000 sec
ans.txt:
cout speed: 11.681000 sec
printf speed: 2.916000 sec
fast_out speed: 0.275000 sec
可以发现,fread/fwrite 的速度惊人,
1
0
6
10^6
106的整数数据竟快了10倍!普通 速读/写 在它面前只能低头
模板应用(fread、fwrite)
假如数据不是int,是 short、long long 呢?我们可以应用C++里的模板技术,来对函数引入一个通用类型。具体可参考:
模板类_百度百科
而且,如果要一次性输入多个数,还可以使用可变参数模板。
代码
#define getc() (_b1==_b2?fread(_b,1,100000,stdin),_b2=_b+100000,*((_b1=_b)++):*(_b1++))
char _b[100000],*_b1,*_b2,_d[100000],*_p=_d;
template<class T>inline void read(register T&x){
x=0;register char c=getc();
for(;c<48||c>57;c=getc());
for(;c>47&&c<58;c=getc())x=(x<<3)+(x<<1)+(c^48);
}
// 可变参数模板
template<class T1,class ...T2>inline void read(register T1&x,register T2&...X){
read(x),read(X...);
}
template<class T>inline void write(register T x){
static T _q[35];register char _t=0;
do{_q[_t++]=x%10,x/=10;}while(x);
while(_t){
register char c=_q[--_t]+48;
if(_p-_d==100000)fwrite(_d,1,100000,stdout),_p=_d;
*_p++=c;
}
}
输出还是要记得加:
fwrite(_d,1,_p-_d,stdout);