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]
;
  

《C语言解惑》