1
第 5章 选择结构程序设计
2
在第四章中已学习过的表达式语句、赋值语句等都是顺序执行的语句,用它们编出的程序一定是顺序执行的。然而,要解决一个较为复杂的问题仅有这类顺序执行的语句是不够的。例如要求任一实数的绝对值,
3
下面是 求一个实数绝对值 的一种算法,
4
很明显,仅使用顺序执行的语句实现这种处理就比较困难,因为它需要根据 x的值,选择不同的分支处理。
为了解决这类程序设计问题,计算机程序设计语言一般都提供相应的程序流程控制语句,用于控制程序执行的顺序。
用程序设计语言按上述处理流程编出求 x绝对值的程序需要解决两个问题:首先,条件 x<0的表示及判定;其次,如何根据 x<0的判断结果实现程序的控制转移。这正是在本章中要讨论的内容。
在程序设计语言中,根据某个条件判断的结果,决定程序的控制转移方式,称之为程序的选择控制方式 。
5
5.1 关系运算符与关系表达式关系 运算是对两个操作数进行大小等同的比较运算,
比较的结果为成立不成立。
在程序中,两个量之间的大小关系通常作为某种条件,
以决定程序执行的顺序或执行的操作。
1,关系运算符
C语言定义的 关系 运算符有:
6
> (大于)
< ( 小于)
>= ( 大于等于)
<= ( 小于等于)
== (等于)
!= (不等于)
注意:
等于运算符 = = 为连续两个等于号,主要为区别赋值运算符 = 。 在作相等判断时经常只写一个 =,把相等判断作为赋值运算,且不易发现。如 if ( x==7 ) 错写成 if ( x=7),
原因是赋值会产生一个值。
7
2,关系运算符的优先级和结合性优先级,
(高 ) < > <= >= 相同
(低 ) = = != 相同它们的优先级高于赋值运算符,低于算术运算符。
结合性,自左向右
3、关系表达式由关系运算符,操作数(各种表达式)及小括号组成的运算式。
8
例如:
a>3?a?<?b? a>b!=b a==b<c (正确 )
a>b=c (错误 ) 应写成 a>(b=c)
关系表达式的值:
关系表达式的值是 逻辑值(真或假) 。
条件成立时其值为 1
条件不成立时其值为 0
结果类型为,int
两个操作数的类型不同时,自动转换成相 同类型后再进行比较。
C语言无逻辑型数据,非 0 为真,0 为假。所以 任何 表达式都可作条件,因它们都有值。
9
例,
已知 int x=2,y=1,z=0; 判断以下关系表达式的值:
x>y+1 x>=y+1 x>(y=2)
(值 0) (值 1) (值 0)
x>y=2 x>y==1 x==(x=0,y=1,z=2)
(语法错 ) (值 1) (值 0)
z>y>x /*语法正确,语义错误 */
(值 0)
10
因浮点数的近似表示与计算误差问题,所以应避免对浮点数做,==”、,!=”运算,理论上相等的两个数,可能获得不等的比较结果。
若有 float x,a ; 需要判断,x==a 时,通常用,
fabs(x-a)<1e-6代替 。 1e-6 可根据精度要求改变。 例如,
d=b*b-4*a*c ;
if ( fabs (d ) < 1e-8 )
x = - b / ( 2*a ) ;
else
if ( d>0 )
{ 计算两个实数根 }
else
{ 计算两个复数根 }
11
在程序设计中需要判断 x!=a 时 ( x,a为浮点数),
通常用 fabs(x-a) > 1e-6 代替 x!=a,1e-6 可根据精度要求适当改变。
因 char类型的数据按整数处理,所以可以对两个
char类型数据直接进行比较。
而对两个字符串的比较,则应调用函数库中的 strcmp( )
函数进行,strcmp( )函数的使用详见附录 ⅴ 。
12
5.2 逻辑运算符和逻辑表达式逻辑 运算反映两个操作数之间的 逻辑关系。逻辑 运算符也称 逻辑连接符。
1,逻辑运算符
! 逻辑非 单目运算符,如,!(a>b)
&& 逻辑与 双目运算符,如,(a>b)&&(x>y)
|| 逻辑或 双目运算符,如,(a>b)||(x>y)
其优先级低于关系运算符,高于赋值运算符,左结合 性。
高低
13
2,逻辑表达式
1) 一般形式
e1&&e2
e1 || e2
!e1
e1,e2,任何合法的操作数或表达式,且它们的数据类型不必相同。 值是逻辑值 1(真)或 0( 假),结果类型为,int 。 例如:
int a=3,d;
d = a>=0 && a<=10; 即 1→ d
d = !( a<0 || a>10 ) 即 1→ d
14
int year,leap ;
scanf (,%d”,&year ) ;
leap=year%4==0&&year%100!=0||year%400==0 ;
if ( leap )
printf (,%d is leap year!”,year ) ;
例 1,判断某一年份是否闰年。
满足以下条件之一的年份为闰年,能被 4整除但不能被 100整除;或者能被 400整除。
a<=v && v<=b
而应写成:
a <= v <= b
数学中的 a ≤ v ≤ b 不能直接写成,例 2:
15
(1) 在计算 e1 && e2时,若 e1 的值为假,则不必再计算 e2的值,因为不管 e2 是什么结果,整个表达式的值都是
0 ;只有当 e1值为真时,才需要计算 e2的值,此时整个表达式的值由 e2的值决定。
2) 逻辑表达式求值的优化处理
(2) 在计算 e1||e2 时,若 e1值为真,则不需要计算 e2的值,
整个表达式的值为 1,只有当 e1值为假时,才计算 e2的值,整个表达式的值由 e2的值决定。
16
例:
int a=0,b=5,c ;
c = a++>0 && b--<10 ;
运算结果,c 为 0,a 为 1,b 为 5
因 a++>0 为假( 0),整个表达式的结果已知,
所以 && 后的 b--<10 不需计算,则 b-- 也不计算,故
b的值不变,仍为 5。而对于:
c = ++a>0 && b--<10;
运算结果,c 为 1,a 为 1,b 为 4
其中 ++a 和 b-- 都必须计算,所以 b的值改变了。
17
例:
int a=0,b=5,c;
c = a++ > 0 || b-- < 10;
运算结果,c 为 1,a 为 1,b 为 4
c = ++a > 0 || b-- < 10;
运算结果,c 为 1,a 为 1,b 为 5
18
&& 和 ||尽管它们有各自的优先级和结合性,但因为它们都是 序列点运算符,所以 C在处理它们时将忽略它们的优先级和结合性,统一按从左到右的运算顺序处理。
如对于:
e1 || e2 && e3
若按运算符的优先级,运算顺序应为:
e1 || ( e2 && e3 )
但因 ||是序列点运算符,所以先算 e1,然后再计算 e2&&e3。
19
main ( )
{
int x=1,z=1,y=1,k ;
k=x++ || ++y && ++z ;
printf (,k=%d\nx=%d\ny=%d\nz=%d\n”,k,x,y,z ) ;
}
先做 x++,取 x的当前值为 1,按优化规则,||后的 ++y && ++z
不必计算。但因 ||是序列点运算符,所以在计算到 || 运算符时,
x++已计算过,故 x的值为 2。
下面是一个含有序列点处理问题和逻辑运算符处理的优化问题的例子:
程序运行后的输出是:
k=1
x=2
y=1
z=1
20
main ( )
{
int x=0,z=1,y=1,k ;
k = x++||++y&&++z ;
printf (,k=%d\nx=%d\ny=%d\nz=%d\n”,k,x,y,z ) ;
}
k = 1
x = 1
y = 2
z = 2
如果将上述程序改写成如下形式:
先做 x++,取 x的当前值为 0,按优化规则,
||后的 ++y && ++z 必须计算。但因 ||是序列点运算符,所以在计算到 || 运算符时,x++已计算过,故 x的值为 1。在其后计算 ++y && ++z 时,++y 的值为 2,还不能决定 ++y && ++z 的值,所以 ++z 必须计算,得 z的值为 2。故 ++y && ++z 的值为真,则整个 x++||++y&&++z的值为真,
故把 1赋给 k。
则程序运行后的输出是:
21
main ( )
{
int x=0,z=1,y=0,k ;
k = x++||y++&&++z ;
printf ( "k=%d\nx=%d\ny=%d\nz=%d\n“,k,x,y,z ) ;
}
如果将上述程序改写成如下形式:
k = 0
x = 1
y = 1
z = 1
先做 x++,取 x的当前值为 0,按优化规则,
||后的 y++ && ++z 必须计算。但因 ||是序列点运算符,所以在计算到 || 运算符时,x++
已计算过,故 x的 值为 1 。在其后计算 y++ &&
++z 时,因 y的当前值为 0,已能决定
y++&&++z 的值为假,即为 0,所以 ++z不必须计算,z 的值不变仍为 1。 但按 序列点运算规则,y++已计算过,故 y的值为 1。则整个 x++||y++&&++z的值为假,故把 0赋给 k。
则程序运行后的输出是:
22
main ( )
{
int x=0,z=1,y=1,k ;
k=x++&&++y||++z ;
printf("k=%d\nx=%d\ny=%d\nz=%d\n",
k,x,y,z ) ;
}
如果将上述程序改写成如下形式:
则程序运行后的输出是:
k=1
x=1
y=1
z=2
先做 x++,取 x的当前值为 0,按优化规则其后的 ++y不计算,故 y的值不变仍为 1;因 &&是序列点运算符,所以在计算到 && 运算符时,x++
已计算过,故 x的值为 1;
则 x++&&++y计算的结果为 0;但因其后的运算符为 ||,所以必须计算其后的 ++z,才能得出整个逻辑表达式的值,
因 ++ 在 z 之前,使 z+1
得 2 。 因此整个逻辑表达式的值为真,即为 1,把 1赋给 k,故 k的值为 1。
23
5.3 if 语句在上一节中已经解决了如何表示判断条件的问题。
在这一节中我们将解决:
根据某个条件判断的结果,决定程序的控制转移问题。
if 语句用来表示算法的选择结构。
24
a) 一般形式
if (e) 语句或写成,if (e) 语句其中:
e 是表达判断条件的任何表达式。通常是关系表达式和逻辑表达式。
语句 是条件为真时要执行的语句,该处只能放置一条语句。若要执行多条语句,则必须使用复合语句 。
注意:
整个结构是一条语句(包含 语句 部分); if(e)的后面不能 加分号,否则为当条件为真时执行空语句。
1,单路选择结构的 if 语句
25
b) if 语句的执行与功能先对 e 求值,若 e的值为真(非 0)执行 语句,若
e 的值为假( 0),执行跟在 语句 后面的语句。
e为非 0
非 0
e = 0
语句求 e的值下一条语句
26
例 1:
if ( n ) printf (,%f,,m/n ) ;
这里 n 等同于使用 n != 0 的判定。
例 2:
if ( !n ) n++ ;
这里 !n 等同于 n==0 的判定。
27
if ( ( fp=fopen (,a:\\a.dat”,“r” ) ) == NULL )
{
printf (,Can not open the file!” ) ;
exit (1) ;
}
例 3,赋值判断例
main ( )
{ char ch ;
ch=getchar ( ) ;
if ( ch>=?a?&& ch<=?z? )
ch +=?A?-? a? ;
putchar ( ch ) ;
}
例 4:
28
main ( )
{
float a,b,t ;
scanf (,%f,%f,,&a,&b ) ;
if ( a>b )
{
t = a ;
a = b ;
b = t ;
}
printf (,a=%5.2f,b=%5.2f,,a,b ) ;
}
例 5,使用复合语句例若程序运行时的输入,3.7,-2.3
则程序的 输出,a=-2.30,b= 3.70
29
这种形式是在单路选择结构的 if 语句后面加上 else子句
(包括语句 2)的形式。其中的 e 和 语句 1 的含义与单路选择结构中相同,不同的是当 e 为假时,执行 语句 2。
注意,else子句 可以没有,没有 else子句时就是单路选择结构。 因此单路选择结构是两路选择结构的特例。
else不能单独使用,后面也不能有分号。
2、两路选择结构的 if语句
a) 一般形式
if ( e )
语句 1
else
语句 2
30
b) 两路选择结构 if语句的执行与功能先对 e求值,若 e的值为真(非 0)执行 语句 1,若 e的值为假( 0),执行 else后面的 语句 2。 语句 1或 语句 2执行后,继续执行跟在 语句 2后面的语句。
e为非 0非 0 e=0
语句 1 语句 2
31
main ( )
{
double x,xabs ;
scanf (,%lf,,&x ) ;
if ( x<0 )
xabs = -x ;
else
xabs = x ;
printf (,x=%f,|x|=%f,,x,xabs ) ;
}
c) 求实数 x的绝对值的例
32
if ( e1 )
语句 1
else if ( e2 )
语句 2
else if ( e3 )
语句 3
……
else if ( en )
语句 n
else
语句 n+1
3,多路选择结构的 if 语句
a) 一般形式
33
它用来进行两个以上的多路条件判定,并选择执行相应的处理语句。 其中 ei 与 语句 i 解释同前,最后的 else 属于最后一个 if,并且它可以缺省 。 else 和中间的 else if 不能单独使用,后面也不能有分号。
b) 多路选择结构的 if 语句的执行
(1) 从,表达式 1” 开始顺序进行诸条件的判定,若 ei
条件为真,则执行其后的 语句 i,忽略其后所有的表达式条件判定,语句 i 执行完后便结束整个 if结构的执行,程序直接转移到,语句 n+1”后的语句处执行(如果最后有 else子句) ;
34
(3) 如果所有的条件都不成立,且最后又无 else子句,则这个嵌套的 if 语句中没有任何语句被执行,直接执行该嵌套结构后的那条语句。
e1为非 0
语句 2
语句 n
语句 1e
2为非 0
en为非 0
语句 n+1

en=0
非 0
非 0
非 0
(2) 如果从 ei 起到 en的所有条件判断都为假,则执行 else后的语句 n+1 ( 如果 else子句存在);
35
#include <stdio.h>
main ( )
{
char c ;
c = getchar ( ) ;
if ( c>=?0?&& c<=?9? )
printf (,The character is a digit.” ) ;
else if ( c == || c ==?\n? || c ==?\t? )
printf(“The character is a white space.”) ;
else
printf (,The character is an other character.” ) ;
}
c) 判别输入的字符是数字、白空字符还是其它字符的程序例
36
main ( )
{
int x=0,y=1,z=2,w ;
if ( x++ )
w = x ;
else if ( x++&&y>=1 )
w = y ;
else if ( x++&&z>1 )
w = z ;
printf (,x=%d\ny=%d\nz=%d\nw=%d\n”,x,y,z,w ) ;
}
程序例:
程序的输出是,x = 2
y = 1
z = 2
w = 1
37
该例比较特别,阅读时要注意两个方面:
其一:注意序列点处理规则 。 因 ||是序列点所以忽略 ||
和 &&运算符的优先规则,先算 ||左边的 x++,并保证 x增 1处理完成;
其二,注意逻辑运算符处理的优化规则。
(1) 在计算 e1 && e2时,若 e1的值为假,则不必再计算 e2 的值,因为不管 e2 是什么结果,整个表达式的值都是 0 ;只有当 e1值为真时,才需要计算 e2的值,此时整个表达式的值由
e2的值决定。
(2) 在计算 e1||e2 时,若 e1值为真,则不需要计算 e2的值,
整个表达式的值为 1,只有当 e1值为假时,才计算 e2的值,
整个表达式的值由 e2的值决定 。
38
d ) if 语句的嵌套
if 结构中的,语句,可以是任何C语言语句,因此它们可以是一个 if语句,这就是说 if语句是可以 嵌套使用的。同理,内嵌的 if语句又可以嵌套另一个 if语句,依此类推 。
39
一般而言,如果嵌套的 if语句都带 else子句,那么 if的个数与 else的个数总相等,加之良好书写习惯,则嵌套中出现混乱与错误的机会就会少一些。但在实际程序设计中常需要使用带 else子句和不带 else子句的 if语句的混合嵌套。
在这种情况下,嵌套中就会出现 if与 else 个数不等的情况,
很易于出现混乱的现象。例如:
if ( 表达式 1 )
if ( 表达式 2 )
语句
else
语句
40
从形式上看,编程者似乎希望程序中的 else子句属于第一个 if,但编译程序并不这样认为,仍然把它与第二个 if 相联系。 对于这类情况,C语言明确规定,if 嵌套结构中的
else总是属于在它上面的、最近的、又无 else子句的那个 if 语句。 尽管有这类规定,建议还是应尽量避免使用这类嵌套为好。如果必须这样做,应使用复合语句的形式明显指出 else的配对关系。如可以这样来处理: