程序控制语句分为两类:控制选择和控制循环。具体实现涉及关系表达式、条件表达式和逻辑表达式,关系表达式又可以用来构成逻辑表达式。
4.1.1 写错关系运算符
表4-1是C语言提供的6种关系运算符及其含义。
表4-1 C语言提供的6种关系运算符及其含义
它没有“=>”和“=<”运算符。判断错误的方法很简单,一般是说大于(>)等于(=)或小于(<)等于(=),不会说成等于大于或等于小于,记住相等(=)是最后叙述,当然放在最后。同理推之,不等(!)于(=)的格式必然是“!=”。
讲到等(=)于(=),显然没有停顿,所以在两个等(=)符号之间不能有空格。其实,这4个运算符之间都不能有空格,否则就成了不存在的运算符。不过最容易出现的错误还是在两个等号之间留一个空格。
【例4.1】下面的程序是对运算后的变量求和,找出其中的错误。
#include <stdio.h>void main(){ int i=2,j=5,k=6,sum=1; i=i<5; k==j<0; sum==i+j+k; printf(/"%d+%d+%d=%dn/",i,j,k,sum);}
程序的输出结果为
1+5+6=1
虽然编译给出警告,但并不影响产生执行文件,只是结果有点出人意料。
因为关系运算符的优先级高于赋值运算符,所以语句“i=i<5;”中先计算“2<5”,其结果为真,即为1,由赋值运算符赋给i,得到“i=1”。
在6种关系运算符中,<、<=、>和>=运算符的优先级别相同,但高于==和!=。语句“k==j<0;”并没有价值,因为程序中没有用到这个结果。其原因正是运算符“<”的优先级比“==”的高,使得C语言先计算它,“5<0”不成立,其结果为0。然后再计算“6==0”,显然也不成立,结果为0。虽然这条语句的结果为0,但并不影响k的值,k仍然为6。
对语句“sum==i+j+k;”而言,“i+j+k=1+5+6=12”。判断“1==12”不成立。但这个结果并不影响sum的值,所以sum仍然为1。由此得到错误的输出“1+5+6=1”。
由此可见,程序多半是将赋值运算符“=”错认为关系运算符“==”。如果将这两条语句改为如下语句:
k=j<0;sum=i+j+k;
对于语句“k=j<0;”而言,“5<0”不成立,其结果为0。将结果赋给k,得到“k=0”。程序运行结果将变为:1+5+0=6。
由此可见,应该消除所有的警告语句。尽管有的程序能获得正确结果,但也不能让它存在此类信息。例如下面的程序。
【例4.2】程序语句错误、运行结果正确的例子。
#include <stdio.h>void main(){ int i=0,sum=0; for(i==1;i<11;i++) sum=sum+i; printf(/"sum=%dn/",sum);}
for循环中应该是“i=1”。这里的错误虽然不影响运算结果,但程序的执行效率是不一样的。因为对于“i==1”,使得循环从“i=0”开始,多运行一次。只是这次运算加的是“0”值,才使得结果也是“sum=55”。
4.1.2 混淆表达式和关系表达式的值
由上一小节可见,不能混淆表达式和关系表达式,以及关系表达式的值。
【例4.3】演示表达式书写不规范的例子。
#include <stdio.h>void main(){ int a=4,b=3,c=2,d,e; d=a>b>c; e=a>(b>c); printf(/"d=%d,e=%dn/",d,e);}
对于“d=a>b>c;”,“>”运算符是自左至右运算,所以先算“a>b”的值为1,再执行关系运算“1>c”,得值0赋给d。
语句“e=a>(b>c);”使用括号改变运算顺序,先计算括号里面的“3>2”,结果为1,再计算“4>1”,结果为1,赋给e。程序输出结果为
d=0,e=1
尽管程序编译成功,但有警告信息。由此可见,编译系统并不希望采用这种赋值方法。即使像“(a+8)>=(b+3);”这种形式,也是不喜欢的。各个编译系统对此的反应可能不一样,这里使用VC6.0,该编译系统认为对连续两个情况的转换存在不安全性。对于单纯由表达式加“;”号构成的语句,认为这个结果没有被实际使用,所以也给出警告信息。明白这一点,就好理解了。
结论:关系表达式的值本身没有价值,只是用来构成合理的语句。最常用的是两种:一种是直接提供给控制语句,以便根据这个值执行相应动作;另一种是用来构成逻辑表达式,逻辑表达式的值再作为控制语句的控制依据。
表达式可以是算术表达式、逻辑表达式、赋值表达式、字符表达式等。例如:
b,a+b,d-c,x=5,y=7,/'d/',/'c/',a>b,b>c
用关系运算符将两个表达式连接起来的式子,称为关系表达式。例如:
a>b,a+b<=d-c,(x=5)>=(y=7),/'d/'!=/'c/'
关系表达式的值是个逻辑值,即“真”和“假”。C语言没有逻辑(bool)型数据类型,仅以1代表“真”,以0代表“假”。所以当把关系表达式的值赋给整数类型的变量时,编译系统会给出警告,认为这种赋值缺少安全性。
也可以用关系运算符将两个关系表达式连接起来构成新的关系表达式。
(a>b)>( b>c)
若a=4,b=3,c=2,对于如下的表达式,则有:
a>b的值为“真”,表达式的值为1。
(a>b)==c-1的值为“真”(因为a>b值为1,等于c-1的值),表达式的值为1。
a-b<c的值为“真”,表达式的值为1。
d=b>c,d的值为1。
e=a>b>c,e的值为0。因为“>”运算符是自左至右运算,所以先算“a>b”的值为1,再执行关系运算“1>c”,得值0赋给e。
由分析可知,可以将例4.3的程序改写为例4.4的形式,以消除警告信息。
【例4.4】表达式书写规范的例子。
#include <stdio.h>void main(){ int a=4,b=3,c=2,d,e; d=a>b; d=d>c; e=b>c; e=a>e; printf(/"d=%d,e=%dn/",d,e);}
4.1.3 混淆逻辑表达式和逻辑表达式的值
【例4.5】这个程序的本意是让f的值大于等于2,结果出乎意料之外,输出为“f<2”,请分析原因并改正错误。
#include <stdio.h>void main(){ int a=4,b=3,c=2,d,e,f; d=a>b; d=d>c; e=b>c; e=a>e; f=a<b&&c>e+2; if(f>=2) printf(/"f>=2n/"); else printf(/"f<2n/");}
因为算术运算符的优先级高,所以先做“e+2”运算,实际上右边的表达式变为“c>3”,其结果为0。左边“a<b”运算的结果为0,将0赋给f,导致输出f<2。
将该语句改为
f=(a<b&&c>e)+2;
即可得到f=2,从而得到正确的输出“f>=2”。
C语言提供的逻辑运算符只有如下3种。
&& 逻辑与
|| 逻辑或
! 逻辑非
含有逻辑运算符的表达式就是逻辑表达式。显然,逻辑表达式的值应该是一个逻辑量“真”或“假”。C语言编译系统在给出逻辑运算结果时,以数值1代表“真”,以0代表“假”。
逻辑运算符进行逻辑运算,当然运算符两侧参加运算的值应该是逻辑值,所以上节说的关系表达式可以作为参加逻辑运算的对象。其实,C语言逻辑运算符两侧的运算对象并不限于关系表达式,即它不但可以是数值0和1,或者是0和非0的整数,也可以是任何类型的数据,包括字符型、实型或指针型等。不过,逻辑运算要求将两侧的对象先转化为逻辑值。也就是系统最终以0和非0来判定它们是属于“真”还是“假”,然后参与逻辑运算。如果一个量不为0,则为“真”,用1代表它。如果为0,则为“假”,以0代表它。例如
/'a/'&&/'b/'
的值为1(因为字母a和字母b的ASCII码的值都不为0,都按“真”处理)。而
a<b&&c>e;
的值则要先计算逻辑运算符两边的关系表达式的值。如果a=4,b=3,c=2,e=1,则“a<b”为0,而“c>e”为1,结果为0。其实,对于“&&”运算符,左边的式子为0已经决定结果为0,没有必要再计算右边的关系表达式,在实际计算中,并不计算右边的表达式。
在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的解时,才执行该运算符。
逻辑运算符的优先级低于关系运算符,所以能保证在求出关系运算符表达式的值之后进行逻辑运算。
4.1.4 混淆逻辑运算符和位运算符
最容易混淆“&&”和“&”,“||”和“|”。前者是逻辑运算符,后者是位运算符。位运算符的优先级比逻辑运算符高。
【例4.6】将“<”和“>”错认为移位运算符的例子。
#include <stdio.h>void main(){ int a=4,b=3,c=5; if(a<<b) printf(/"%d<%dn/",a,b); else printf(/"%d>%dn/",a,b); if(b>>c) printf(/"%d>%dn/",b,c); else printf(/"%d<%dn/",b,c);}
这个程序将“<”错为“<<”,将“>”错为“>>”,得到如下的错误输出。
4<33<5
“<<”和“>>”是移位运算符,相当于乘除法运算。
4<<3=4*2 3=32,32不是0,输出“4<3”。
3>>5=3*2 -5=0,值为0,用else输出“3<5”。
将这两个符号修改即可得到如下的正确输出。
4>33<5
混淆“&&”和“&”,“||”和“|”的情况,将在后面举例。