官术网_书友最值得收藏!

4.9 選擇(判斷)結構

計算機本質上就是能夠執行運算和做出邏輯判斷的機器,它的這種能力可以通過編程語言中的選擇結構(即判斷結構)表現出來。C++/C有3種基本的選擇結構:if結構(單選擇)、if/else結構(雙重選擇)和switch結構(多重選擇)。

if結構和if/else結構的語法分別如下:

C++/C也支持下面的if/else結構:

            if(…){…}
            else if(…){…}
            else if(…){…}
            else{…}
        因為它相當于如下的嵌套結構:
            if (…) {
              …
            } else {
              if (…) {
                  …
              } else {
                  if (…) {
                    …
                  } else {
                    …
                  }
              }
            }

因為每一個else分支里面僅有一條if語句,故省略了{}。

【建議4-2】: 在if/else結構中,要盡量把為TRUE的概率較高的條件判斷置于前面,這樣可以提高該段程序的性能。

按理說,if語句是C++/C語言中比較簡單和常用的語句,然而很多程序員用隱含錯誤的方式書寫if語句,最常見的就是變量與零值的比較。

4.9.1 布爾變量與零值比較

假設布爾變量名稱為flag,它與零值比較的標準if語句如下:

            if(flag)         // 表示flag為真
            if(!flag)         // 表示flag為假

【提示4-16】: 根據布爾類型(boolean)的語義,0為“假”,任何非0值都是“真”。可用true和false來表示“真”和“假”兩個概念。語言實現必須通過可比的值來區分二者,比如0和非0就可以擔當此任務。但是TRUE的值究竟是什么并沒有統一的標準,不同的語言可能采用不同的方案。具體到C++語言,標準化以前的某些實現并不支持bool這種內置類型,也沒有true/false這兩個內置常量。if語句判斷其條件表達式的真假并不是通過把它的計算結果轉換為布爾類型的臨時變量來進行的,而是將其結果直接和0進行相等性比較,如果不等于0則表示真,否則為假。許多語言都采用了這個比較通用的做法。標準化后的某些C++實現可能對if語句的處理做了增強或者調整,因為現在bool是標準類型了,并且增加了true和false這兩個常量。由于歷史的原因,Visual C++ 將TRUE定義為1,而Visual Basic將TRUE定義為-1。

標準C++規定的bool類型常量和整數、指針等之間的轉換規則如下:

            false→0,  true→1;
            0→false,  任何非0值→true;

但是不同的實現對true的表示可能不同(可能不符合標準),因此下面這樣的語句:

            int flag = -1;
            if ( flag == true ) {}
            else{}

在不同的實現下行為可能不一致。但是false的值是確定的,因此應該總是和false比較。

不要將布爾變量flag直接與true或者1、-1、0等進行比較。下列if語句都屬于不良用法:

      if(flag!=true)  // 錯誤用法
      if(flag==true)      // 錯誤用法
      if(flag==1)         // 錯誤用法
      if(flag!=1)         // 錯誤用法
      if(flag==0)         // 不良用法,讓人誤以為flag是整數
      if(flag!=0)         // 不良用法,讓人誤以為flag是整數

4.9.2 整型變量與零值比較

假設整型變量為value,它與零值比較的標準if語句如下:

            if(value==0)
            if(value!=0)

不可以模仿bool變量的風格而寫成:

            if(value)     // 會讓人誤以為value是布爾變量
            if(!value)

4.9.3 浮點變量與零值比較

計算機表示浮點數(float或double類型)都有一個精度限制。對于超出了精度限制的浮點數,計算機會把它們的精度之外的小數部分截斷。因此,本來不相等的兩個浮點數在計算機中可能就變成相等的了。例如:

            float a = 10.222222225, b = 10.222222229;

在數學上a和b是不相等的,但是在32位計算機中它們就是相等的。

【提示4-17】: 如果兩個同符號浮點數之差的絕對值小于或等于某一個可接受的誤差(即精度),就認為它們是相等的,否則就是不相等的。精度根據具體應用要求而定。不要直接用“==”或“!=”對兩個浮點數進行比較,雖然C++/C語言支持直接對浮點數進行==和!=的比較操作,但是由于它們采用的精度往往比我們實際應用中要求的精度高,所以可能導致不符合實際需求的結果甚至錯誤。

假設有兩個浮點變量x和y,精度定義為EPSILON = 1e-6,則錯誤的比較方式如下:

            if(x==y)                // 隱含錯誤的比較
            if(x!=y)                // 隱含錯誤的比較

應該轉化為正確的比較方式:

            if(abs(x-y)<=EPSILON)    //x等于y
            if(abs(x-y)>EPSILON)     //x不等于y

同理,x與零值比較的正確方式為:

            if(abs(x)<=EPSILON)      //x等于0
            if(abs(x)>EPSILON)       //x不等于0

從數學意義上講,兩個不同的數字之間存在著無窮個實數。計算機只能區分至少1bit不同的兩個數字,并且使用較少的位(32或64位)來表示一個很大范圍內的數字,因此浮點表示只能是一種近似結果。在針對實際應用環境編程時,總是有一個精度要求,而直接比較一個浮點數和另外一個值(浮點數或者整數)是否相等(==)或不等(!=)可能得不到符合實際需要的結果。

例如:假設在某光學精密儀器制造工業應用中,要求該儀器各零部件尺寸精度達到1μm,即10-6m。那么下面兩個數從數學意義上來說應該是不相等的:

            d1=1.123456 2(m)    d2=1.123456 8(m)

但是在精度要求為10-6m的情況下,它們應該被視為相等。而如果使用==和!=對d1和d2直接進行比較,結果將可能如下:

            if(d1==d2)……          //FALSE
            if(d1!=d2)……           //TRUE

原因就是這樣直接比較的精度比我們要求的10-6m要高。

同樣道理,一個浮點數和一個整數之間的直接判等也存在類似的偏差。例如:

            d3=1.000000 1(m)    d4=1(m)

同樣在精度要求為10-6m的情況下,它們應該被視為相等。而如果使用==和!=對d3和d4直接進行比較,結果可能恰恰相反。

所以,在實際應用環境下,如果直接比較浮點數和另一個數(整數或浮點數)是否相等(==)或不等(!=),可能會產生錯誤的結果,進而導致軟件出錯。

直接比較浮點數和另一個數(整數或浮點數)是否相等(==)或不等(!=),其結果可能依賴于具體的編譯環境和平臺(如操作系統和硬件系統結構),因為每一個編譯平臺都有自己默認的精度,對浮點數直接進行==和!=的比較采用的就是這個默認精度,而不是按照內存中兩個數僅有某個bit不同(其他所有bit都相同)來判斷兩個數是否相等的。

例如:

            float x = 0.0f, y = 0.0f;
            …
            if (abs(x - y) <= numeric_limits<float>::epsilon()) {
              // x == y
            } else {
              // x != y
            }
            if (abs(x) <= numeric_limits<float>::epsilon()) {
              // x == 0
            } else {
              // x != 0
            }

雖然不建議直接使用==和!=比較浮點數,但是可以直接比較浮點數誰大誰小,即可將“<”和“>”直接應用于浮點數之間的比較及浮點數和整數的比較。但是,對內置類型來說,“!(a>b) && !(a<b)”與“a==b”的語義是等價的,因此在針對實際應用環境編程時也不建議使用“!(a>b) && !(a<b)”來判斷浮點數相等與否。

4.9.4 指針變量與零值比較

指針變量的零值是“空值”(記為NULL),即不指向任何對象。盡管NULL的值與0相同,但是兩者意義不同(類型不同):

            #ifdef __cplusplus
            #define  NULL   0
            #else
            #define  NULL   ((void*)0)
            #endif

假設指針變量的名字為p,它與零值比較的標準if語句如下:

            if(p==NULL)    //p與NULL顯式比較,強調p是指針變量
            if(p != NULL)

而不要寫成:

            if(p==0)        // 容易讓人誤以為p是整型變量
            if(p != 0)

或者:

            if(p)            // 容易讓人誤以為p是布爾變量
            if(!p)

4.9.5 對if語句的補充說明

有時候我們可能會看到if (NULL == p) 這樣古怪的格式。不是程序寫錯了,而是程序員為了防止將if (p == NULL) 誤寫成if (p = NULL),有意把p和NULL顛倒。編譯器認為if (p = NULL) 是合法的,但是會指出if (NULL = p)是錯誤的,因為NULL不能被賦值。類似的還有if ( 100 == i )等。

程序中有時會遇到if/else/return的組合,應該將如下不良風格的程序:

            if(condition)
              return x;
            return y;
        改寫為:
            if(condition) {
              return x;
            }else {
              return y;
            }

或者改寫成更加簡練的:

            return  condition?x:y;

4.9.6 switch結構

有了if/else語句,為什么還要switch語句?

switch是多分支選擇語句,而if/else語句只有兩個分支可供選擇。雖然可以用嵌套的if語句來實現多分支選擇,但那樣的程序冗長難讀,更重要的是可能在匹配到某一個分支前執行多次無謂的比較。相反,switch的效率比if/else結構高,這正是switch語句存在的理由。

switch語句的基本格式是:

            switch (表達式)
            {
            case常量表達式1:
              語句序列
              break;
            case常量表達式2:
              語句序列
              break;
            …
            default :
              …
              break;
            }

【提示4-18】: (1)switch沒有自動跳出的功能,每個case子句的結尾不要忘了加上break,否則當表達式與某一個case子句匹配并執行完它的語句序列后,將接著執行下面case子句的語句序列,這就導致了多個分支重疊(除非有意讓多個分支共享一段代碼)。break語句只是一個“jmp”指令,其作用就是跳到switch結構的結尾處。

(2)不要忘記最后那個default子句。即使程序真的不需要default處理,也應該保留語句default : break;,這樣做并非多此一舉,而是為了防止別人誤以為你忘了default處理,以及出于清晰性和完整性的考慮。

我們舉一個選擇結構的例子(見示例4-10):輸入一個年份,判斷是否為閏年。判斷閏年的方法是:如果該年能被4整除但不能被100整除,或者能被400整除,就是閏年。

示例4-10

                #include <stdio.h>
                int main(int argc, char* argv[])
                {
                      unsigned long year;
                      printf("Input a year : ");
                      scanf("%lu", &year);
                      if ( ( year % 4 == 0 && year % 100 != 0 ) || ( year % 400 == 0 ) ) {
                          printf("Year %lu is a leap year.\n", year);
                      }else {
                          printf("Year %lu is not a leap year.\n", year);
                      }
                      return 0;
                }
主站蜘蛛池模板: 明水县| 双桥区| 慈利县| 内黄县| 新宁县| 鹤庆县| 普定县| 金昌市| 康保县| 磴口县| 博客| 望江县| 广德县| 康平县| 长岛县| 辽阳市| 普定县| 通榆县| 麦盖提县| 浦江县| 吉木乃县| 开阳县| 祁东县| 都昌县| 邳州市| 佛山市| 大同市| 南召县| 青铜峡市| 汤阴县| 洱源县| 林甸县| 上蔡县| 乐业县| 昭觉县| 富蕴县| 兖州市| 瓮安县| 彰化县| 松桃| 营口市|