背景
前些天發現了寫讀入優化和不寫讀入優化的區別。。。
別人的代碼:
我的代碼:
我似乎發現了什麼東西。。。
然後我點進第一名的代碼一看:
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無語。。。
讀入優化並不是裝逼,而真正可以節省時間。所以讀入優化是針對數據比較多的題目而言的,所以讓我們養成寫讀入優化的習慣!