背景
前些天发现了写读入优化和不写读入优化的区别。。。
别人的代码:
我的代码:
我似乎发现了什么东西。。。
然后我点进第一名的代码一看:
void get(int &res){
char ch;bool flag=0;
while(!isdigit(ch=getchar())) (ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
当时我是懵逼的。小小一个函数,竟然可以快那么多!
在分析函数的时候,我弄不明白(ch=='-')&&(flag=true)
和(flag)&&(res=-res)
是什么意思,然后我就分析了一下:
(A)&&(B)
是先判断A是否成立,如果A成立,B为执行的语句(如a=b
),就执行B语句。
这就是读入优化!
然后我搜集了一下资料,这一种写法是比较装逼,会遭雷劈的写法,所以我们还是脚踏实地,从零开始。
读入优化的原理与实现
C++里有很多种输入方式,我们最常用的是scanf和cin,因为这两个函数比较好用一些。除此之外,还有一些读入字符的函数,给大家普及一下:
#include<cstdio>
#include<cstring>
int main()
{
char c[];
int len=strlen(c);//获取c数组的长度,需要用#include<cstring>头文件
gets(c);//读入一行字符串,遇到回车后停止,可以无限读取,不会判断上限,所以容易溢出。而且该函数,这个函数在ISO/IEC 9899 2011(C11)标准中被移除。
fgets(c,len,stdin);//和gets函数差不多,读取长度超过len或者遇到回车都会停下来,所以每次最多读取len-1个(因为字符数组最后一个'\0'占位)
c[0]=getchar();//像scanf一样,读入一个字符,并返回这个字符,可以直接赋值,需要用#include<cstdio>头文件。和scanf不同的是,该函数是非缓冲输入函数,也就意味着它比scanf更快。
c[0]=getch();//直接读入一个字符,而没有回显(但在linux系统下有回显)。也就是说,你读入了一个字符,它不会在界面里显示而是直接读入这个字符,getch也会直接返回这个字符。比如说你写了一个程序小游戏,你肯定不希望看到wasd满天飞,所以就用getch。需要#include<conio.h>头文件。同样,它没有缓冲。
}
getchar比scanf更快。
我们可不可以用getchar来改进我们的输入呢?
当然是可以的。首先getchar是一个一个字符读入的,所以我们要一个一个读入,但是我们要给他进位,就要乘以10,因为它是ASCII码,就要给它减个‘0’。比如是字符‘1’,‘1’-‘0’就为数字1.
理论了过后,我们就来模拟一下过程:
假设输入2018。
- 程序读入字符‘2’,‘2’-‘0’得数字2,ans=0*10+2=2.
- 程序读入字符‘0’,‘0’-‘0’得数字0,ans=2*10+0=20.
- 程序读入字符‘1’,‘1’-‘0’得数字1,ans=20*10+1=201.
- 程序读入字符‘8’,‘8’-‘0’得数字8,ans=201*10+8=2018.
基本上就是这样,不给大家一一举例了。
依照以上的逻辑,ans=ans*10+ch-'0'
。
可是有的时候输入这样子:
123 456 789
每两个数之间有空格,这又怎么办呢?
哦,那么就先把数字前的空格读完,即一直读入,直到出现数字为止。
到此我们就可以给出第一代的程序了:
void get(int &a)
{
a=0;
char ch;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') a=a*10+ch-'0',ch=getchar();
}
遇到负数怎么处理?
相信有些读者会说直接将数字乘以-1,可是一开始数字为0,乘以任何数都为0,肯定是不能这样做的。
那只要在最开始设置标记为1,读整数之前,如果有符号(‘-’),就设置标记为-1,最终用数字乘以标记即可。
肯定有人要说了,那开头的isdigit
函数是什么鬼?
其实这个函数就是检测字符为不为阿拉伯数字0到9,所以我们也可以用这个函数减小代码量。
注:要用#include<iostream>
头文件。
代码
最终有两种代码。
void get(int &p){
int flag=1;char ch;
for(p=0;!isdigit(ch);ch=getchar()) if(ch=='-') flag=-1;
for(;isdigit(ch);ch=getchar()) p=p*10+ch-'0';
p*=flag;
}
这种代码直接get(int)
即可。
int get(){
int flag=1,p=0;char ch;
for(;!isdigit(ch);ch=getchar()) if(ch=='-') flag=-1;
for(;isdigit(ch);ch=getchar()) p=p*10+ch-'0';
return p*flag;
}
这种代码int=get()
即可。
验证读入优化的效率
数据制造程序
用freopen生成1000000个数的文本,并分别用scanf,cin和读入优化读入。
#include<cstdio>
const int N=1000000;
int main()
{
freopen("test.txt","w",stdout);//保存输出结果为文件
for(int i=1;i<=4;i++)//测试直接get,赋值get,scanf,cin四种读入程序
{
for(int j=1;j<=N;j++)
printf("%d ",i);
puts("");
}
}
测试程序
#include<ctime>
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1000000;
void get_tradition(int &p){
int flag=1;char ch;
for(p=0;!isdigit(ch);ch=getchar()) if(ch=='-') flag=-1;
for(;isdigit(ch);ch=getchar()) p=p*10+ch-'0';
p*=flag;
}
int get_assignment(){
int flag=1,p=0;char ch;
for(;!isdigit(ch);ch=getchar()) if(ch=='-') flag=-1;
for(;isdigit(ch);ch=getchar()) p=p*10+ch-'0';
return p*flag;
}
int main()
{
freopen("test.txt","r",stdin);//读入测试文件
freopen("test.out","w",stdout);
int x;
double A[4];
double t1=clock();
for(int i=1;i<=N;i++)
get_tradition(x);
double t2=clock();
A[0]=(t2-t1)/1000;
t1=clock();
for(int i=1;i<=N;i++)
x=get_assignment();
t2=clock();
A[1]=(t2-t1)/1000;
t1=clock();
for(int i=1;i<=N;i++)
scanf("%d",&x);
t2=clock();
A[2]=(t2-t1)/1000;
t1=clock();
for(int i=1;i<=N;i++)
cin>>x;
t2=clock();
A[3]=(t2-t1)/1000;
fclose(stdout);
freopen("Answer.out","w",stdout);
printf("When number is %d,\n",N);
printf("get_tradition took %.3lf second(s)\n",A[0]);
printf("get_assignment took %.3lf second(s)\n",A[1]);
printf("scanf took %.3lf second(s)\n",A[2]);
printf("cin took %.3lf second(s)\n",A[3]);
}
总结
When number is 1000000,
get_tradition took 0.078 second(s)
get_assignment took 0.062 second(s)
scanf took 0.297 second(s)
cin took 2.063 second(s)
cena
作死,n=100000000时,
表示cin无语。。。
读入优化并不是装逼,而真正可以节省时间。所以读入优化是针对数据比较多的题目而言的,所以让我们养成写读入优化的习惯!