1. 程式人生 > >c++讀入優化(整數)

c++讀入優化(整數)

一、背景

這是某道題目的狀態:

我的程式碼:

大牛的程式碼:

我瞬間萌幣了!

我有這麼慢?Are you kidding me?

點進去一看:

  1. void Read(int & p)

  2. {

  3. p=0;

  4. int flag=1;

  5. char c=getchar();

  6. while(c<'0' or c>'9')

  7. {

  8. if(c=='-') flag=-1;

  9. c=getchar();

  10. }

  11. while(c>='0' and c<='9')

  12. p=p*10+(c-'0'),c=getchar();

  13. p*=flag;

  14. }

這是什麼?

原來,這是讀入優化啊!

二、讀入優化的原理與實現

C語言和C++庫裡有很多種輸入方式,我們最常用的是scanf和cin。除此之外,還有:

char c[]; gets(c);//讀入一行字串,但是需要注意的是,它只會在遇到'\n'後停止,不會因為超過c的記憶體上限而停止。所以容易溢位。

char c[]; fgets(c,len,stdin);//讀入一行字串。和gets不同的是,它如果讀入了len-1個字元,就會停止。

char c=getchar();//就像scanf一樣,只讀入一個字元,並返回這個字元,需要#include<cstdio>標頭檔案。與scanf不同的是,它沒有緩衝,也就意味著它會更快。

char c=getch();//直接讀入一個字元,而不會回顯。也就是說,你讀入了一個字元,它不會在介面裡顯現而直接讀入getch,getch也會直接返回這個字元。比如說你寫了一個程式小遊戲,你肯定不希望看到wasdwasdwasd滿天飛,所以就用getch。需要#include<conio.h>標頭檔案。同樣,它沒有緩衝。

等等。

可以看到,getchar比scanf更快。我們可不可以用getchar來改進我們的輸入呢?

下面以整數的讀入優化為例。其實我也只會整數的讀入優化。。。

讓我們隨便寫一個整數:

令p=123456789

如果用getchar,它會讀入一個字元‘1’,然後讀入'2','3','4'......

怎麼產生數字呢?我們用'1'-'0'(實質是ASCII碼的運算),結果就是數字1。

同理,char c=getchar(); int k=c-'0'; 就可以得到這個數字k。

現在要把所有的k加到一起,得到p。

讓我們一步一步地來:

因為我們一次只讀入一位,所以要把這個數拆成一位一位的形式。

最高位,1=1;

前兩位,12=1*10+2;

前三位,123=1*100+2*10+3=12*10+3;

前四位,1234=1*1000+2*100+3*10+4=123*10+4;

.......

規律已經很明顯了。

每讀入一個代表數字的字元c,p=p*10+c-'0';

我們只需要不停地while(c>='0' and c<='9'),並且處理p即可。

問題來了,如果這個c不代表數字,比如說:

123  456

789

123和456中間有空格,456和789之間有換行,怎麼處理呢?

因為這裡是三個整數,讀入了123以後,還剩下“  456”,前兩個c=‘ ’肯定不能讓c-‘0’算在p裡面。因此,我們需要跳過不是代表數字的字元。

我們就可以寫出一個基本的整數讀入優化:

  1. void Read(int & p)

  2. {

  3. p=0;

  4. char c=getchar();

  5. while(c<'0' or c>'9') c=getchar();

  6. while(c>='0' and c<='9')

  7. p=p*10+(c-'0'),c=getchar();

  8. }

要讀入整數p,只需要呼叫Read(p)即可。

除了這種,比較常用的是

  1. int Read()

  2. {

  3. int p=0;

  4. char c=getchar();

  5. while(c<'0' or c>'9') c=getchar();

  6. while(c>='0' and c<='9')

  7. p=p*10+(c-'0'),c=getchar();

  8. return p;

  9. }

呼叫p=Read();即可。

其實(c>='0' and c<='9')也是一個函式isdigit(c),如果c是代表數字的字元,就返回1,否則返回0,需要#include<cctype>標頭檔案。

所以也可以寫成:

  1. void Read(int & p)

  2. {

  3. p=0;

  4. char c=getchar();

  5. while(!isdigit(c)) c=getchar();

  6. while(isdigit(c))

  7. p=p*10+(c-'0'),c=getchar();

  8. }

的形式。

三、整數讀入優化的特殊處理——負數

對於一個負數,讀入優化只會讀入數字,負號並不會被讀入。

所以需要讀入負數,要特判這個字元是不是負號,如果是,那麼這個值要改成負數。

這就是文章開頭的程式碼:

  1. void Read(int & p)

  2. {

  3. p=0;

  4. int flag=1;

  5. char c=getchar();

  6. while(c<'0' or c>'9')

  7. {

  8. if(c=='-') flag=-1;

  9. c=getchar();

  10. }

  11. while(c>='0' and c<='9')

  12. p=p*10+(c-'0'),c=getchar();

  13. p*=flag;

  14. }

但是,這個程式碼有一個弊端。所有的‘-’都必須代表負號。如果是減號,比如4-3,就會讀錯。

四、驗證讀入優化的效率

用freopen生成五百萬個數的文字,並分別用scanf,cin和讀入優化讀入。

文字生成:

  1. #include<cstdio>

  2. int main()

  3. {

  4. freopen("test.txt","w",stdout);

  5. const int N=5000000;

  6. for(int i=1;i<=N;i++)

  7. printf("%d ",N);

  8. }

驗證程式:

  1. #include<cstdio>

  2. #include<ctime>

  3. #include<algorithm>

  4. #include<windows.h>

  5. #include<iostream>

  6. #include<cctype>

  7. using namespace std;

  8. void s(int & p)

  9. {

  10. scanf("%d",&p);

  11. }

  12. void c(int & p)

  13. {

  14. cin>>p;

  15. }

  16. void Read(int & p)

  17. {

  18. p=0;

  19. char c=getchar();

  20. while(!isdigit(c)) c=getchar();

  21. while(isdigit(c))

  22. p=p*10+(c-'0'),c=getchar();

  23. }

  24. double t1,t2;

  25. int main()

  26. {

  27. freopen("test.txt","r",stdin);

  28. int p;

  29. t1=clock();

  30. //for(int i=1;i<=5000000;i++) s(p);

  31. //for(int i=1;i<=5000000;i++) c(p);

  32. for(int i=1;i<=5000000;i++) Read(p);

  33. t2=clock();

  34. //printf("scanf took %.2lf second",(t2-t1)/1000);

  35. //printf("cin took %.2lf second",(t2-t1)/1000);

  36. printf("Read took %.2lf second",(t2-t1)/1000);

  37. }

結果:

scanf took 0.85 second

cin took 14.63 second

Read took 0.35 second

所以說讀入優化還是很快的。