讀古今文學網 > C語言解惑 > 4.2 程序控制語句容易出現的問題 >

4.2 程序控制語句容易出現的問題

C語言控制程序走向的原理就是利用條件的值,也就是根據條件的邏輯值改變程序執行順序的走向。條件可以分為兩類:一類是根據條件的邏輯值改變執行的順序,另一類是根據規定的循環條件,重複執行某段程序。

除了條件本身的語句錯誤之外,還涵蓋邏輯表達式錯誤和關係表達式錯誤。所以編程時,這部分最容易產生錯誤。

4.2.1 條件分支語句的錯誤

條件分支是指使用if的語句,這是用錯概率最高的語句。

1.條件不正確

【例4.7】找出下面程序中的錯誤。


#include <stdio.h>
void main 
( 
)
{
      int a
, b
;
      scanf
("%d
,%d"
,&a
, &b
);
      if
(a=b
) printf
("%d
等於%d\n"
,a
,b
);
      else    printf
("%d
不等於%d\n"
,a
,b
);
}
  

【解答】「a=b」賦值語句將if語句中的a重新賦值為b,即變成「if(b)」,表達式就變成判斷b是否為零。如果輸入的b不為零,就輸出if語句後面的打印語句,打印結果變成「b等於b」。如果輸入的b為零,就輸出else語句後面的打印語句,打印結果變成「0不等於0」。其實,輸入給a變量的值根本沒起作用,即上面的程序實際上等效於下面的程序。


#include <stdio.h>
void main 
( 
)
{
      int b
;
      scanf
("%d"
,&b
);
      if
(b
)   printf
("%d
等於%d\n"
,b
,b
);
      else    printf
("%d
不等於%d\n"
,b
,b
);
}
  

將「a=b」改為「a==b」即可消除錯誤。

不要以為將「==」錯為「=」都能通過編譯,需要具體問題具體分析,不能一概而論。

【例4.8】分析下面程序不能通過編譯的原因。


#include <stdio.h>
void main
()
{
     int num=0
;
     printf
("
本程序是判斷輸入一個整數的奇偶性。請輸入:"
); 
     scanf
("%d"
,&num
);
     if
(num % 2 =0 
) printf 
("
數字 %d 
是偶數。\n"
,num
); 
     else            printf 
("
數字 %d 
是奇數。\n"
,num
); 
}
  

編譯針對if語句給出如下出錯信息。


error C2106
: '=' 
: left operand must be l-value
  

「=0」的含義是把0賦給左邊的變量,而「num%2」是對變量的取模操作,所以這個表達式錯誤。這種錯誤稱為編譯時錯誤。

將if表達式改為「num%2==0」,則是把「num%2」的值與0比較,程序正確,編譯通過。順便說一下,如果這時將「&num」的「&」號去掉,也能通過編譯,但運行出錯,這就是運行時錯誤。

不能通過編譯的錯誤稱為編譯時錯誤,能通過編譯而運行出錯則稱為運行時錯誤。

2.沒有正確使用復合語句

下面的程序是典型的錯誤之一。

【例4.9】在判斷語句後沒有正確使用復合語句的錯誤。


#include <stdio.h>
void main
()
{
     int Max=50
, a
;
     printf
("
輸入一個整數:"
); 
     scanf
("%d"
,&a
);
     if
(a>Max
) 
        printf
("
限定為"
); 
        a=Max
;
     printf
("a=%d\n"
,a
);
}
  

上面的程序原想限制輸入不能大於50,如果大於50,則取50。結果是不管輸入何值,始終都取50。其原因是if只對一條語句有效,多於一條的語句必須將它們括起來,構成復合語句,即


(1
)if 
(條件) {     //
多條語句}
(2
)if 
(條件) {     //
多條語句}
     else {            //
多條語句}
  

將其修改為:


if
(a>Max
)
{
    printf
("
限定為"
); 
    a=Max
;
}
  

即可得到正確輸出。下面是三組輸入對應的輸出結果。


輸入一個整數:
60
限定為50
輸入一個整數:
50
a=50
輸入一個整數:
30
a=30
  

3.判斷重複

【例4.10】下面是根據a大於、等於和小於0三種情況輸出不同結果的程序,編譯沒有錯誤,但運行結果都會輸出「0>0.」,請改正程序中的錯誤。


#include <stdio.h>
void main
()
{
     int a
;
     printf
("
輸入一個整數:"
); 
     scanf
("%d"
,&a
);
     if
(a=0
) printf
("%d=0.\n"
,a
);
     if
(a<0
) printf
("%d<0.\n"
,a
); 
     else    printf
("%d>0.\n"
,a
);
}
  

運行結果都是「0>0.」,是因為if的邏輯表達式不對,「a=0」是賦值運算符,所以不管輸入何值,a均被設置為0,使得第1條if語句總是「if(0)」。條件不成立,不執行打印語句,轉而執行下一條if語句,即執行「if(0<0)」,條件也不成立,執行else的打印語句。因此,不管輸入何值,都執行這個打印語句,輸出「0>0.」。

不過,僅僅將「a=0」改為「a==0」並不能解決問題,因為如果輸入0值,程序會輸出兩行信息。正常情況下,程序根據「if(a==0)」的判別,在執行打印輸出「0=0.」之後,應該結束運行。但程序沒有結束,而是繼續執行第2個if語句,所以需要為第1個if語句增加一個else語句,使其退出這個回路,也就是其他的判斷放在它的else回路內去實現。

改正後的程序如下。


#include <stdio.h>
void main
()
{
     int a
;
     printf
("
輸入一個整數:"
); 
     scanf
("%d"
,&a
);
     if
(a==0
) printf
("%d=0.\n"
,a
);
     else
       if
(a<0
)
              printf
("%d<0.\n"
,a
); 
       else
              printf
("%d>0.\n"
,a
);
}
  

為了增加易讀性,可以採用如下格式。


#include <stdio.h>
void main
()
{
     int a
;
     printf
("
輸入一個整數:"
); 
     scanf
("%d"
,&a
);
     if
(a==0
) printf
("%d=0.\n"
,a
);
     else{if
(a<0
)
                  printf
("%d<0.\n"
,a
); 
             else
                  printf
("%d>0.\n"
,a
);
     }
}
  

在只有一個if語句時,一般不會漏掉else語句,但在if嵌套中,容易發生漏掉else的情況,從而導致與原設計不同的效果。

4.注意else的執行原則

else總是跟在同一對花括號中,與它最靠近的那個尚未匹配的if相結合。這一點一定要注意,以免設計的程序與原定的走向不一樣。

【例4.11】下面程序的本意是區分a等於零和不等於零兩種操作,分析並改正程序中存在的問題。


#include <stdio.h>
void f
(int a
)
{ printf 
("%d\n"
,5*a
);}
void main
()
{
      int a
,b
,c
;
      printf
("
輸入兩個整數:"
); 
      scanf
("%d%d"
,&a
,&b
);
      if
(a==0
) 
      if
(b==0
) printf 
("error\n"
); 
      else {
         c=a+b
; 
         f 
(c
); 
      }
}
  

【分析】根據所給目的,知道這個判斷是要區分兩種主要的情況:a=0與a≠0。

在a=0情況下,如果b=0,程序輸出error。如果b≠0,程序任何事情也不做。

在a≠0情況下,此程序將c置為a+b,然後再用c作為參數來調用f函數。

實際上,此程序判斷的執行卻不是如上順序。因為else總是跟在同一對花括號中,與它最靠近的那個尚未匹配的if相結合,所以上述程序實際上是如下這個樣子。


if
(a==0
){
      if
(b==0
) printf 
("error\n"
); 
      else {
               c=a+b
; 
               f 
(c
); 
           }
}
  

由此可見,在a≠0時,程序任何事情也不做。

為使else與第一個if語句相結合,達到原設計思想的判斷程序的目的,在程序中採用「}」將第2個if語句隔離開來,保證else與第1個if語句結合,從而達到區分a是否等於零的兩種情況。


if
(a==0
){
      if
(b==0
) printf 
("error\n"
); 
}
else{
      c=a+b
; 
      f 
(c
); 
}
  

經過這種處理,第2個語句就不與else在同一對花括號裡了。總之,在碰到這種情況時,一定要寫出正確的語句結構。原則是:不要忘記else語句是跟最近一個尚未匹配的if語句相配對。

即使按語法所寫的語句也仍然可能具有二義性,甚至表達的根本不是想要的意思,這樣造成的錯誤是「邏輯錯誤」。邏輯錯誤也是比較嚴重的錯誤,應引起編程者的足夠重視。

完整的程序如下。


#include <stdio.h>
void f
(int a
)
{
  printf 
("%d\n"
,5*a
);
}
void main
()
{
     int a
,b
,c
;
     printf
("
輸入兩個整數:"
); 
     scanf
("%d%d"
,&a
,&b
);
     if
(a==0
){
           if
(b==0
) printf 
("error\n"
); 
     }
     else{
           c=a+b
; 
           f 
(c
); 
     }
}
  

下面是3種情況的輸出結果。


輸入兩個整數:
4 5
45
輸入兩個整數:
0 0
error
輸入兩個整數:
0 8
  

對於第3種情況,程序任何事情都沒做。三種情況的運行全部正確。

4.2.2 控制重複的分支語句

1.使用for語句的注意事項

使用for語句的低級錯誤是將「=」號錯為「==」號,或者將「;」錯為「,」號。下面是一個典型的例子。

【例4.12】下面是計算1+2+…+10的程序,找出其中的錯誤。


#include <stdio.h>
void main
()
{
     int i
,sum=0
;
     for
(i=1
;i==11
;i++
)
           sum=sum+i
;
     printf 
("sum=%d\n"
,sum
);
}
  

運行結果是「sum=0」。

for語句首先求表達式i=1的值,得到i=1。其次判斷表達式1==11的值,不為0則執行()後的加法語句。如果為0,則不再重複加法操作,而去執行下面的輸出語句。這裡的邏輯表達式「1==11」的論述是不成立的,所以其值是0,因此一次加法都不做,直接去執行輸出語句。這個式子要到「11==11」才為1,選取的條件不對。

應該使用「i!=11」才正確。第1次「1!=11」成立,直到「11!=11」才為0,結束循環。一般使用如下規範的形式。


for
(i=1
; i<11
; i++
)
  

【例4.13】演示「==」與「!=」的區別。


#include <stdio.h>
void main
()
{
     int i
,sum=0
;
     for
(i=1
;i<5
;i++
)
     {
          sum=sum+i
;
          printf 
("%d
,%d
,%d\n"
,i
,i==3
,i
!=3
);
     }
     printf 
("sum=%d\n"
,sum
);
}
  

輸出結果如下:


1
,0
,1
2
,0
,1
3
,1
,0
4
,0
,1
sum=10
  

【例4.14】下面是計算輸入字符串長度的程序,可惜計算的結果總是1。分析一下原因並改正之。


#include <stdio.h>
int len
(char str
)
{
        int i
;
        for
(i=1
;str[i]
!='\0'
;i++
)
        return
(i
);
}
void main
()
{
        char st[100]
;
        printf 
("
輸入字符串:\n"
);
        gets
(st
);
        printf 
("
字符串長度=%d\n"
,len
(st
));
}
  

雖然for語句本身實現了計算功能,但它也需要一個不做事的循環體以構成完整的for結構。程序最後返回的長度,就是最後一個字符的長度,所以總為1。不做事,就是一個「;」號,即for()的體外缺少一個「;」號。將len函數修改為:


int len
(char str
)
{
         int i
;
         for
(i=1
;str[i]
!='\0'
;i++
)
;
         return
(i
);
}
  

運行示範如下:


輸入字符串:
How are you
?
字符串長度=12
  

for語句的3個表達式可以省略1個、2個甚至3個,但無論省略幾項,兩個分號不能省略。在表達式1和表達式3省略的情況下,應該被執行的部分便什麼也不執行,在表達式2或3個表達式都省略的情況下,即形如


for
(表達式1
; ; 表達式3
)語句;
  


for
( ; 
; 
)語句;
  

的for語句將無限循環下去。這是因為作為條件的表達式2不出現時,編譯程序認為其值恆為真。例如,下面的程序將不停地打印出字符a。


#include <stdio.h>
 void main 
( 
)
 {
      for 
( 
; 
; 
)
          putchar 
( 'a' 
);
 }
  

可以為它們設計結束循環的條件。

for語句的條件表達式可以省略,這是它同if語句、while語句及do~while語句的區別之一。

【例4.15】下面是使用逗號運算符的程序,它用字符進行循環控制,演示計算1+2+3…+9+10的和,但輸出卻是如下的結果:


a
,1 b
,2 c
,3 d
,4 e
,5 f
,6 g
,7 h
,8 i
,9 j
,10
  

下面是它的源程序,請找出程序中的錯誤。


#include <stdio.h>
void main
()
{
         char c
;
       int j
,sum=0
;
         for
(c='a'
,j=1
; c<'k'
; c++
,j+1
)
         printf
("%c
,%d\n"
,c
,sum=sum+j
);
}
  

for語句中的j+1只是把j加1,j並沒改變,一直保存為1。這就使「sum=sum+j」只是每次加1,為此得到了上面的結果。

應該使「j=j+1」,也就是寫成「++j」或者「j++」。例如:


for
(c='a'
,j=1
; c<'k'
; c++
,j++
)
  

在這個地方,它與「j=j+1」的效果一樣。修改後的輸出結果如下。


a
,1 b
,3 c
,6 d
,10 e
,15 f
,21 g
,28 h
,36 i
,45 j
,55
  

2.使用switch語句的注意事項

【例4.16】下面程序在編譯時會出現一個警告,但不影響產生執行文件。你認為有必要排除這個警告嗎?


#include <stdio.h>
void main
()
{
     int i
;
     for
(i=2
;i<10
;++i
){
          switch
(i
){
               case 2
:    case 3
:
               case 5
:    case 7
:
                      printf
("%d\n"
,i
);
                 break
;
           defualt
:
                      printf
("%d\n"
,i
);
                 break
;
          }
     }
}
  

應該排除,因為很可能會給出錯誤的結果。這個程序是拼錯關鍵字。正確拼寫為「default」。關鍵字在編輯系統下的顏色區別於語句,所以不用編譯即可發現這類錯誤。

這個警告是必須消除的,因為在其他情況下,程序不會執行第2個printf語句,執行結果是錯的。

在使用switch語句時,要注意不要在應該有break語句的地方漏掉break語句。C語言中的break語句是退出該部分程序,如果漏掉了break語句,則程序順序執行。為了便於查錯,建議在省去break語句的地方加上註釋。例如:


switch 
(N
) {
        case 1
: printf 
( "one" 
); 
                break
;
        case 2
: printf 
( "two" 
);
                //
沒有break
語句
        case 3
: printf 
( "three" 
);
                break
;
}
  

switch的括號內可以是數值,也可以是字符。要引起注意的是如何獲得正確的值和處理非法值。常常用它作為菜單選擇。

3.使用while語句的注意事項

它有while和do~while兩種格式,其實出錯概率大的就是邏輯表達式。

【例4.17】下面的程序用來計算1+2+3+…+10的值,找出程序中的錯誤。


#include <stdio.h>
void main
()
{
     int i=0
, sum=0
;
     while
(i<11
);
          sum=sum+
(i++
);
     printf
("sum=%d\n"
,sum
);
}
  

「while(i<11);」不能有「;」號,去掉「;」即可。這個程序使用「i++」不安全,它與編譯系統有關係,可以改為


while
(i<11
){
         sum=sum+i
;
         i++
;
}
  

以避免不同編譯系統的差異。

【例4.18】下面的程序用來計算輸入非0數的和,找出它的錯誤。


#include <stdio.h>
void main
()
{
     int a
, sum=0
;
     printf
("
輸入0
結束,請輸入一個整數:"
);
     scanf
("%d"
,&a
);
     while
(a
!=0
){
           sum=sum+a
;
           printf
("sum=%d\n"
,sum
);
           printf
("
輸入0
結束,請輸入一個整數:"
);
           scanf
("%d"
,&a
);
           printf
("sum=%d\n"
,sum
);
     }
}
  

首先判斷出多了一條打印語句,然後判斷哪一條是多餘的。這個程序在循環體外取得輸入數據,所以循環體內的第1個打印語句是必需的,而最後一個是多餘的。為了與下面的例子比較,為它增加一句。修改後的程序如下:


#include <stdio.h>
void main
()
{
     int a
, sum=0
;
     printf
("
輸入0
結束,請輸入一個整數:"
);
     scanf
("%d"
,&a
);
     while
(a
!=0
){
          sum=sum+a
;
          printf
("sum=%d\n"
,sum
);
          printf
("
輸入0
結束,請輸入一個整數:"
);
          scanf
("%d"
,&a
);
     }
     printf
("
再見!\n"
);
}
  

運行示範如下。


輸入0
結束,請輸入一個整數:
25
sum=25
輸入0
結束,請輸入一個整數:
58
sum=83
輸入0
結束,請輸入一個整數:
29
sum=112
輸入0
結束,請輸入一個整數:
0
再見!
  

4.使用do~while語句的注意事項

while是先進行條件判斷後執行循環體,而do~while則是先執行循環體後判斷條件。

【例4.19】分析下面程序的錯誤。


#include <stdio.h>
void main
()
{
         int a
, sum=0
;
         do{
            printf
("
輸入0
結束,請輸入一個整數:"
);
            scanf
("%d"
,&a
);
            sum=sum+a
;
            printf
("sum=%d\n"
,sum
);
         }while
(a
!=0
)
         printf
("
再見!\n"
);
}
  

while與if和for的判別語句不能有「;」號,下一條語句才有「;」號,從而組成循環體。但do~while語句不同,循環體在do和while之間,所以邏輯表達式之後以「;」號結束。程序中漏掉「;」號,這一句應該為:


}while
(a
!=0
);
  

修改後的程序示範運行如下:


輸入0
結束,請輸入一個整數:
25
sum=25
輸入0
結束,請輸入一個整數:
58
sum=83
輸入0
結束,請輸入一個整數:
29
sum=112
輸入0
結束,請輸入一個整數:
0
sum=112
再見!
  

注意比較這個程序與while程序執行的異同。例4.18是先判斷循環條件,條件符合就結束循環。而例4.19則是先執行一次循環體,然後用在循環體內取得的數據作為判斷條件,所以它又輸出一次「sum=112」。

下面是在循環體內增加1條if語句以避免多打印1次的完整程序。


//
增加if
語句的程序
#include <stdio.h>
void main
()
{
      int a
, sum=0
;
      do{
          printf
("
輸入0
結束,請輸入一個整數:"
);
          scanf
("%d"
,&a
);
          if
(a==0
) break
;
          sum=sum+a
;
          printf
("sum=%d\n"
,sum
);
      }while
(a
!=0
);
      printf
("
再見!\n"
);
}
  

5.結束循環不正確

【例4.20】break和continue的區別。

下面是一個想打印數碼1至5,但不包括數碼3的程序,運行結果卻只打印出數碼1和2。由這個程序可以看出break和continue的區別。


#include <stdio.h>
void main
( 
)
{
     int i=0
;
     while 
( ++i<=5 
)
     {
        if 
( i==3
)
               break
;
        printf 
( "%d  "
,i 
);
     }
     printf 
( "\n"
);
}
  

break語句是中止語句,是從循環語句中跳出的一個極其方便的方法。循環語句中的break語句一執行,程序立即無條件地從包含break語句的最小while、do~while、for以及switch循環中跳出。當i=3時,滿足條件,跳出while循環體,所以只輸出1和2。題目要求的只是不輸出3,所以不允許跳出循環體。將「break;」改為


continue
;
  

即可。continue語句是繼續語句,用在while、do~while和for這3種語句中,它指出立即進行下次條件表達式的判斷。具體來說,就是:如果在while和do~while語句中,一執行continue語句,則立即進行while後()內的條件表達式的判斷;如果在for語句中,一執行continue語句,則在判斷表達式2之前先求解表達式3。

【例4.21】本程序演示了break的使用方法。假設已知產值及產值增長速度,求解產值增長一倍時所需年數。程序中所求年數存於y中,當前產值存於c中,每年產值增長速度使用scanf函數存放在a中。


#include <stdio.h>
void main
( 
)
{
     float
  c1
,a
,c
;
     c=100000000.00
;
     for
( 
; 
; 
)                    //
第6
行
     {
            int
  y=0
;
            printf
( "A=" 
);
            scanf 
( "%f"
, 
&a 
);
            if 
( a<=0.0 
) 
                    break
;
            c1=c
;
            for 
( 
; 
; 
)               //
第14
行
            {
                   c1*=
(1+a
);
                   ++y
;
                   if 
( c1>2*c 
)   break
;
            }
            printf 
( "A=%f\t year=%d\n"
,a
, y 
);
     }
}
  

運行結果如下所示。


A=
0.2
A=0.200000       year=4
A=
0
  

程序中使用了兩個break語句:第1個break語句表示當所輸入的增長速度a≤0時,說明不合題意,需立即退出第6行的for循環語句;第2個break語句表示當產值已達到2倍時,y中的數就是所求的年數,這時已無須再執行第14行的for語句了,必須從該for語句中退出循環。

由此可見,對於第1個break,它的最小for語句在第6行;對於第2個break,它的最小for語句在第14行。

【例4.22】下面程序計算三個數相乘之積和連續相除之商,但運算結果不對,請分析原因並改正之。


#include<stdio.h>                    //
包含系統頭文件
double mult
( double
,double
,double
);
double pe
( double
,double
,double
);
void main
( 
)
{
     double a
,b
,c
,d=0
;
     scanf
("%lf%lf%lf"
,&a
,&b
,&c
);
     d=mult
(a
,b
,c
);
     printf
("%lf*%lf*%lf = %lf\n"
,a
,b
,c
,d
);
     d=pe
(a
,b
,c
);
     printf
("%lf/%lf/%lf = %lf\n"
,a
,b
,c
,d
);
}
double mult 
( double a
,double b
,double c
)
{ return a*b*c
;}
double pe 
( double a
,double b
,double c
)
{ if
(a*b*c
!=0
) return a/b/c
;}
  

其實,這個程序是不完整的,雖然有警告信息並可以運行,但會出現錯誤(這就是前面提到的運行時錯誤),下面是運行出錯的例子。


2 5 0
2.000000*5.000000*0.000000 = 0.000000
2.000000/5.000000/0.000000 = -1.#IND00
  

雖然pe函數想得很巧妙,判斷三個數的乘積是否為零,只要為零就不能做除法,但這時它也不應該直接退出程序,而必須返回一個值。這個程序有兩條結束運行的分支,每個分支都必須有return語句。

主程序沒有對異常進行處理,仍然執行輸出語句,所以給出錯誤信息。

第1種改正的方法是使pe輸出異常值,即改為:


double pe 
( double a
,double b
,double c
)
{
        if
(a*b*c
!=0
) return a/b/c
;
        else return 1
;
}
  

然後在主程序中根據異常信號輸出信息。例如:


d=pe
(a
,b
,c
);
 if
(d==1
) printf
("
被除數有為0
的情況,退出!\n"
);
 else     printf
("%lf/%lf/%lf = %lf\n"
,a
,b
,c
,d
);
  

這時的運行結果就變為


2 5 0
2.000000*5.000000*0.000000 = 0.000000
被除數有為0
的情況,退出!
  

第2種方法是設計一個能處理被除數為0的情況的pe程序,讓它給出異常信息,然後直接結束程序的運行。這要用到exit(1)語句,它包含在頭文件stdlib.h中。

完整的程序如下:


#include<stdio.h>               //
包含系統頭文件
#include<stdlib.h> 
double mult
( double
,double
,double
);
double pe
( double
,double
,double
);
void main
( 
)
{
      double a
,b
,c
,d=0
;
      scanf
("%lf%lf%lf"
,&a
,&b
,&c
);
      d=mult
(a
,b
,c
);
      printf
("%lf*%lf*%lf = %lf\n"
,a
,b
,c
,d
);
      d=pe
(a
,b
,c
);
      printf
("%lf/%lf/%lf = %lf\n"
,a
,b
,c
,d
);
}
double mult 
( double a
,double b
,double c
)
{ return a*b*c
;}
double pe 
( double a
,double b
,double c
)
{
        if
(a*b*c
!=0
) 
            return a/b/c
;
      printf
("
被除數有為0
的情況,退出!\n"
);
        exit
(1
);
}
  

運行示範如下。


98.8 2.4 5.6
98.800000*2.400000*5.600000 = 1327.872000
98.800000/2.400000/5.600000 = 7.351190
28.5 0 9843.2
28.500000*0.000000*9843.200000 = 0.000000
被除數有為0
的情況,退出!
98.4 23.5 0
98.400000*23.500000*0.000000 = 0.000000
被除數有為0
的情況,退出!
  

4.2.3 運算符優先級錯誤

控制語句涉及關係表達式和邏輯表達式,所以要特別注意運算符的優先級問題,稍不注意就會造成錯誤的結果。

【例4.23】運算符優先級錯誤。


#include <stdio.h>
#include <stdlib.h>
void main
()
{
     char ch
;
     while
(ch=getchar
()!='#'
)
           putchar
(ch
);               //
在屏幕上顯示出來
     printf
("\n"
);
}
  

程序輸出不是輸入的字符。while語句首先判斷「()」內的邏輯表達式的值,其值若不為「#」執行「()」後的語句,為「#」則跳出while循環。這裡肯定是表達式不正確。設計者原以為通過語句


ch=getchar
()
  

獲得字符,當這個字符不是「#」號時,滿足條件,輸出接收的內容。其實並不是這樣執行的。注意這個邏輯表達式中有「=」和「!=」兩個運算符,而且「=」的優先級最低,即低於「!=」運算符。所以實際的執行過程不是先將輸入的字符賦給ch,而是將輸入字符和字符「#」進行比較,不相等為1,這時將這個比較值(所得的結果值為1)賦給ch。於是執行的是


putchar
(1
);
  

這是個圖形符號。應該先保證ch得到輸入的值,為了使「=」運算符先執行,應使用括號將其括起來,即「(ch=getchar())」,然後再使用「!=」與「#」比較。正確的語句為:


while
((ch=getchar
())!='#'
)
  

修改後的程序運行示範如下。


How are you
?#
How are you
?
  

也可以使用do~while實現這一功能。下面是等效的程序。


#include <stdio.h>
#include <stdlib.h>
void main
()
{
       char ch
;
       ch=getchar
();
       do{
           putchar
(ch
);               //
在屏幕上顯示出來
           ch=getchar
();
     }while
(ch
!='#'
);
       printf
("\n"
);
}
  

修改後的程序示範運行如下:


How are you
?#
How are you
?
  

在一個邏輯表達式中如果包含多個邏輯運算符,則它們的優先級次序為:

(1)!(非)→&&(與)→||(或),!級別為最高。

(2)綜合運算時的優先級別次序為:

!(非)→算術運算符→關係運算符→&&和||→賦值運算符

它們的優先級關係見附錄A,附錄中的優先級高低關係是自上而下依次遞減。

4.2.4 求值順序

【例4.24】改正下面程序的錯誤並給出輸出結果。


#include <stdio.h>
void main
( 
)
{
     int a=2
,b=3
,c=4
,d=5
;
     a=
(a>b
?a
:c>d
?c
:d
);
     if
((a-5
)!=0
) printf
("
等於5\n"
);
     else         printf
( "
不等於5\n"
,a
);
}
  

if語句的邏輯表達式錯誤,應該使用


if
((a-5
)==0
) printf
("
等於5\n"
);
  

現在看a的值是多少。賦值運算符優先級最低,不用管它,將最後的計算結果賦給它即可。對「?:」運算符,例如「a?b:c」,這是個條件表達式,應首先計算a,然後再根據a的值決定計算b還是c,表面上像是「自左向右」運算。但如果使用條件表達式參與?:運算,條件運算符的結合方向為「自右至左」,而不是「自左向右」。這個表達式就相當於


a>b
?a
:(c>d
)?c
:d
  

這裡應先判斷「(c>d)?c:d」。對於這個條件表達式,先求解表達式(c>d),即(4>5)。其值為0(假),此時取表達式d的值作為整個表達式的值,所以c=5。

再判斷「a>b?a:c」,所以a=5。if語句變為


if
(5-5==0
)
  

條件成立,程序輸出「等於5」。所以附錄A中給出?:的運算順序是自右至左。而對於不用條件表達式的情況,把「真」和「假」作為條件,自右至左選取計算目標即可。

【例4.25】判斷下面程序是否錯誤。


#include <stdio.h>
void main
( 
)
{
     double x
,y
,c=5
,sum=0.0
;
       printf
( "
輸入x
和y
的值:"
);
       scanf
("%lf%lf"
,&x
,&y
);
     if
(x
!=0 && y/x<c
) sum=x+c
;
     else sum=y+c
;
       printf
( "sum=%f\n"
,sum
);
}
  

沒有錯誤。有些人可能認為語句


if
(x
!=0 && y/x<c
)
  

一定是錯的,理由是y/x中的x不能為零。其實,只有在x不為零時,才會求y/x;如果x=0,它就執行下一條語句,而不會計算y/x。所以它是正確的語句。下面通過運行示範驗證這一點。


輸入x
和y
的值:
0 2
sum=7.000000
輸入x
和y
的值:
0 0
sum=5.000000
輸入x
和y
的值:
2 4
sum=7.000000
輸入x
和y
的值:
2 16
sum=21.000000
  

求值的順序與優先級不同,這是兩個完全不同的問題。C語言的某些運算符總是以一個已知的特定順序對其操作對像進行求值的,而有些運算符則不是這樣。例如,對於表達式


a<b 
&& c<d
  

如果a大於或等於b,根本不會再去求c<d的值;只有a<b時,才會去求c<d的值。雖然語義規定了首先求a<b的值,但究竟是先求a還是先求b,或者兩者並行運算,也沒有一定之規,這取決於機器及其編譯程序。

在C語言中,只有「&&」、「||」、「?:」和「,」這4個運算符規定了求值的順序。現說明如下:

(1)對「&&」和「||」,首先計算左邊的操作數,只有在必要時才計算右邊的操作數。

(2)對於條件表達式「a?b:c」,首先計算a,然後再根據a的值決定計算b還是c。但特別要注意的是:如果再用條件表達式參與?:運算,條件運算符的結合方向為「自右至左」,而不是「自左向右」。詳見例4.24中的分析。

(3)「,」號運算符首先計算左操作數並丟棄它的計算結果值,然後再計算它的右操作數,並將其計算結果值作為整個逗號表達式的值。例如在f((x,y))裡,「x,y」是逗號表達式,是f的一個參數,先計算x,丟掉它的值,然後再對y求值,並將其結果值作為逗號表達式「x,y」的值,即f的參數。需要注意的是:用於將函數參數隔開的逗號不是逗號運算符,例如f(x,y)。

其餘所有的C運算符都是以不確定的順序來對操作數進行求值運算的,特別要注意,賦值運算符對於求值的順序沒有做出任何規定,正如本例演示的,語句


if
(x
!=0 && y/x<c
)
  

是正確的一樣。編程時應該避免依賴編譯程序,例如語句


i=0
;
while 
(i<n
)
      y[i] = x[i++]
;
  

就是假設了求值的順序。這在有些機器上可能是正常的,但在另一些機器上則不一定,應該避免這種寫法。對這種情況,建議寫成


i=0
; 
while 
(i<n
) {
        y[i]=x[i]
; 
        i++
;
}
  

或最好簡寫成


for 
( i=0
; i<n
; i++ 
)
       y[i]=x[i]
;