第5章选择结构程序设计 微课视频  学习意义 选择控制结构是结构化程序设计所采用的三种基本控制结构之一,另外两种是顺序控制和循环控制。有人曾经证明: 任何程序都可用顺序、选择、循环三种控制结构来实现,而结构化程序设计的研究成果表明: 只用这三种控制结构编写的程序易于保证正确性。在编制程序时,有时并不能保证程序一定执行某些指令,而是要根据一定的外部条件来判断哪些指令要执行。如菜谱中要加工番茄,可能有这样的步骤: 如果是用番茄,则去皮、切碎,开始放入,如果是用番茄酱,就在最后放入。这里并不知道具体操作时执行哪段指令,但菜谱给出了不同条件下的处理方式,计算机程序也是如此,可以根据不同的条件执行不同的代码,这就是选择结构。程序总是为解决某个实际问题而设计的,而问题往往包含多个方面,不同的情况需要有不同的处理方法,所以选择结构在实际应用程序中可以说是无处不在,离开了选择结构很多情况将无法处理,因此,正确掌握选择结构程序设计方法对于编写实际应用程序尤为重要。本章将首先介绍与选择结构有关的关系运算符、逻辑运算符和条件运算符的使用方法,然后重点介绍C语言中的两个选择控制语句if和switch,最后通过几个实例程序帮助读者更好地掌握选择结构程序设计的方法。  学习目标 (1) 理解选择结构的含义; (2) 掌握C语言语句的分类; (3) 掌握关系运算符、逻辑运算符和条件运算符的用法; (4) 掌握if、switch语句的使用方法。  难点提示 (1) 由关系运算符、逻辑运算符组成复杂的条件表达式; (2) switch语句实现选择结构程序设计。 微课视频 5.1C语言程序中语句的分类 C语言程序的执行部分是由语句组成的。程序的功能也是由执行语句实现的。C语言中的语句可以分为以下五类。 1. 表达式语句 表达式语句由表达式加上分号“;”组成。其一般形式为: 表达式; 例如,a=10 是赋值表达式,而 a=10; 则是赋值语句; k++是表达式,k++; 则是表达式语句。 2. 函数调用语句 由函数名、实际参数加上分号“;”组成。其一般形式为: 函数名(实际参数表); 执行函数语句就是调用函数体并把实际参数赋予函数定义中的形式参数,然后执行被调函数体中的语句,求取函数值(在第8章中再详细介绍)。 例如,printf("C Program") 是函数调用; printf("C Program"); 是函数调用语句,其功能是输出字符串"C Program"。 3. 复合语句 把多条语句用花括号{}括起来组成的一条语句称为复合语句。在程序中应把复合语句看成单条语句,而不是多条语句。例如: { c=a+b; z=x+y; printf("c=%d, z=%d", c, z); } 是一条复合语句。复合语句内的各条语句都必须以分号“;”结尾,在括号“}”外不要加分号。 复合语句又称分程序,在复合语句内可以定义变量,但定义的变量只能在复合语句内使用。例如,下面程序中的阴影部分是复合语句。在该复合语句中,定义了一个变量z。 #include int main( ) { int x=10, y=20, z; z=x+y; { int z; z=x*y; printf("z=%d\n", z);//输出复合语句中z的值 } printf("z=%d\n", z);//输出复合语句外z的值 return 0; } 输出的结果将为: z=200 z=30 为什么会输出这样的结果?通过后面第8章的学习读者将会明白。复合语句主要使用在选择和循环控制结构中。 4. 空语句 只有分号“;”组成的语句称为空语句。空语句是什么也不执行的语句。在程序中空语句可用来作空循环体。例如,while(getchar( )!='\n');的功能是,只要从键盘输入的字符不是回车则重新输入。这里的循环体为空语句。 5. 控制语句 用来实现一定的控制功能的语句称为控制语句。控制语句用于控制程序的流程,以实现程序的各种结构方式。它们由特定的语句定义符组成。C语言用控制语句来实现选择结构和循环结构。C语言有九种控制语句,可分成以下三类。 (1) 条件判断语句——if语句、switch语句。 (2) 循环执行语句——do while语句、while语句、for语句。 (3) 转向语句——break语句、goto语句、continue语句、return语句。 5.2关系运算符、逻辑运算符、条件运算符 在学习各种控制语句之前,必须学会关系运算符、逻辑运算符和条件运算符的用法。它们是选择结构和循环结构程序设计的基础。 微课视频 ■ 5.2.1关系运算符和关系表达式 在程序中经常需要比较两个量的大小关系,以决定程序下一步的工作。比较两个量的运算符称为关系运算符。C语言提供了6种关系运算符,它们的含义及优先级关系见表51。 表51关系运算符 关系运算符 含义 优先级 结 合 性 > 大于 >=(>和=之间没有空格) 大于或等于 < 小于 <=(<和=之间没有空格) 小于或等于 ==(两个=之间没有空格) 等于 !=(!和=之间没有空格) 不等于 这些关系运算符等优先级,但比下面的优先级高 这些关系运算符等优先级,但比上面的优先级低 左结合性 用关系运算符连接起来的式子称为关系表达式。其一般形式为: 表达式关系运算符表达式 例如,a+b>c-d,x>3/2,'a'+1(b>c),a!=(c==d)等。关系表达式的值是“真”和“假”,分别用1和0表示。例如,5>0的值为“真”,即为1; (a=3)>(b=5)由于3>5不成立,故其值为假,即为0。 注意:  C语言用0表示假,非0表示真。  一个关系表达式的值不是0就是1,0表示假,1表示真。 图51关系运算符的优先级 关系运算符都是双目运算符,其优先级高于与、或以及赋值运算符,但低于算术运算符和移位运算符(如图51所示)。 例如: c > a+b含义是c >(a+b) a > b !=c含义是(a > b) !=c a==b < c含义是a==(b < c) a=b > c含义是a=(b > c) a >> 2 < c+d含义是(a >> 2) <(c+d) a & 4 > b | c含义是(a &(4 > b)) | c 关系运算符具有左结合性,相同优先级的关系运算符连用时,按照从左向右的顺序计算表达式的值。例如,如果有: a=1; b=2; c=3; d=a !=c==a < b < c; 则d的值是1。因为=的优先级最低,所以d=a!=c==a10 || x<-10,!x && !y都是合法的逻辑表达式。由于表达式又可以是逻辑表达式,因此也允许出现嵌套的情况,例如,x>y && x>z && x<0等。逻辑表达式的值也是“真”或“假”,分别用1和0表示。 “||”运算符两边的式子只要有一个式子为真,整个逻辑表达式的值就是真(即为1),否则整个逻辑表达式的值就是假(即为0)。 “&&” 运算符两边的式子只有都是真时,整个逻辑表达式的值才是真(即为1),否则整个逻辑表达式的值就是假(即为0)。 “!”运算符是单目运算符,当其右边的式子是真时,整个逻辑表达式的值是假(即为0),否则整个逻辑表达式的值就是真(即为1)。 表53列出了逻辑运算符的运算规则。 表53逻辑运算符的运算规则 A B !A !B A && B A || B 假 假 1 1 0 0 假 真 1 0 0 1 真 假 0 1 0 1 真 真 0 0 1 1 逻辑运算符中的!是单目运算符,因此其优先级较高,它与++、--和sizeof同优先级,比算术运算符的优先级要高。另两个逻辑运算符是双目运算符,优先级较低,比关系运算符和位运算符都要低,但比赋值运算符的优先级高(如图52所示)。 图52逻辑运算符的优先级 例如: a <=x && x <=b等价于(a <=x)&&(x <=b) a > b && x > y等价于(a > b)&&(x > y) a==b || x==y等价于(a==b)||(x==y) !a || a > b等价于(!a) || (a > b) !a > b等价于(!a) > b c=a || b等价于c=(a || b) a | 7 && b & 8等价于(a | 7)&&(b & 8) a >> 2 && b << 1等价于(a >> 2)&&(b << 1) 三个逻辑运算符中,!的优先级最高,&&的优先级高于||的优先级。!具有右结合性,而&&和||具有左结合性。例如,如果有: a=4; b=5; c=b> 3 && 2 || 8 < b - !a; 则c的值是1。因为>、<、-、&&、||、!的优先级高于=,所以c=b>3&&2||83&&2||8、<、-、!,所以又等价于c=((b>3) &&2||(83)&&2)||(83)&&2)||(8<(b-(!a))));。所以先计算(b>3) && 2的值,因为b=5,所以b>3的值为1,1 && 2的值为1,而对于8<(b-(!a))来说,因为!a为0,b-0的值为5,8<5的值为0,所以其值为0,最后1||0的值就为1,再赋给c,所以c的值为1。 有一点需要说明,在逻辑表达式求值的过程中,并不是所有的逻辑运算符都被执行,只有在必要的情况下才会执行。例如:  在求解a && b && c的值时,只在a为真时,才判别b的值; 只在a、b都为真时,才判别c的值。如果a为假,则不会判别b和c,因为整个表达式的值已经确定了。  在求解a || b || c的值时,只在a为假时,才判别b的值; 只在a、b都为假时,才判别c的值,如果a为真,就不会判别b和c,因为整个表达式的值已经确定了。 例如: a=1; b=2; c=3; d=4; m=1; n=1; 则(m=a>b)&&(n=c>d)的结果将使得m的值为0,但n的值仍为1。因为先计算m=a>b,则m的值为0(因a3 ? 6 : 20的值是6,5<3 ? 6 : 20的值是20。 条件表达式通常用于赋值语句之中。例如,将变量a、b中的最大值赋给变量max可以写成如下语句。 max=a > b ? a : b; 使用条件表达式时,还应注意以下几点:  条件运算符的运算优先级特别低,仅高于赋值运算符和逗号运算符,而比其他运算符都低。因此 max=(a>b) ? a : b;可以去掉括号而写为: max=a > b ? a : b;  条件运算符可嵌套。例如,x>0 ? 1 : (x<0 ? -1 : 0)。其表达式的值是: 如果x是正数,则为1,如果是负数,则为-1,如果为零,则为0。  条件运算符的结合方向是自右至左。如 a>b ? a : c>d ? c : d 等价于 a>b ? a: (c>d ? c : d)。  条件运算符?和: 是一对运算符,不能分开单独使用。  表达式1、表达式2、表达式3的类型可以不相同,表达式值取较高的类型。例如,x ? 'a' : 'b'的含义是x为0,表达式值为'b'; x不为0,表达式值为'a'。x>y ? 1 : 1.5的含义是 x大于y,表达式值为1.0; x小于或等于y,表达式值为1.5。 下面例51中的程序是利用条件运算符对第4章中例413小写字母转盘程序的改写。 【例51】小写字母转盘。 1#include 2#include 3 4int main( ) 5{ 6char ch, ch1, ch2;//变量定义 7 8ch=getche( );//读取一字符 9putchar('\n');//换行 10ch1=ch=='a' ? 'z' : ch-1;//求前驱字母 11ch2=ch=='z' ? 'a' : ch+1;//求后继字母 12printf("ch1=%c, ch2=%c\n", ch1, ch2);//显示结果 13return 0; 14} 运行结果(假设输入字母为w): ch1=v, ch2=x 到现在为止,已经学习了三十多个运算符了。掌握它们的优先级关系特别重要。机械地记忆这三十多个运算符的优先级关系是痛苦的。下面给出的优先级的记忆规则将会使这件事变得轻松许多。 图54各类运算符的优先级关系 (1) 总体上讲,单目运算符都是同等优先级的,具有右结合性,并且优先级比双目运算符和三目运算符都高。 (2) 三目运算符的优先级比双目运算符要低,但高于赋值运算符和逗号运算符。 (3) 逗号运算符的优先级最低,其次是赋值运算符。 (4) 只有单目运算符、赋值运算符和条件运算符具有右结合性,其他运算符都是左结合性。 (5) 双目运算符中,算术运算符的优先级最高,逻辑运算符的优先级最低。 图54给出了各类运算符之间的优先关系,详细内容见附录D。 运算符优先级的口诀是: “单算移关,位逻条赋,逗!”。 5.3选择结构的程序设计 在程序的三种基本结构中,第二种为选择结构,其基本特点是: 程序的流程由多路分支组成,在程序的一次执行过程中,根据不同的情况,只有一条分支被选中执行,而其他分支上的语句则被直接跳过。 C语言中提供了if语句和switch语句来实现选择结构,if语句用于两者选一的情况,而switch语句用于多分支选一的情形。 微课视频 ■ 5.3.1if语句 用if语句可以构成选择结构。它根据给定的条件进行判断,以决定执行某个分支程序段。C语言的if语句有三种基本形式。 1. 简单if语句形式 语句格式为: if(表达式) 语句; 图55简单if语句流程图 其语句功能是如果表达式的值为真,则执行其后的语句,否则不执行该语句,而执行if语句后面的语句。其对应的流程图如图55所示。 例如,下面的程序段是输入两个整数,输出其中的大数。 int a, b, max; printf("input two numbers: "); scanf("%d%d", &a, &b); max=a; if(max < b) max=b; printf("max=%d", max); 本例程序中,输入两个数a和b。把a先赋给变量max,再用if语句判别max和b的大小,如果max小于b,则把b赋给max。因此max中总是大数,最后输出max的值。 2. ifelse形式 语句格式为: if(表达式) 语句1; else 语句2; 图56ifelse语句流程图 其语句功能是如果表达式的值为真,则执行语句1,否则执行语句2,其对应的流程图如图56所示。 例如,下面的程序段同样是输出两个整数中的最大数。 int a, b; printf("input two numbers: "); scanf("%d%d", &a, &b); if(a > b) printf("max=%d\n", a); else printf("max=%d\n", b); 本程序中,改用ifelse语句判别a和b的大小,若a大,则输出a,否则输出b。 3. ifelseif形式 语句格式为: if(表达式1) 语句1; else if(表达式2) 语句2; else if(表达式3) 语句3;  [else 语句n;] 其语句功能是依次判断表达式的值,当出现某个值为真时,则执行其对应的语句。然后跳到整个if语句之外继续执行程序。如果所有的表达式均为假,则执行语句n。然后继续执行后续程序,其对应的流程图如图57所示。 图57ifelseif语句流程图 例如,下面的程序段是判断输入字符的种类。 char c; printf("Enter a character: "); c=getchar( ); if(c < 0x20)printf("The character is a control character\n"); else if(c >='0' && c <='9')printf("The character is a digit\n"); else if(c >='A' && c <='Z')printf("The character is a capital letter\n"); else if(c >='a' && c <='z')printf("The character is a lower letter\n"); else printf("The character is other character\n"); 本程序中,根据输入字符的ASCII码来判别字符类型。由ASCII码表可知ASCII值小于32(十六进制0x20)的为控制字符。在'0'和'9'之间的为数字,在'A'和'Z'之间为大写字母,在'a'和'z'之间为小写字母,其余则为其他字符。这是一个多分支选择的问题,用ifelseif语句编程,判断输入字符ASCII码所在的范围,分别给出不同的输出。例如,输入为'g',输出显示它为小写字符。 在使用if语句时还应注意以下几点: (1) 在if语句中,条件判断表达式必须用括号括起来。 (2) 在三种形式的if语句中,在if关键字之后均为表达式。该表达式通常是逻辑表达式或关系表达式,但也可以是其他任何表达式,如赋值表达式等,甚至也可以是一个变量。只要表达式非零时,表达式的值就为真,否则就是假。例如,下面的if语句都是合法的。 if(a=5) 语句;//表达式的值永远为非0,所以其后的语句总是要执行的 if(b)语句;//等价于if(b !=0) 语句 (3) 在if语句的三种形式中,所有的语句应为单个语句,如果要想在满足条件时执行一组(多个)语句,则必须把这一组语句用{ }括起来组成一个复合语句。但要注意的是在}之后不能再加分号。例如,下面的程序段左边是正确的,而右边则是错误的,因为在if与else之间有两条语句,必须用{ }括起来组成一个复合语句。 正确的if语句 错误的if语句 if(a > b) if(a > b) { a++; a++; b++; b++; else } { else a=0; { b=1; a=0; } b=1; } 注意: 在if和else之间如果只有一条语句,则可不用{ }括起来,但如果多于一条语句则必须用{ }括起来,否则会产生编译错误。 (4) 在简单的if语句中(即只有if,没有else),如果在满足条件时执行的是多条语句, 原则上应用{ }括起来,但如果没有用{ }括起来,尽管不会产生编译错,但程序的逻辑将出现异常。例如,下面程序的目的是当x小于y时,交换x和y的值。左边的程序是正确的,而右边的程序表现异常,原因就是if语句的后面漏掉了{ }。 正确的程序 逻辑异常的程序 #include #include int main( ) int main( ) { { int x=4, y=2, t=0; int x=4, y=2, t=0; if(x < y) if(x < y) { t=x; t=x; x=y; x=y; y=t; y=t; } printf("x=%d, y=%d\n", x, y); return 0; printf("x=%d, y=%d\n", x, y); } return 0; } 运行结果: x=4, y=2 运行结果: x=2, y=0 (5) 在if语句中,如果表达式是一个判断两个数是否相等的关系表达式,要当心不要将 ==写成了赋值运算符=。下面程序的意图是当x等于0时,输出x=0,否则输出x!=0。左边的程序是正确的,右边的程序则是错误的,因为将x==0误写成了x=0。程序中x的值是0,因此x==0的值是1,而x=0的值是0。 结果正确的程序 结果错误的程序 #include #include int main( ) int main( ) { { int x=0; int x=0; if(x==0) if(x=0) printf("x=0\n"); printf("x=0\n"); else else printf("x !=0\n"); return 0; printf("x !=0\n"); return 0; } } 运行结果: x=0 运行结果: x !=0 (6) if语句允许嵌套,即if语句中的执行语句又是if语句。其一般形式可表示为: if(表达式) if语句;或 if(表达式) if语句; else if语句; 在嵌套内的if语句可能又是ifelse型的,这将会出现多个if和多个else重叠的情况,这时要特别注意if和else的配对问题。例如: if(表达式1) if(表达式2) 语句1; else 语句2; 图58ifelse配对原则 其中的else究竟是与哪一个if配对呢?C语言规定,在省略{ }时,else总是和它上面离它最近的未配对的if配对(如图58所示)。因此,else与第二个if配对,上述例子理解的形式应为: if(表达式1) if(表达式2) 语句1; else 语句2; 如果希望else与第一个if配对,那么必须在else前加上{ },即写成如下形式。 if(表达式1) { if(表达式2) 语句1; } else 语句2; 试比较下面程序一和程序二之间的差异。 程序一 程序二 #include #include int main( ) int main( ) { { int a=1, b=-1; int a=1, b=-1; if(a > 0) if(a > 0) if(b > 0) { a++; if(b > 0) else a++; a--; } printf("a=%d\n"); return 0; else a--; } printf("a=%d\n"); return 0; } 运行结果: a=0 运行结果: a=1 微课视频 ■ 5.3.2switch语句 C语言还提供了另一种用于多分支选择的switch语句,实际上,switch语句是ifelse语句的变形。在某些情况下,使用switch语句要比if语句更为简洁、易读。其一般格式为: switch(表达式) { case 常量表达式C1: 语句组1; break; case 常量表达式C2: 语句组2; break;  case 常量表达式Cn: 语句组n; break; [default:语句组; break;] } 其执行过程为:  当switch后面“表达式”的值与某个case后面的“常量表达式”的值相同时,就执行case后面的语句(组); 当执行到break语句时,跳出switch语句,转向执行switch语句的下一条语句。  如果没有任何一个case后面的“常量表达式”的值与“表达式”的值匹配,则执行default后面的语句(组),然后再执行switch语句的下一条语句。 switch语句执行的流程图如图59所示。 图59switch语句流程图 在使用switch语句时还应注意以下几点: (1) switch后面的“表达式”,必须是一个整型表达式,而且每个case后的“常量表达式”的类型应该与switch后面的“表达式”的类型一致。例如,下面的程序是错误的。 floata, b=4.0; scanf("%f", &a); switch(a)//错误,不可为浮点型表达式 { case 1:b=b+1;break; case 2:b=b-1;break; } printf("b=%f\n",b); (2) case后面语句(组)可加{ }也可以不加{ },但一般不加{ }。例如: switch(i) { case 1:{ b=b+1;break; }// { }可加可不加 case 2:b=b-1;break; } (3) 每个case后面“常量表达式”的值必须各不相同,否则会出现相互矛盾的现象(即对表达式的同一值,不允许有两种或两种以上的执行方案)。例如,下面的程序是错误的。 inta, b=4; scanf("%d", &a); switch(a) { case 1:b=b+2;break; case 2:b=b*2;break; case 1:b=b+2;break; //错误,case 1在前面已使用,可改为case 3 } printf("b=%d\n", b); (4) 每个case后面必须是“常量表达式”,表达式中不能包含变量。初学者使用switch 语句时很容易犯此类错误,所以这一点务必要注意。例如,下面程序的功能是欲判断用户输入的字符类别,但程序是错误的,因为case后面的表达式不是常量表达式。 char c; printf("Enter a character: "); c=getchar( ); switch(c) { case c < 0x20 ://错误,case后面跟着变量 printf("The character is a control character\n"); break; case c >='0' && c <='9' : printf("The character is a digit\n"); break; case c >='A' && c <='Z' : printf("The character is a capital letter\n"); break; case c >='a' && c <='z' : printf("The character is a lower letter\n"); break; default : printf("The character is other character\n"); break; } 要实现该程序的功能,应使用ifelseif形式的分支结构,其程序参见5.3.1节。当然,如果非要使用switch语句来实现,则只能将每种字符列举出来,这种方法显然是非常烦琐的,所以if语句的功能并不是简单地用switch来代替就行,有时switch语句根本就无法实现if语句的功能。 (5) case后面的“常量表达式”仅起语句标号作用,并不进行条件判断。系统一旦找到入口标号,就从此标号开始执行,不再进行标号判断,所以必须加上break语句,以便结束switch语句。比较下面程序一、程序二的运行结果。 程序一 程序二 #include #include int main( ) int main( ) { { char ch; char ch; ch=getch( ); ch=getch( ); switch(ch) switch(ch) { { 程序一 程序二 case'Y' :printf("Yes\n");break; case'Y' :printf("Yes\n");break; →case'N' :printf("No\n");break;→ →case'N' :printf("No\n"); case'A' :printf("All\n");break; case'A' :printf("All\n");break;→ default :printf("Yes,No or All\n"); default :printf("Yes,No or All\n"); } return 0; } return 0; } } 运行结果(假设输入N): No 运行结果(假设输入N): No All 对于程序二来说,因为ch的值是'N',因此从case 'N'处开始执行,首先执行printf函数调用,输出No,因为后面没有break,所以接着执行下一行语句,即case 'A'后面的printf函数调用,输出All,当遇到break时,就跳出switch语句。 (6) 多个case子句,可共用同一语句(组)。例如,下面的程序中,当a的值是1、2、3时,将b的值加2,当a的值是4、5、6时,将b的值减2,否则将b的值乘以2。 inta, b=4; scanf("%d", &a); switch(a) { case 1: case 2: case 3:b+=2;break; case 4: case 5: case 6:b -=2;break; default:b *=2;break; } printf("b=%d\n", b); (7) case子句和default子句如果都带有break子句,那么它们之间顺序的变化不会影响 switch语句的功能。下面的两个程序是等价的。 程序一 程序二 #include #include int main( ) int main( ) { { charch; charch; ch=getch( ); ch=getch( ); switch(ch) switch(ch ) { { 程序一 程序二 case'Y' :printf("Yes\n");break; case'Y' :printf("Yes\n");break; case'N' :printf("No\n");break; default :printf("Yes,No or All\n"); case'A' :printf("All\n");break; break; default :printf("Yes,No or All\n"); case'N' :printf("No\n");break; break; case'A' :printf("All\n");break; } return 0; } return 0; } } (8) case子句和default子句如果有的带break子句,而有的没有带break子句,那么它们之间顺序的变化可能会影响输出的结果。比较下面程序一、程序二的运行结果。 程序一 程序二 #include #include int main( ) int main( ) { { charch; charch; ch=getch( ); ch=getch( ); switch(ch) switch(ch) { { case'Y' :printf("Yes\n");break; case'Y' :printf("Yes\n");break; case'N' :printf("No\n");break; default :printf("Yes,No or All\n"); case'A' :printf("All\n");break; case'N' :printf("No\n");break; default :printf("Yes,No or All\n"); case'A' :printf("All\n");break; } return 0; } return 0; } } 运行结果(假设输入B): Yes,No or All 运行结果(假设输入B): Yes,No or All No (9) switch语句可以嵌套。例如,下面程序的switch语句中又嵌套了一个switch语句。 int main( ) { int x=1, y=0, a=0, b=0; switch(x) { case1:switch( y ) { case 0:a++;break; case 1:b++;break; } case2: a++;b++; break; case3: a++;b++; } printf("\na=%d, b=%d", a, b); return 0; } 程序运行的结果为: a=2,b=1 5.4选择结构程序设计举例 微课视频 【例52】已知某公司员工的保底薪水为500,某月所接工程的利润profit(整数)与利润提成的关系如表54所示(计量单位: 元)。计算员工的当月薪水。 表54工程利润与利润提成的关系 工程利润profit 提 成 比 率 profit≤1000 没有提成 1000<profit≤2000 提成10% 2000<profit≤5000 提成15% 5000<profit≤10000 提成20% 10000<profit 提成25% 程序应该这样来设计: (1) 首先要定义一个变量profit用来存放员工所接工程的利润。 (2) 其次提示用户输入员工所接工程的利润,并调用scanf函数接受用户输入员工所接工程的利润。 (3) 然后根据表54的规则,计算该员工当月的提成比率ratio。 (4) 最后计算该员工当月的薪水salary(保底薪水+所接工程的利润×提成比率),并输出结果。 画出NS流程图表示算法如图510所示。 图510计算当月薪水NS流程图 具体程序如下: 1#include 2int main( ) 3{ 4longprofit;//所接工程的利润 5floatratio; //提成比率 6floatsalary=500; //薪水,初始值为保底薪水500 7 8printf("Input profit: ");//提示输入所接工程的利润 9scanf("%ld", &profit);//输入所接工程的利润 10 11//计算提成比率 12if(profit <=1000) 13ratio=0; 14else if(profit <=2000) 15ratio=(float)0.10; 16else if(profit <=5000) 17ratio=(float)0.15; 18else if(profit <=10000) 19ratio=(float)0.20; 20elseratio=(float)0.25; 21 22salary+=profit*ratio; //计算当月薪水 23printf("salary=%.2f\n", salary);//输出结果 24return 0; 25} 运行结果(假设输入4000): Input profit: 4000↙ salary=1100.00 程序解释: 如果在调用scanf函数时,用户输入4000,则profit的值是4000,因此第12行的profit<=1000的值为假,不会执行第13行的语句,然后程序试图执行第14行语句,但profit <=2000的值也为假,因此不会执行第15行的语句,接着程序试图执行第16行语句,profit <=5000的值为真,因此会执行第17行的语句,提成比率ratio为0.15,并跳出if语句。最后,根据“保底薪水+所接工程的利润×提成比率”计算当月的薪水salary=500+4000×0.15=1100元,并输出结果。 为了更好地理解if语句的用法,可以将例52改写成下面这样。但读者要仔细比较一下它们的不同之处。 1#include 2int main( ) 3{ 4longprofit;//所接工程的利润 5floatratio; //提成比率 6floatsalary=500; //薪水,初始值为保底薪水500 7 8printf("Input profit: ");//提示输入所接工程的利润 9scanf("%ld", &profit);//输入所接工程的利润 10 11//计算提成比率 12if(profit <=1000) 13ratio=0; 14if(1000 < profit && profit <=2000) 15ratio=(float)0.10; 16if(2000 < profit && profit <=5000) 17ratio=(float)0.15; 18if(5000 < profit && profit <=10000) 19ratio=(float)0.20; 20if(10000 < profit) 21ratio=(float)0.25; 22 23salary+=profit*ratio; //计算当月薪水 24printf("salary=%.2f\n", salary);//输出结果 25return 0; 26} 微课视频 【例53】用switch语句来实现例52。 算法设计要点: 为使用switch语句,必须将利润profit与提成的关系转换成某些整数与提成的关系。分析本题可知,提成的变化点都是1000的整数倍(1000,2000,5000,…),如果将利润profit整除1000,则当: profit≤1000对应0,1 1000<profit≤2000对应1,2 2000<profit≤5000对应2,3,4,5 5000<profit≤10000对应5,6,7,8,9,10 10000<profit对应10,11,12,… 为解决相邻两个区间的重叠问题,最简单的方法就是: 利润profit先减1(最小增量),然后再整除1000即可。 profit≤1000对应0 1000<profit≤2000对应1 2000<profit≤5000对应2,3,4 5000<profit≤10000对应5,6,7,8,9 10000<profit对应10,11,12,… 具体程序如下: 1#include 2 3int main( ) 4{ 5longprofit; //所接工程的利润 6intgrade; 7floatratio; //提成比率 8floatsalary=500; //薪水,初始值为保底薪水500 9 10printf("Input profit: ");//提示输入所接工程的利润 11scanf("%ld", &profit);//输入所接工程的利润 12 13//将利润-1、再整除1000,转换成switch语句中的case标号 14grade=(profit-1)/1000; 15switch(grade)//计算提成比率 16{ 17case0:ratio=0;break; // profit≤1000 18case1:ratio=(float)0.10;break; // 1000<profit≤2000 19case2: 20case3: 21case4:ratio=(float)0.15;break; // 2000<profit≤5000 22case5: 23case6: 24case7: 25case8: 26case9:ratio=(float)0.20;break; // 5000<profit≤10000 27default:ratio=(float)0.25; // 10000<profit 28} 29salary+=profit*ratio; //计算当月薪水 30printf("salary=%.2f\n", salary);//输出结果 31return 0; 32} 微课视频 【例54】写一段程序,从键盘上输入年份year(4位十进制数),判断其是否闰年。闰年的条件是: 能被4整除,但不能被100整除,或者能被400整除。 算法设计要点: (1) 如果X能被Y整除,则余数为0,即如果X%Y的值等于0,则表示X能被Y整除。 (2) 首先画出判别闰年算法的NS流程图,如图511所示。将是否闰年的标志leap预置为0(非闰年),这样仅当year为闰年时,将leap置为1即可。最后判断leap是否为1,若是,则输出“闰年”信息。这种处理两种状态值的方法,对优化算法和提高程序可读性非常有效,请读者仔细体会。 图511判别闰年算法NS流程图 具体程序如下: 1#include 2 3int main( ) 4{ 5int year, leap=0;// leap=0: 预置为非闰年 6 7printf("Please input the year: ");//提示输入年份 8scanf("%d", &year);//输入年份 9 10if(year % 4==0)//如果被4整除 11if(year % 100 !=0)//如果不被100整除 12leap=1; //置为闰年 13if(year % 400==0)//如果被400整除 14leap=1; //置为闰年 15 16//输出结果 17if(leap)//如果是闰年(leap==1) 18printf("%d is a leap year.\n", year); 19else 20printf("%d is not a leap year.\n", year); 21return 0; 22} 运行结果(假设输入2020): Please input the year: 2020↙ 2020 is a leap year. 程序解释:  程序的第7、8行用于输入年份。如输入值为2020,则year的值为2020。  第10~14行用于判别输入的年份year是否为闰年。如果year为2020,则能被4整除,即对于第10行的if语句来说,因为year%4==0为真,所以执行第11行,因为2020不被100整除,即year%100!=0为真,所以执行第12行的语句,则leap置为1(闰年标志)。又因2020不被400整除,即year%400!=0,所以第14行不执行。  第17行中因为leap为1,所以执行第18行的语句,输出其为闰年。  利用逻辑运算能描述复杂条件的特点,可将上述程序中的第10~14行优化为: if(( year % 4==0 && year % 100 !=0 ) || ( year % 400==0 ) ) leap=1; 或直接写为: leep=( year % 4==0 && year % 100 !=0 ) || ( year % 400==0 ); 微课视频 【例55】编写一程序,从键盘上输入任意两个数和一个运算符(+,-,*,/),计算其运算的结果并输出。 算法设计要点: 首先输入两个数和一个运算符号,然后根据运算符号来进行相应的运算,但是在做除法运算时,应先判别除数是否为0,如果为0,运算非法,给出提示信息。如果运算符号不是+、-、*、/,则同样是非法的,也应给出提示信息。其他情况,输出运算的结果。 具体程序如下: 1#include 2 3int main( ) 4{ 5float a, b;//存放两个数的变量 6int tag=0; //运算合法的标志,0合法,1非法 7char ch;//运算符变量 8float result; //运算结果变量 9 10printf("input two number: ");//提示输入两个数 11scanf("%f%f", &a, &b);//输入两个数 12fflush(stdin);//清键盘缓冲区 13printf("input arithmetic label(+ -*/): ");//提示输入运算符 14scanf("%c", &ch);//输入运算符 15 16switch(ch)//根据运算符来进行相关的运算 17{ 18case'+':result=a+b;break; //加法运算 19case'-':result=a-b;break; //减法运算 20case'*':result=a*b;break; //乘法运算 21case'/':if(!b)//除法运算,判除数是否为0 22{ 23printf("divisor is zero!\n");//显示除数为0 24tag=1; //置运算非法标志 25} 26else//除数非0 27result=a/b; //计算商 28break; 29default:printf("illegal arithmetic label\n");//非法的运算符 30tag=1; //置运算非法标志 31} 32if(!tag)//运算合法,显示运算结果 33printf("%.2f %c %.2f=%.2f\n", a, ch, b, result); 34return 0; 35} 运行结果(假设输入的两个数为: 2030,运算符为:+): input two number: 20 30↙ input arithmetic label(+ -*/): +↙ 20.00+30.00=50.00 程序解释:  程序的第5行定义了两个浮点型变量a和b,用于存放要计算的两个数。  第6行定义一变量tag,其值用于运算合法的标志,0: 合法,1: 非法。  第7行定义一变量ch用于存放运算符。  第8行定义一变量result用于存放运算结果。  第10~14行输入计算的数据及运算符。  第16~31行根据运算符的类型进行相应的运算。但在做除法运算时,首先要判断除数是否为0,如果为0,则置运算非法标志,即tag置1,否则做运算(见第21~28行)。如果运算符不是+、-、*、/,则置运算非法标志,即tag置1(见第29、30行)。  第32、33行表示如果运算合法,则显示相应的运算结果。 微课视频 5.5本章小结及常见错误列举 C语言程序的执行部分是由语句组成的。程序的功能也是由执行语句实现的。C语言中的语句可以分为表达式语句、函数调用语句、复合语句、空语句及控制语句五类。 关系表达式和逻辑表达式是两种重要的表达式,主要用于条件执行的判断和循环执行的判断。 C语言提供了多种形式的条件语句以构成选择结构。 (1) if语句主要用于单向选择。 (2) ifelse语句主要用于双向选择。 (3) ifelseif语句和switch语句主要用于多向选择。 任何一种选择结构都可以用if语句来实现,但并非所有的if语句都有等价的switch语句。switch语句只能用来实现以相等关系作为选择条件的选择结构。 本章主要讨论了选择结构程序设计的有关方法,重点介绍了if语句和switch语句,在学习这两种控制语句的过程中,又接触到了一些新的C语言关键字,它们是: if else switch case default break 下面列举了一些在选择结构程序设计中常见的错误。 (1) 忘记必要的逻辑运算符。 例如: if(a > b > c)  本意为如果a>b并且b>c,由于在数学中使用a>b>c的形式,也就把它照搬到计算机程序中。而在C语言程序中,表达式a>b>c的求值是先求a>b,得到一个逻辑值0或1,再拿这个数与c进行比较,结果当然是不对的。对于这种情况,应该使用逻辑表达式,写成: if(a > b && b > c)  (2) 在关系表达式中误用=来表示==。 C语言用两个连续的赋值运算符来表示“相等”关系运算符。如果将=当作==使用,通常不会有语法错,但却隐含着不易发现的逻辑错误。 (3) 应该用复合语句时忘记写花括号{ }。 例如: if(a > b) temp=a; a=b; b=temp; 由于没有写花括号,if的影响只限于temp=a;一条语句,而不管(a>b)是否为真,都将执行后两个赋值语句。正确的写法为: if(a > b) { temp=a; a=b; b=temp; } (4) 在不该加分号的地方加分号。 例如: if(a==b); c=a+b; 本意是如果a等于b则执行c=a+b,但由于if(a+b)后面跟有分号,c=a+b;在任何情况下都要执行。因为if后加分号相当于后跟一个空语句,这种错误是因为习惯在每行的末尾都加分号所致,正确的写法为: if(a==b) c=a+b; (5) 在else之前的语句丢失分号。 例如: if(a > b) max=a//漏了分号,正确的写法为: max=a; else max=b; (6) 将&&、||误输入为&、|。 在C语言中,&&是逻辑运算符,而&可以是位运算符,也可以是取地址运算符; ||是逻辑运算符,而|是位运算符。如果将&&、||误输入成&、|,也不会有语法错误,但隐含着逻辑错误。 (7) ==、!=、<=、>=运算符中间多了空格。 像==、!=、<=、>=这些运算符虽然由两个或两个以上的字符组成,但它们是整体,构成这些运算符的字符之间不能有空格。 (8) case子句后面的程序段中漏掉了break子句。 仔细比较下面两段程序,左边的程序正常,右边的程序表现异常,因为case子句的后面漏掉了break子句。 ch=getch( );ch=getch( ); switch(ch) switch(ch) { { case'Y' : printf("Yes\n"); case'Y' : printf("Yes\n"); break; //漏掉了break; case'N' : printf("No\n"); case'N' : printf("No\n"); break; break; } } (9) case后面跟着变量表达式。 switch语句中,case后面必须是常量表达式,不能是变量或变量表达式。下面的程序是错误的。 switch(a) { case a > 0 :printf("a > 0\n");break; case a < 0 :printf("a < 0\n");break; case a==0 :printf("a=0\n");break; } (10) switch后面的表达式为浮点类型。 C语言规定,switch后面的表达式必须是整型,不能是浮点类型。例如,下面的程序是错误的。 float f; int a=4; switch(f) { case 1 :a++;break; case 2 :a--;break; } (11) if或switch后面的表达式忘记了( )。 C语言规定,if或switch后面的表达式一定要加( ),否则会产生编译错。例如,下面的程序是错误的。 ifa > 10 //漏掉了( ),应改为: if(a > 10) b++; 习题5 1. 填空题 (1) C语言中的语句可以分为、、、、五类。 (2) C语言用表示假,表示真。 (3) C语言提供的三种逻辑运算符是、和。 (4) 关系运算符具有结合性,相同优先级的关系运算符连用时,按照的顺序计算表达式的值。 (5) 对于一个关系表达式的值,表示假,表示真。 (6) 对于C语言运算符的优先级,运算符优先级最高,运算符的优先级最低。 (7) C语言中用于选择结构的控制语句有语句和语句两种,前者用于的情况,而后者用于的情形。 (8) 当a=3, b=2, c=1时,表达式f=a>b>c的值是。 (9) 当m=2,n=1,a=1,b=2,c=3时,执行完d=(m=a!=b)&&(n=b>c)后; n的值为 ,m的值为。 (10) 条件“2B&&C>A ||AB的值是。 (15) 若a=1,b=4,c=3,则表达式!(ab)&&(n=c>d)后n的值是 ()。 A. 1B. 2C. 3D. 4 (4) 对if语句中表达式的类型,下面正确的描述是()。 A. 必须是关系表达式 B. 必须是关系表达式或逻辑表达式 C. 必须是关系表达式或算术表达式 D. 可以是任意表达式 (5) 多重ifelse语句嵌套使用时,寻找与else配对的if方法是()。 A. 缩排位置相同的ifB. 其上最近的if C. 下面最近的ifD. 其上最近的未配对的if (6) 以下错误的if语句是()。 A. if(x >y)z=x; B. if(x==y)z=0; C. if(x !=y)printf("%d", x)elseprintf("%d", y); D. if(x b)a=b, b=c;c=a; printf("a=%d, b=%d, c=%d", a, b, c); return 0; } A. a=20, b=30, c=20B. a=20, b=40, c=20 C. a=30, b=40, c=20D. a=30, b=40, c=30 (8) 对于条件表达式(k)?(i++) : (i--)来说,其中的表达式k等价于()。 A. k==0B. k==1C. k !=0D. k !=1 (9) 下面程序运行结果为()。 int main( ) { char c='a'; if('a' < c <='z')printf("LOW"); elseprintf("UP"); return 0; } A. LOWB. UPC. LOWUPD. 程序语法错误 (10) 对下述程序,正确的判断是()。 int main( ) { int a, b; scanf("%d, %d", &a, &b); if(a > b)a=b;b=a; elsea++;b++; printf("%d, %d", a, b); return 0; } A. 有语法错误不能通过编译B. 若输入4,5则输出5,6 C. 若输入5,4则输出4,5D. 若输入5,4则输出5,5 (11) 逻辑运算符两侧运算对象的数据类型()。 A. 只能是0或1B. 只能是0或非0正数 C. 只能是整型或字符型数据D. 可以是任何类型的数据 (12) 以下关于运算符优先顺序的描述中正确的是()。 A. 关系运算符<算术运算符<赋值运算符<逻辑运算符 B. 逻辑运算符<关系运算符<算术运算符<赋值运算符 C. 赋值运算符<逻辑运算符<关系运算符<算术运算符 D. 算术运算符<关系运算符<赋值运算符<逻辑运算符 (13) 下列运算符中优先级最高的是()。 A. =a) && (cl <=z) C. ('a' >=cl) || ('z' <=cl)D. (cl >='a') && (cl <='z') (16) 已知 int x=10, y=20, z=30; 以下语句执行后x, y, z的值是()。 if(x > y) z=x; x=y; y=z; A. x=10, y=20, z=30B. x=20, y=30, z=30 C. x=20, y=30, z=10D. x=20, y=30, z=20 (17) 请阅读以下程序: int main( ) { int a=5, b=0, c=0; if(a=b+c)printf("***\n"); elseprintf("$$$\n"); return 0; } 以上程序()。 A. 有语法错不能通过编译B. 可以通过编译但不能通过连接 C. 输出***D. 输出$$$ (18) 请阅读以下程序,其运行结果是()。 int main( ) { char c='A'; if('0' <=c <='9')printf("YES"); else printf("NO"); return 0; } A. YESB. NOC. YESNOD. 语句错误 (19) 当a=1, b=3, c=5, d=4时,执行完下面一段程序后x的值是()。 if(a < b) if(c < d)x=1; else if(a < c) if(b < d)x=2; elsex=3; elsex=6; elsex=7; A. 1B. 2C. 3D. 6 (20) 已知x=43, ch='A', y=0; 则表达式(x>=y&&ch<'B'&&!y)的值是()。 A. 0B. 语法错C. 1D. “假” (21) 有如下程序,正确的输出结果是()。 int main( ) { int a=15, b=21, m=0; switch(a % 3) { case 0: m++; break; case 1: m++; switch(b % 2) { default: m++; case 0: m++; break; } } printf("%d\n", m); return 0; } A. 1B. 2C. 3D. 4 (22) 阅读以下程序,如果从键盘上输入5,则正确的输出结果是()。 int main( ) { int x; scanf("%d", &x); if(x-- < 5)printf("%d", x); elseprintf("%d", x++); return 0; } A. 3B. 4C. 5D. 6 (23) 若a、b、c1、c2、x、y均是整型变量,正确的switch语句是()。 A. switch(a+b)B. switch(a*a+b*b) {{ case 1: y=a+b;break;case 3; case 0: y=a-b;break;case 1: y=a+b; break; case 3: y=b-a;break;} } C. switch aD. switch(a-b) {{ case c1: y=a-b;breakdefault: y=a*b;break case c2: x=a*d;breakcase 3: case 4: x=a+b;break default: x=a+b;case 10: case 11: y=a-b; break; }} (24) 与 y=(x>0?1:x<0?-1:0);的功能相同的if语句是()。 A. if(x>0)y=1;B. if(x) else if(x<0)y=-1;if(x>0)y=1; elsey=0;else if(x<0)y=-1; C. y=-1D. y=0; if(x)if(x >=0) if(x>0)y=1;if(x>0)y=1; else if(x==0)y=0;elsey=-1; elsey=-1; (25) 若有定义: float w;int a, b; 则合法的switch语句是()。 A. switch(w){B. switch(a) { case 1.0:printf("*\n");case 1: printf("*\n"); case 2.0:printf("**\n");case 2: printf("**\n"); }} C. switch(b) {D. switch(a+b); { case 1:printf("*\n");case 1: printf("*\n"); default:printf("\n");case 2: printf("**\n"); case a:printf("**\n");default: printf("\n"); }} (26) 执行以下程序段后的输出结果是()。 int w=3, z=7, x=10; printf("%d ", x>10 ? x=100 : x-10); printf("%d ", w++ || z++); printf("%d ", !w > z); printf("%d\n", w && z); A. 0 1 1 1B. 1 1 1 1C. 0 1 0 1D. 0 1 0 0 (27) 以下程序的运行结果是()。 int main( ) { int k=4, a=3, b=2, c=1; printf("\n%d\n", k=y)&&(y>=z)B. (x>=y)AND(y>=z) C. (x>=y>=z) D. (x>=y)||(y>=z) (30) 以下程序的输出结果是()。 int main( ) { int a=-1, b=4, k; k=(++a<0)&&!(b--<=0); printf("%d%d%d\n", k, a, b); return 0; } A. 104B. 103C. 003D. 004 3. 程序填空题 (1) 执行下面程序时,若从键盘上输入8,则输出为9,请填空。 int main( ) { int x; scanf("%d", &x); if(> 8) printf("%d\n",++x); else printf("%d\n", x--); return 0; } (2) 执行下面程序时输出为1,请填空。 int main( ) { int a=4, b=3, c=2, d=1; printf("%d\n",(a < b ? a : d < c ?: b)); return 0; } (3) 下面程序的功能是根据输入的百分制成绩score,转换成相应的五分制成绩grade并打印输出。转换的标准为: 当90≤score≤100时,grade为A; 当80≤score<90时,grade为B; 当70≤score<80时,grade为C; 当60≤score<70时,grade为D; 当score<60时,grade为E; 请填空。 #include int main( ) { int score, mark; scanf("%d", ); mark=; switch(mark){ default:printf("%dE", score); case 10: case :printf("%dA", score);break; case :printf("%dB", score);break; case :printf("%dC", score);break; case :printf("%dD", score);break; } return 0; } (4) 以下程序是对四个数a、b、c、d从大到小的顺序输出,请填空。 #include int main( ) { int a, b, c, d, t; scanf("%d%d%d%d", &a, &b, &c, &d); if(a int main( ) { int y, f; scanf("%d", &y); if(y%400==0) f=1; else if() f=1; else ; if(f)printf("%d is not ", y); printf("a leap year\n"); return 0; } (6) 输入一个字符,如果它是一个大写字母,则把它变成小写字母; 如果它是一个小写字母,则把它变成大写字母; 其他字符不变,请填空。 #include int main( ) { char ch; scanf("%c", &ch); if() ch=ch+32; else if(ch>='a' && ch<='z') ; printf("%c", ch); return 0; } 4. 编程题 (1) 编一程序判断输入整数的正负性和奇偶性。 (2) 编程判断输入数据的符号属性。 sign=1x>0 0x=0 -1x<0 输入x,打印出sign的值。 (3) 输入任意三个数num1、num2、num3,按从小到大的顺序排序输出。 (4) 在屏幕上显示一张如下所示的时间表。 *****Time***** 1morning 2afternoon 3night Please enter your choice: 操作人员根据提示进行选择,程序根据输入的时间序号显示相应的问候信息,选择1时显示“Good morning”,选择2时显示“Good afternoon”,选择3时显示“Good night”,对于其他的选择显示“Selection error!”,用switch语句编程实现。 (5) 输入一个年份和月份,打印出该月份有多少天(考虑闰年),用switch语句编程。 (6) 运输公司对用户计算运费。路程(以s表示,单位为km)越远,每千米运费越低。标准如下: s<250没有折扣 250≤s<5002%折扣 500≤s<10005%折扣 1000≤s<20008%折扣 2000≤s<300010%折扣 3000≤s15%折扣 设每千米每吨货物的基本运费为p,货物重为w,距离为s,折扣为d,则总运费f的计算公式为: f=p × w ×s ×(1-d) 编一程序用于计算总运费。要求用switch语句来实现。 (7) 编一程序,对于给定的一个百分比制成绩,输出相应的五分制成绩。设90分以上为‘A’,80~89分为‘B’,70~79分为‘C’,60~69分为‘D’,60分以下为‘E’(用switch语句实现)。 (8) 编程实现: 输入一个整数,判断它能否被3,5,7整除,并输出以下信息之一。  能同时被3,5,7整除;  能被其中两个数(要指出哪两个)整除;  能被其中一个数(要指出哪一个)整除;  不能被3,5,7任一个整除。