閱讀209 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Way on c & c++ 小記 [二]

簡單的數據類型?感覺不那麼簡單。

       C語言的數據類型我認為可以劃分為3類,分別是空類型void、基本類型(字符型、整型、浮點型和枚舉類型)和派生類型(數組、函數、指針、結構體和共用體)。

       標準C上規定int類型範圍為-32768 ~32767,然而實際上往往並非如此,這取決於編譯器的設計,比如下述代碼:

#include <stdio.h>
 
int main(int argc, char*argv[]){
       printf("%d/n",sizeof(int));
       printf("%d/n",sizeof(float));
       printf("%d/n",sizeof(double));
       printf("%d/n",sizeof(long long));
       return 0;
}

       vc2008中的輸出為:4 4 8 8

       這說明了在vc2008編譯環境下,int類型占據了4個字節的內存空間。而看到int類型,很容易讓人聯想到自加運算(++),關於自加運算不妨看看a+++ba+++++ba+++(++b)有何區別。下麵是測試的結果:

#include <stdio.h>
 
int main(int argc, char*argv[]){
       inta,b,c;
       a = b = c = 0;
       //c = a+++b;  //輸出1,0,0
       //c = a+++++b;      //無法通過編譯,提示++需要左值
       //c =a+++(++b);//輸出1,1,1
       printf("%d,%d,%d/n",a,b,c);
       return 0;
}

       插入旁白:關於左值、右值,網上有人說是翻譯錯誤,應理解為可尋址的和可讀的,我覺得也頗有幾分道理,但這裏就引用編譯器報錯原文。

       第一種結果很容易理解,先執行c=a+b,之後a自加1;第三種結果也好理解,在賦值運算前++b先得到執行,所以c1,賦值之後a++得到執行。關鍵在第二種,為什麼會編譯出錯?“++需要左值”是指哪個“++”?為什麼是那個“++”?在此,不妨反問一句,如果讓自己設計編譯器,那麼遇到a+++++b這樣的語句應該怎樣處理?先帶著這個疑問繼續探尋。

       編譯器會對a+++++b這樣的表達式進行出錯處理,但卻接受a+++b這樣的表達式,從一定程度上說明了編譯器“聰明”到可以處理a++,但卻還不夠“聰明”去處理++b。當然,這裏指的++b是特定地、位於a+++++b這樣表達式當中的限定場合的。為什麼呢?這就屬於詞法分析的問題了。仍然回到上麵那個問題,如果讓自己設計編譯器,遇到a+++b要怎麼辦呢?

       為了找出是哪個“++”需要左值,我們不妨將代碼稍加改動為:

#include <stdio.h>
 
int main(int argc, char*argv[]){
       inta,b,c;
       a = b = c = 0;
       c = ((a++)++)+b;
       printf("%d,%d,%d/n",a,b,c);
       return 0;
}

       編譯器仍然提示同樣的錯誤,於是我們可以推出是第三個加號和第四個加號組成的“++”運算符需要左值,即a+++++b等效於((a++)++)+b

    想來現在的最新問題是,(a++)後麵為什麼不能接自加運算“++”?

    回到l-valuer-value上來,不管采用左值、右值一說,或者采用可尋址值和可讀值一說,其本質都是對內存的讀寫操作(我無法確定這裏是否有寄存器操作),也就是說源操作數和目的操作數的地址要能確定。這問題個人感覺不是很好回答,因為依舊涉及到了編譯器的設計問題,在此僅談談個人的理解。

    ++a是先對a執行加1操作,然後返回對a的引用,自然就可以確定此時a的地址,所以(++a)++是可以編譯通過的(於visual c++ 2008編譯環境,經博友Promi測試,VC6和GCC並不能編譯通過)。而a++是新建了一個臨時變量並賦予其當前a的值,再對a進行增1運算,最後返回臨時變量值,此時就無法再編譯的時候獲得該臨時變量的地址,於是造成了((a++)++)+b無法編譯通過。

由此可知,在++aa++不影響邏輯結構的情況下,我們應該傾向於使用++a。需要強調的是,我使用的是vc2008編譯環境。

    接著回到編譯器設計考慮的問題。在C語言編譯器的詞法分析中有“貪心法”一說,因為存在“++”運算,所以遇到“+”自然要再往前看是否還有“+”。或許應該換一種說法,在我的知識範圍內,詞法分析都有“貪心法”一說,即識別單詞過程應該包含盡可能多的字符。這很好理解,比如C語言中要求變量名必須以下劃線或者字母開頭,之後可以下劃線、字母和數字混合,那麼當我們遇到_abc123def時,我們難道分析道_abc就停止了嗎?自然不是的,我們要繼續往前以識別出一個完整的單詞。這是詞法分析的知識範疇,那麼編譯器是如何識別出標識符的呢?關於這個問題,杭電ACM網站上有這麼一道簡單題,可以去測試一下自己的思路對不,https://acm.hdu.edu.cn/showproblem.php?pid=2024。再深入想想,如果是判斷合法的數值類型應該怎麼操作呢?這裏的數值類型包括整形、浮點型和指數型。

       對於上述杭電2024題目,我第一次的思路是分為兩種情況:第一個字母隻能有下劃線和字母,其它字母隻能有下劃線、字母或數字,於是產生了以下代碼:

#include <stdio.h>
#include <ctype.h>
 
int main(int argc, char*argv[]){//hdu2024_1
       intn,flag;
       charid[100];
       scanf("%d",&n);
       getchar();//由於scanf會將'/n'放在緩衝區中,所以先處理掉它
       while(n--){
              flag = 0;
              gets(id);
              if(!(id[0]== '_' || isalpha(id[0])))    flag = 1;
              for(int i=1; id[i] != '/0';++i){
                     if(!(isalpha(id[i])|| isdigit(id[i]) || id[i] == '_')){
                            flag = 1;
                            break;
                     }
              }
              printf("%s/n",(flag == 1) ? "no" : "yes");
       }
       return 0;
}


       這種思路比較簡單,適用於識別標識符,但如果是要識別數值呢(含整形、浮點和指數型)?這裏給出另外一種識別標識符的思路,可以用來識別數值類型,即編譯器設計時使用的狀態轉換圖(或其改進後的自動機):

標識符狀態轉換圖

       上述識別標識符轉換圖的含義為:開始狀態為0,如果遇到字母或者下劃線則轉向狀態1;在狀態1下,如果遇到字母、數組或者下劃線則仍轉向狀態1,遇到其它則表示識別出一個標識符了。由其可產生如下代碼:

#include <stdio.h>
#include <ctype.h>
 
int main(int argc, char*argv[]){//hdu2024_2
       int n,flag, state;
       charid[100];
       scanf("%d",&n);
       getchar();//由於scanf會將'/n'放在緩衝區中,所以先處理掉它
       while(n--){
              flag = state = 0;
              gets(id);
              for(int i=0; id[i] != '/0';++i){
                     switch(state){
                            case 0:
                                   if(isalpha(id[i]) || id[i] == '_')   state = 1;
                                   else{      flag= 1;  break;    }
                                   break;
                            case 1:
                                   if(isalpha(id[i]) || isdigit(id[i]) || id[i] == '_')       state= 1;
                                   else{      flag= 1;  break;    }
                                   break;
                            case 2:   //這裏的case2對於hdu2024這道題目不是必須的,但在詞法分析中卻是需要的
                                   flag = 1;  break;
                     }
              }
              printf("%s/n",(flag == 1) ? "no" : "yes");
       }
       return 0;
}


       數據類型果真不那麼簡單,上麵那麼多的“+”已經有點眼花繚亂了,但對於數據類型的知識範圍而言不過是剛剛開了個頭。現在給自己一道飯後甜點,char類型的單個字符為什麼不能表示一個漢字字符呢?

2010-1-26

----------------------------------------cuttingline----------------------------------------

 

最後更新:2017-04-02 04:01:45

  上一篇:go Java EL??????-1.JUEL??????-??????-????????????-?????????
  下一篇:go GNU 和 Linux