C++從零開始(二)——何謂表達式

  本篇是此係列的開頭,在學英語時,第一時間學的是字母,其是英語的基礎。同樣,在C++中,所有的代碼都是通過標識符(Identifier)、表達式(Expression)和語句(Statement)及一些必要的符號(如大括號等)組成,在此先說明何謂標識符。

 

 

   標識符

   標識符是一個字母序列,由大小寫英文字母、下劃線及數字組成,用於標識。標識就是標出並識別,也就是名字。其可以作爲後面將提到的變量或者函數或者類等的名字,也就是說用來標識某個特定的變量或者函數或者類等C++中的元素。

   比如:abc就是一個合法的標識符,即abc可以作爲變量、函數等元素的名字,但並不代表abc就是某個變量或函數的名字,而所謂的合法就是任何一個標識符都必須不能以數字開頭,只能包括大小寫英文字母、下劃線及數字,不能有其它符號,如,!^等,並且不能與C++關鍵字相同。也就是我們在給一個變量或函數起名字的時候,必須將起的名字看作是一個標識符,並進而必須滿足上面提出的要求。如12ab_C就不是一個合法的標識符,因此我們不能給某個變量或函數起12ab_C這樣的名字;ab_12C就是合法的標識符,因此可以被用作變量或函數的名字。

   前面提到關鍵字,在後續的語句及一些聲明修飾符的介紹中將發現,C++提供了一些特殊的標識符作爲語句的名字,用以標識某一特定語句,如if、while等;或者提供一些修飾符用以修飾變量、函數等元素以實現語義或給編譯器及連接器提供一些特定信息以進行優化、查錯等操作,如extern、static等。因此在命名變量或函數或其他元素時,不能使用if、extern等這種C++關鍵字作爲名字,否則將導致編譯器無法確認是一個變量(或函數或其它C++元素)還是一條語句,進而無法編譯。

   如果要讓某個標識符是特定變量或函數或類的名字,就需要使用聲明,在後續的文章中再具體說明。

   數字

   C++作爲電腦編程語言,電腦是處理數字的,因此C++中的基礎東西就是數字。C++中提供兩種數字:整型數和浮點數,也就是整數和小數。但由於電腦實際並不是想象中的數字化的(詳情參見《C++從零開始(三)》中的類型一節),所以整型數又分成了有符號和無符號整型數,而浮點數則由精度的區別而分成單精度和雙精度浮點數,同樣的整型數也根據長度分成長整型和短整型。

   要在C++代碼中表示一個數字,直接書寫數字即可,如:123、34.23、-34.34等。由於電腦並非以數字爲基礎而導致了前面數字的分類,爲了在代碼中表現出來,C++提供了一系列的後綴進行表示,如下:

 u或U  表示數字是無符號整型數,如:123u,但並不說明是長整型還是短整型
 l或L  表示數字是長整型數,如:123l;而123ul就是無符號長整型數;而34.4l就是長雙精度浮點數,等效於雙精度浮點數
i64或I64  表示數字是長長整型數,其是爲64位操作系統定義的,長度比長整型數長。如:43i64
 f或F  表示數字是單精度浮點數,如:12.3f
 e或E  表示數字的次冪,如:34.4e-2就是0.344;0.2544e3f表示一個單精度浮點數,值爲254.4

   當什麼後綴都沒寫時,則根據有無小數點及位數來決定其具體類型,如:123表示的是有符號整型數,而12341434則是有符號長整型數;而34.43表示雙精度浮點數。

   爲什麼要搞這麼多事出來,還分什麼有符號無符號之類的?這全是因爲電腦並非基於數字的,而是基於狀態的,詳情在下篇中將詳細說明。

   作爲科學計算,可能經常會碰到使用非十進制數字,如16進制、8進制等,C++也爲此提供了一些前綴以進行支持。

   在數字前面加上0x或0X表示這個數字是16進製表示的,如:0xF3Fa、0x11cF.而在前面加一個0則表示這個數字是用8進製表示的,如:0347,變爲十進制數就爲231.但16進制和8進制都不能用於表示浮點數,只能表示整型數,即0x34.343是錯誤的。

   字符串

   C++除了提供數字這種最基礎的表示方式外,還提供了字符及字符串。這完全只是出於方便編寫程序而提供的,C++作爲電腦語言,根本沒有提供字符串的必要性。不過由於人對電腦的基本要求就是顯示結果,而字符和字符串都由於是人易讀的符號而被用於顯示結果,所以C++專門提供了對字符串的支持。
   前面說過,電腦只認識數字,而字符就是文字符號,是一種圖形符號。爲了使電腦能夠處理符號,必須通過某種方式將符號變成數字,在電腦中這通過在符號和數字之間建立一個映射來實現,也就是一個表格。表格有兩列,一列就是我們欲顯示的圖形符號,而另一列就是一個數字,通過這麼一張表就可以在圖形符號和數字之間建立映射。現在已經定義出一標準表,稱爲ASCII碼錶,幾乎所有的電腦硬件都支持這個轉換表以將數字變成符號進而顯示計算結果。

 

   有了上面的表,當想說明結果爲“A”時,就查ASCII碼錶,得到“A”這個圖形符號對應的數字是65,然後就告訴電腦輸出序號爲65的字符,最後屏幕上顯示“A”。

   這明顯地繁雜得異常,爲此C++就提供了字符和字符串。當我們想得到某一個圖形符號的ASCII碼錶的序號時,只需通過單引號將那個字符括起來即可,如:'A',其效果和65是一樣的。當要使用不止一個字符時,則用雙引號將多個字符括起來,也就是所謂的字符串了,如:"ABC".因此字符串就是多個字符連起來而已。但根據前面的說明易發現,字符串也需要映射成數字,但它的映射就不像字符那麼簡單可以通過查表就搞定的,對於此,將在後續文章中對數組作過介紹後再說明。

   操作符

   電腦的基本是數字,那麼電腦的所有操作都是改變數字,因此很正常地C++提供了操作數字的一些基本操作,稱作操作符(Operator),如:+ - * / 等。任何操作符都要返回一個數字,稱爲操作符的返回值,因此操作符就是操作數字並返回數字的符號。作爲一般性地分類,按操作符同時作用的數字個數分爲一元、二元和三元操作符。

   一元操作符有:

  +  其後接數字,原封不動地返回後接的數字。如: +4.4f的返回值是4.4;+-9.3f的返回值是-9.3。完全是出於語義的需要,如表示此數爲正數。
 -  其後接數字,將後接的數字的符號取反。如: -34.4f的返回值是-34.4;-(-54)的返回值是54。用於表示負數。
 !  其後接數字,邏輯取反後接的數字。邏輯值就是“真”或“假”,爲了用數字表示邏輯值,在 C++中規定,非零值即爲邏輯真,而零則爲邏輯假。因此3、43.4、'A'都表示邏輯真,而0則表示邏輯假。邏輯值被應用於後續的判斷及循環語句中。而邏輯取反就是先判斷“!”後面接的數字是邏輯真還是邏輯假,然後再將相應值取反。如: 
 
  !5的返回值是0,因爲先由5非零而知是邏輯真,然後取反得邏輯假,故最後返回0。

  !!345.4的返回值是1,先因345.4非零得邏輯真,取反後得邏輯假,再取反得邏輯真。雖然只要非零就是邏輯真,但作爲編譯器返回的邏輯真,其一律使用1來代表邏輯真。

  ~  其後接數字,取反後接的數字。取反是邏輯中定義的操作,不能應用於數字。爲了對數字應用取反操作,電腦中將數字用二進制表示,然後對數字的每一位進行取反操作(因爲二進制數的每一位都只能爲1或0,正好符合邏輯的真和假)。如~123的返回值就爲-124。先將123轉成二進制數01111011,然後各位取反得10000100,最後得-124。 
 
  這裏的問題就是爲什麼是8位而不是16位二進制數。因爲123小於128,被定位爲char類型,故爲8位(關於char是什麼將下篇介紹)。如果是~123ul,則返回值爲4294967172。

  爲什麼要有數字取反這個操作?因爲CPU提供了這樣的指令。並且其還有着很不錯且很重要的應用,後面將介紹。

    關於其他的一元操作符將在後續文章中陸續提到(但不一定全部提到)。

   二元操作符有:

+ - * / %

   其前後各接一數字,返回兩數字之和、差、積、商、餘數。如: 
   34+4.4f的返回值是38.4;3+-9.3f的返回值是-6.3。

  34-4的返回值是30;5-234的返回值是-229。

  3*2的返回值是6;10/3的返回值是3。

  10%3的返回值是1;20%7的返回值是6。

  
&& ||

   其前後各接一邏輯值,返回兩邏輯值之“與”運算邏輯值和“或”運算邏輯值。如: 
   'A'&&34.3f的返回值是邏輯真,爲1;34&&0的返回值是邏輯假,爲0。

  0||'B'的返回值是邏輯真,爲 1;0||0的返回值是邏輯假,爲0。

  
&  | ^

   其前後各接一數字,返回兩數字之“與”運算、“或”運算、“異或”運算值。如前面所說,先將兩側的數字轉成二進制數,然後對各位進行與、或、異或操作。如: 
   4&6的返回值是4,4轉爲00000100,6轉爲00000110各位相與得,00000100,爲4。

  4|6的返回值是6,4轉爲00000100,6轉爲00000110各位相或得,00000110,爲6。

  4^6的返回值是2,4轉爲00000100,6轉爲00000110各位相異或得,00000010,爲2。

  
>  < = ≥ ≤ !=

   其前後各接一數字,根據兩數字是否大於、小於、等於、大於等於、小於等於及不等於而返回相應的邏輯值。如: 
   34>34的返回值是0,爲邏輯假;32<345的返回值爲1,爲邏輯真。

  23>=23和23>=14的返回值都是1,爲邏輯真;54<=4的返回值爲0,爲邏輯假。

  56==6的返回值是0,爲邏輯假;45==45的返回值是1,爲邏輯真。

  5!=5的返回值是0,爲邏輯假;5!=35的返回值是真,爲邏輯真。

 
>> <<

  其前後各接一數字,將左側數字右移或左移右側數字指定的位數。與前面的 ~、&、|等操作一樣,之所以要提供左移、右移操作主要是因爲CPU提供了這些指令,主要用於編一些基於二進制數的算法。
  <<將左側的數字轉成二進制數,然後將各位向左移動右側數值的位數,如:4,轉爲00000100,左移2位,則變成00010000,得16。

  >>與<<一樣,只不過是向右移動罷了。如:6,轉爲00000110,右移1位,變成00000011,得3。如果移2位,則有一位超出,將截斷,則6>>2的返回值就是00000001,爲1。

  左移和右移有什麼用?用於一些基於二進制數的算法,不過還可以順便作爲一個簡單的優化手段。考慮十進制數3524,我們將它左移2位,變成352400,比原數擴大了100倍,準確的說應該是擴大了10的2次方倍。如果將3524右移2位,變成35,相當於原數除以100的商。

  同樣,前面4>>2,等效於4/4的商;32>>3相當於32/8,即相當於32除以2的3次方的商。而4<<2等效於4*4,相當於4乘以2的2次方。因此左移和右移相當於乘法和除法,只不過只能是乘或除相應進制數的次方罷了,但它的運行速度卻遠遠高於乘法和除法,因此說它是一種簡單的優化手段。

 

 ,  其前後各接一數字,簡單的返回其右側的數字。如: 
   34.45f,54的返回值是54;-324,4545f的返回值是4545f。

  那它到底有什麼用?用於將多個數字整和成一個數字,在《C++從零開始(四)》中將進一步說明。

 

    關於其他的二元操作符將在後續文章中陸續提到(但不一定全部提到)。

   三元操作符只有一個,爲:,其格式爲:<數字1><數字2>:<數字3>.它的返回值爲:如果<數字1>是邏輯真,返回<數字2>,否則返回<數字3>.如:344:2的返回值就是4,因爲34非零,爲邏輯真,返回4.而04:2的返回值就是2,因爲0爲邏輯假,返回2.

   表達式

   你應該發現前面的荒謬之處了——12>435返回值爲0,那爲什麼不直接寫0還吃飽了撐了寫個12>435在那?這就是表達式的意義了。

   前面說“>”的前後各接一數字,但是操作符是操作數字並返回數字的符號,因爲它返回數字,因此可以放在上面說的任何一個要求接數字的地方,也就形成了所謂的表達式。如:23*54/45>34的返回值就是0,因爲23*54的返回值爲1242;然後又將1242作爲“/”的左接數字,得到新的返回值27.6;最後將27.6作爲“>”的左接數字進而得到返回值0,爲邏輯假。

   因此表達式就是由一系列返回數字的東西和操作符組合而成的一段代碼,其由於是由操作符組成的,故一定返回值。而前面說的“返回數字的東西”則可以是另一個表達式,或者一個變量,或者一個具有返回值的函數,或者具有數字類型操作符重載的類的對象等,反正只要是能返回一個數字的東西。如果對於何謂變量、函數、類等這些名詞感到陌生,不需要去管它們,在後繼的文章中將會一一說明。

   因此34也是一個表達式,其返回值爲34,只不過是沒有操作符的表達式罷了(在後面將會瞭解到34其實是一種操作符)。故表達式的概念其實是很廣的,只要有返回值的東西就可以稱爲表達式。

   由於表達式裏有很多操作符,執行操作符的順序依賴於操作符的優先級,就和數學中的一樣,*、/的優先級大於+、-,而+、-又大於>、<等邏輯操作符。不用去刻意記住操作符的優先級,當不能確定操作符的執行順序時,可以使用小括號來進行指定。如:((1+2)*3)+3)/4的返回值爲3,而1+2*3+3/4的返回值爲7.注意3/4爲0,因爲3/4的商是0.當希望進行浮點數除法或乘法時,只需讓操作數中的某一個爲浮點數即可,如:3/4.0的返回值爲0.75.

   & | ^ ~等的應用

   前面提過邏輯操作符“&&”、“||”、“!”等,作爲表示邏輯,其被C++提供一點都不值得驚奇。但是爲什麼要有一個將數字轉成二進制數,然後對二進制數的各位進行邏輯操作的這麼一類操作符呢?首先是CPU提供了相應的指令,並且其還有着下面這個非常有意義的應用。

   考慮一十字路口,每個路口有三盞紅綠燈,分別指明能否左轉、右轉及直行。共有12盞,現在要爲它編寫一個控制程序,不管這程序的功能怎樣,首先需要將紅綠燈的狀態轉化爲數字,因爲電腦只知道數字。所以用3個數字分別表示某路口的三盞紅綠燈,因此每個紅綠燈的狀態由一個數字來表示,假設紅燈爲0,綠燈爲1(不考慮黃燈或其他情況)。

   後來忽然發現,其實也可以用一個數字表示一個路口的三盞紅綠燈狀態,如用110表示左轉綠燈、直行綠燈而右轉紅燈。上面的110是一個十進制數字,它的每一位實際都可以爲0~9十個數字,但是這裏只應用到了兩個:0和1,感覺很浪費。故選擇二進制數來表示,還是110,但是是二進制數了,轉成十進制數爲6,即使當爲111時轉成十進制數也只是7,比前面的110這個十進制數小多了,節約了……??什麼??

   我們在紙上寫數字235425234一定比寫134這個數字要更多地佔用紙張(假設字都一樣大)。因此記錄一個大的數比記錄一個小的數要花費更多的資源。簡直荒謬!不管是100還是1000,都只是一個數字,爲什麼記錄大的數字就更費資源?因爲電腦並不是數字計算機,而是電子計算機,它是基於狀態而不是基於數字的,這在下篇會詳細說明。電腦必須使用某種表示方式來代表一個數字,而那個表示方式和二進制很像,但並不是二進制數,故出現記錄大的數較小的數更耗資源,這也就是爲什麼上面整型數要分什麼長整型短整型的原因了。

   下面繼續上面的思考。使用了110這個二進制數來表示三盞紅綠燈的狀態,那麼現在要知道110這個數字代表左轉紅綠燈的什麼狀態。以數字的第三位表示左轉,不過電腦並不知道這個,因此如下:110&100.這個表達式的返回值是100,非零,邏輯真。假設某路口的狀態爲010,則同樣的010&100,返回值爲0,邏輯假。因此使用“&”操作符可以將二進制數中的某一位或幾位的狀態提取出來。所以我們要了解一個數字代表的紅綠燈狀態中的左轉紅綠燈是否綠燈時,只需讓它和100相與即可。

   現在要保持其他紅綠燈的狀態不變,僅僅使左轉紅綠燈爲綠燈,如當前狀態爲010,爲了使左轉紅綠燈爲綠燈,值應該爲110,這可以通過010|100做到。如果當前狀態是001,則001|100爲101,正確——直行和右轉的紅綠燈狀態均沒有發生變化。因此使用“|”操作符可以給一個二進制數中的某一位或幾位設置狀態,但只能設置爲1,如果想設置爲0,如101,要關掉左轉的綠燈,則101&~100,返回值爲001.

   上面一直提到的路口紅綠燈的狀態實際編寫時可以使用一個變量來表示,而上面的100也可以用一個標識符來表示,如state&TS_LEFT,就可以表示檢查變量state所表示的狀態中的左轉紅綠燈的狀態。

   上面的這種方法被大量地運用,如創建一個窗口,一個窗口可能有二三十個風格,則通過上面的方法,就可以只用一個32位長的二進制數字就表示了窗口的風格,而不用去弄二三十個數字來分別代表每種風格是否具有。
 

發佈了3 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章