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+++b、a+++++b和a+++(++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先得到執行,所以c為1,賦值之後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-value和r-value上來,不管采用左值、右值一說,或者采用可尋址值和可讀值一說,其本質都是對內存的讀寫操作(我無法確定這裏是否有寄存器操作),也就是說源操作數和目的操作數的地址要能確定。這問題個人感覺不是很好回答,因為依舊涉及到了編譯器的設計問題,在此僅談談個人的理解。
++a是先對a執行加1操作,然後返回對a的引用,自然就可以確定此時a的地址,所以(++a)++是可以編譯通過的(於visual c++ 2008編譯環境,經博友Promi測試,VC6和GCC並不能編譯通過)。而a++是新建了一個臨時變量並賦予其當前a的值,再對a進行增1運算,最後返回臨時變量值,此時就無法再編譯的時候獲得該臨時變量的地址,於是造成了((a++)++)+b無法編譯通過。
由此可知,在++a和a++不影響邏輯結構的情況下,我們應該傾向於使用++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
上一篇:
Java EL??????-1.JUEL??????-??????-????????????-?????????
下一篇:
GNU 和 Linux
ASP.NET設計網絡硬盤之刪除文件夾
Java中的隨機數生成器:Random,ThreadLocalRandom,SecureRandom
智能360:中文語音助手
實例:Netty 處理 TCP協議數據分包問題
注意 scanf_s
專訪體素科技 CEO 丁曉偉:醫療人工智能產品如何成為醫生的“左膀右臂”?
Greenplum failed segment的恢複方法
快捎攜手信站大全步入誠信網站時代
QT4靜態編譯後,編譯程序總是通不過,提示“ error: collect2: ld returned 1 exit status”
Visual Studio調試裏麵的F10和F11有什麼區別