第3章 选择结构与循环结构 C语言程序设计是面向过程的结构化程序设计。在结构化的程序中,可以执行的流 程分为3种:顺序结构、选择结构和循环结构。顺序结构是程序流程控制的基本结构;同 时,为了实现各种功能的算法,除了按照正常的流程顺序执行外,还需要改变顺序控制流 程以实现某种功能,这种情况下要用到选择语句、循环语句等控制语句,这些控制语句根 据不同的情况分别执行相应的动作,或者重复执行某个动作。本章介绍在程序中怎样实 现以上选择和循环控制程序结构。 在第1章中曾介绍过,一个函数包含声明部分和执行部分,执行部分是由语句组成 的,语句的作用是向计算机系统发出操作指令,要求执行相应的操作。C语言中的语句主 要有控制语句、表达式语句、空语句、复合语句、函数调用语句等,下面分别进行简要介绍。 (1)控制语句。用于完成一定的控制功能。C语言有以下9种控制语句。 ①if…else (条件语句) ②for (循环语句) ③ while (循环语句) ④ do…while (循环语句) ⑤ break (中断switch或循环语句) ⑥continue (结束当前循环语句) ⑦switch (多分支选择语句) ⑧return (返回语句) ⑨ goto (转向语句) (2)表达式语句。表达式是通过运算符连接操作数得到的式子,包括算术运算表达 式、关系运算表达式、逻辑运算表达式、赋值表达式等。一个表达式加上分号就构成表达 式语句。例如: x=1; a=3*b-c/4; ++i; c=a>b? a:b; 都是合法的表达式语句。 在各种表达式语句中,赋值语句是最基本的表达式语句,也是用得最多的语句。与其 他高级语言相比,C语言的赋值语句有以下不同。 ① C语言的赋值符号“=”是一个运算符,而在其他大多数语言中赋值符号不是运 算符。② 赋值表达式可以包括在其他表达式中,但赋值语句不能包含在其他语句和表达式 中。例如: 66 while((ch=getchar())!='\n') 在该语句中,while中的条件表达式首先执行赋值运算ch=getchar(),然后判断ch 是否为换行符\n。while中的条件表达式可以包含赋值表达式ch=getchar(),但不能包 含赋值语句“ch=getchar();”。 任何表达式都可以加上分号成为语句,例如,“x+y*5-z;”是一条合法语句,但是没 有将运算结果赋值给任何一变量,所以单独执行没有意义。 (3)空语句。空语句是由一个分号(;)构成的语句。 与其他语句不同的是,空语句不执行任何操作,通常与while和for语句一起使用,作 为循环语句的循环体(循环体什么也不执行),例如: while((c=getchar())==' '); 该语句表示将跳过输入字符串中的空白字符。 (4)复合语句。用花括号将多条语句括起来得到的就是复合语句,又称分程序。复 合语句通常用于选择结构、循环结构中。例如: while(grade>CUTOFF){ printf("请输入分数:"); scanf("%d",&grade); total+=grade; } 3条语句用花括号括起来形成一条复合语句,注意最后一条语句的分号不能省略。 对于简单的语句块,有时用逗号表达式替代复合语句。例如, if(ay) printf("x>y"); if语句圆括号中的表达式一般是关系表达式或逻辑表达式,但也可以是其他表达式。 图3-1 if语句的流程图 if语句的流程如图3-1所示。 首先对圆括号中的表达式求值,当表达式的值为真(非0) 时,执行圆括号后的语句段;否则跳过该语句段,继续执行后面的 语句。其中,语句段可以是一条语句,也可以是包含在花括号之 间的复合语句。 例3-1 求绝对值。 【问题描述】 输入任意一个整数,然后输出它的绝对值。 【输入格式】 输入任意一个整数。 【输出格式】 输出它的绝对值。 【样例输入】 -4 【样例输出】 4 【问题分析】 正数和零的绝对值是它自身,负数的绝对值是它的相反数,因此只需要对负数进行处 理即可。 【参考代码】 1. #include 2. using namespace std; 3. int main() 68 4. { 5. int n; 6. cin>>n; 7. if(n<0) 8. n=-n; 9. cout< 2. using namespace std; 3. int main(){ 4. int a,b,c,t; 5. cin>>a>>b>>c; 6. if(a 2. using namespace std; 3. int main(){ 4. int a,b,c; 5. cin>>a>>b; 6. if(a>b) 7. c=a; 8. else 9. c=b; 10. cout<<"max="<5 x2 +3x +1, 其他 ì . í .. .. 【输入格式】 一个实数x。 【输出格式】 实数y,保留到小数点后两位。 【样例输入】 -3 【样例输出】 -9.00 【问题分析】 本题旨在考查if…else的嵌套结构,需要注意的是,构造的条件需要涵盖分段函数的 定义域。 【参考代码】 1. #include 2. using namespace std; 3. int main() 4. { 5. double x,y; 6. cin>>x; 7. if(x<-1) 8. y=-x*x; 9. else{ 10. if(x>5) 11. y=sqrt(x)+2*x-1; 12. else 13. y=x*x+3*x+1; 14. } 15. printf("%.2lf",y); 16. return 0; 17. } 对于if…else语句的嵌套使用,进行以下3点说明。 (1)else总是与if成对出现。可以单独使用if语句,但是不能单独使用else语句。 (2)else总是与它上面最近的未配对的if匹配。 例如,如果将上例程序中if…else语句的嵌套关系改为如下程序,思考修改后的程序 是否正确。 72 y=x*x+3*x+1; if(x>=-1) if(x>5) y=sqrt(x)+2*x-1; else y=-x*x; 编程者把else写在与第一个if同一列上,意图是使else与第一个if对应,但实际上 else是与第二个if配对的,因为它们相距最近。应该计算的是x<-1的情况,但此时表 达式y=-x*x是x<=5的情况,所以结果是错误的。 如果if与else的数目不一样,为避免错误,习惯上将嵌套中的if语句用花括号括起 来。例如,上述错误的程序可以做如下修改: y=x*x+3*x+1; if(x>=-1){ if(x>5) y=sqrt(x)+2*x-1; }e lse y=-x*x; 对于初学者来说,为了避免使用上的错误,最好将嵌套的if语句用花括号括起来,不 管该语句是不是复合语句,都写成如下形式: if(表达式1){ if(表达式2) 语句段1 }e lse 语句段2 这样,花括号限定了嵌套if语句的范围,因此else与第一个if配对。 (3)在嵌套的if…else语句的使用中,将第二个if…else语句嵌套在其上层if…else 语句的else部分内时,会产生最常用的嵌套的if…else语句,其嵌套的一般形式如下: if(表达式1) 语句段1 else if(表达式2) 语句段2 else if(表达式3) 语句段3 … else if(表达式n) 语句段n else 语句段n+1 73 这种嵌套形式又称else…if结构。else…if结构依次计算表达式i(i=1,2,…,n)的 值,当表达式i为真时,执行与之相关的语句段i,并以此结束整个嵌套链。else…if结构 的流程图如图3-4所示。 图3-4 else…if结构的流程图 例3-5 等级成绩。 【问题描述】 学院的考试采用等级制,即将百分制转换为A、B、C、D、E5个等级,设成绩为X,则 90≤X≤100为A,80≤X<90为B,70≤X<80为C,60≤X<70为D,否则为E。编写一 个程序,将输入的分数转换成A、B、C、D和E5个等级。 【输入格式】 输入一个单精度数。 【输出格式】 输出相应等级。 【样例输入】 100 【样例输出】 A 【问题分析】 本题考查if…else语句的嵌套结构。对于这种多条件的问题,建议从一个方向进行 判断,可以从高到低,也可以从低到高。 【参考代码】 1. #include 例3-5 74 2. using namespace std; 3. int main() 4. { 5. double x; 6. cin>>x; 7. if(x>=90) 8. cout<<"A"<=80) 10. cout <<"B"<=70) 12. cout<<"C"<=60) 14. cout<<"D"<4ac,则两个实根不等,输出为x1=…;x2=…,其中x1>x2。 若b2<4ac,则有两个虚根,输出为“x1=实部+虚部i;x2=实部-虚部i”,即x1的 虚部系数大于或等于x2的虚部系数,实部为0时不可省略。实部=-b/(2*a),虚部= sqrt(4*a*c-b*b)/(2*a)。 所有实部要求精确到小数点后5位,数字、符号之间没有空格。 【样例输入1】 1.0 2.0 8.0 【样例输出1】 x1=-1.00000+2.64575i;x2=-1.00000-2.64575i 例3-6 75 【样例输入2】 1 0 1 【样例输出2】 x1=-0.00000+1.00000i;x2=-0.00000-1.00000i 【问题分析】 本题旨在考查if…else结构和输出的格式控制,需要根据不同的情况进行处理。 【参考代码】 1. #include 2. using namespace std; 3. int main() { 4. double a,b,c,delta; 5. double x1,y1,x2,y2,t; 6. cin>>a>>b>>c; 7. delta=b*b-4*a*c; 8. if(delta==0) 9. printf("x1=x2=%.5lf",-b/(2*a)); 10. else{ 11. if(delta>0){ 12. x1=(-b+sqrt(delta))/(2*a); x2=(-b-sqrt(delta))/(2*a); 13. if(x10) //正的虚根用+显示 23. printf("+%.5lfi;x2=%.5lf",y1,x1); 24. else //负的虚根用-显示,若数是负数,直接输出 25. printf("%.5lfi;x2=%.5lf",y1,x1); 26. if(y2>0) 27. printf("+%.5lfi",y2); 28. else 29. printf("%.5lfi",y2); 30. } 31. } 32. return 0; 33. } 76 3.1.4 多分支选择结构———switch语句 if语句可以处理二分支问题,采用嵌套的if…else语句还可以处理多分支问题。但 如果分支太多,嵌套的层数就会很深,if与else的配对就容易出现问题,还会影响程序的 可读性。为此,C语言提供了专门用于处理多分支选择结构的条件语句———switch语句, 又称开关语句。在某些情况下使用switch语句,程序结构清晰,使得代码具有更好的可 读性。switch语句的一般形式如下: switch (表达式) { case 常量表达式1: 语句段1 [break;] case 常量表达式2: 语句段2 [break;] … case 常量表达式n: 语句段n [break;] [default : 语句段n+1] } switch语句的流程:首先计算switch表达式的值,如果该表达式的值等于某个case 的常量表达式的值,则程序的控制转向该case后面的语句,即执行该case后的语句段。 如果case语句段后有break语句,执行完语句段后则控制跳出switch语句,执行switch 之后的程序;如果case语句段后没有break语句,则执行完该语句段后,将继续往下执 行,直到遇到break语句,如果没有break语句,则将执行到switch语句的最后。如果 switch表达式的值不等于任何一个case的常量表达式的值,但switch语句有default语 句,则执行default后面的语句段,执行后,退出switch语句,执行switch之后的语句。 switch语句的流程如图3-5所示。 图3-5 switch语句的流程图 使用switch语句时,有8点需要注意。 (1)switch关键字后面的表达式只能是整型、字符型或枚举型表达式。 77 (2)case关键字后面的常量表达式必须是与表达式相对应的整型、字符型或枚举型 常量,不能是变量和表达式,并且case和常量表达式之间要有空格。 (3)每个case关键字后面的常量表达式的值必须互不相同,即同一个常量在switch 语句中只能对应一种处理方案,否则会出现互相矛盾的现象。 (4)语句段可以是简单语句也可以是复合语句。 (5)如果表达式的值与所有常量表达式的值都不匹配,就执行default后面的语句。 如果没有default语句,则不执行任何语句,流程转到switch语句的下一条语句。 (6)case和default可以出现在任何位置,其前后次序不影响执行结果,但习惯上将 default放在switch语句的底部。 (7)通常在case后面的语句中包含break语句,当程序执行与表达式匹配的case语 句段时,遇到break语句后跳出整个switch语句。如果不存在break语句,则执行完后, 流程控制转到下一个case(包括default)中的语句继续执行。 (8)case提供了执行某一语句段的入口,起着标号的作用;多个case可以执行同一语 句段。用 switch语句可以代替嵌套的if…else语句实现多分支选择结构,下面用switch语 句编写前面用嵌套的if…else语句实现的对成绩进行分类的程序。 例3-7 等级成绩。 【问题描述】 同例3-5。 【输入格式】 输入一个单精度数。 【输出格式】 输出相应等级。 【样例输入】 100 【样例输出】 A 【问题分析】 本题旨在考查switch语句的应用。对问题进行分析,将成绩分为5个区间:[0,60), [60,70),[70,80),[80,90),[90,100]。考虑到switch后面的表达式只能是整型,因此需 要将输入的浮点数转换成整型。因此,可以有效利用整数的整除,将输入的成绩转换为 0~10的整数。 【参考代码】 1. #include 2. using namespace std; 3. int main() 4. { 例3-7 78 5. float score; 6. cin>>score; 7. switch((int)score/10){ 8. case 10: 9. case 9: cout<<"A";break; 10. case 8: cout<<"B";break; 11. case 7: cout<<"C";break; 12. case 6: cout<<"D";break; 13. default: cout<<"E"; 14. } 15. return 0; 16. } 【代码分析】 (int)score是将输入的浮点数转换为整数,整除10会产生0~10的相关整数。考虑 到0~5这6种情况对应等级E,因此将其放在default中。当结果是10和9时,均为等 级A,因此,case10后面不增加任何代码,只是作为一个标号存在。输出相应的等级后, 增加break语句,结束相应的判断。 例3-8 写出下列程序的运行结果。 1. #include 2. int main() 3. { 4. int i; 5. for(i=1;i<=5;i++){ 6. switch(i%2){ 7. case 0: i++; printf("#\n"); 8. case 1: i+=2; printf("*"); 9. default: printf(" "); 10. } 11. } 12. return 0; 13. } 【代码分析】 本题对i=1~5的5种情况进行判断,进行以下分析。 当i=1时,i%2=1,因此,执行case1后的内容,i=3,并在屏幕输出*,由于后面没 有break语句,因此会继续执行default标号的内容,输出空格。 此时i=3,执行for循环中的i++,i=4,i%2=0,执行case0后面的内容,输出#后 换行;由于后面没有break语句,继续执行case1的内容,i=6,输出*;没有break语句, 继续执行default标号后的内容,输出空格。 因此上述程序的运行结果如下(其中□表示空格): 79 *□# *□ 3.1.5 选择结构程序举例 例3-9 闰年。 【问题描述】 任意给出的一个年份,判断是不是闰年。 【输入格式】 只有一个整数year,代表年份。 【输出格式】 如果是闰年输出Yes,否则输出No。 【样例输入】 2000 【样例输出】 Yes 【问题分析】 闰年的基本规则是“四年一闰,百年不闰,四百年再闰”。因此可以分为两种情况: ①年份是4的倍数,但不是100的倍数;②年份是400的倍数。 【参考代码】 1. #include 2. using namespace std; 3. int main() { 4. int year; 5. cin>>year; 6. if((year%4==0 && year%100!=0) || (year%400==0)) 7. cout<<"Yes"; 8. else 9. cout<<"No"; 10. return 0; 11. } 例3-10 3个数排序。 【问题描述】 输入3个数a,b,c,按由小到大的顺序输出。 【输入格式】 标准输入,3个无序的单精度数,以空格隔开。 80 【输出格式】 标准输出,输出3个由小到大排好序的浮点数,以空格隔开。 【样例输入】 3 2 1 【样例输出】 1 2 3 【问题分析】 虽然可以通过简单的数据交换完成,本题旨在考查if…else的嵌套结构,可以不改变 3个数的数值完成数据从小到大的输出。对于3个数,共有3!=6种情况,如图3-6所示。 图3-6 输入3个数,按从小到大的顺序排序 【参考代码】 1. #include 2. using namespace std; 3. int main() { 4. float a,b,c; 5. cin>>a>>b>>c; 6. if(ac) 17. cout< 2. using namespace std; 3. int main() { 4. int a,b,ans; 5. char ch; 82 6. cin>>a>>b; 7. cin>>ch; 8. switch(ch){ 9. case '+': ans=a+b; break; 10. case '-': ans=a-b; break; 11. case '*': ans=a*b; break; 12. case '/': ans=a/b; break; 13. } 14. cout< 2. using namespace std; 3. int main() { 4. int year,month,day,flag,ans=0; 5. scanf("%4d%2d%2d",&year,&month,&day); 6. if((year%4==0 && year%100!=0) || (year%400==0)) 7. flag=1; 8. else 例3-12 83 9. flag=0; 10. switch(month){ 11. case 1: break; 12. case 2: ans+=31; break; 13. case 3: ans+=31+28; break; 14. case 4: ans+=31+28+31; break; 15. case 5: ans+=31+28+31+30; break; 16. case 6: ans+=31+28+31+30+31; break; 17. case 7: ans+=31+28+31+30+31+30; break; 18. case 8: ans+=31+28+31+30+31+30+31; break; 19. case 9: ans+=31+28+31+30+31+30+31+31; break; 20. case 10: ans+=31+28+31+30+31+30+31+31+30; break; 21. case 11: ans+=31+28+31+30+31+30+31+31+30+31; break; 22. case 12: ans+=31+28+31+30+31+30+31+31+30+31+30; break; 23. } 24. ans+=day; 25. if(flag==1 && month>2) //对闰年的情况进行处理 26. ans++; 27. cout< 2. using namespace std; 3. int main() { 4. int year,month,day,flag,ans=0; 5. scanf("%4d%2d%2d",&year,&month,&day); 6. if((year%4==0 && year%100!=0) || (year%400==0)) 7. flag=1; 8. else 9. flag=0; 10. switch(month){ 11. case 12: ans+=30; //11 月的天数 12. case 11: ans+=31; //10 月的天数 13. case 10: ans+=30; //9 月的天数 14. case 9: ans+=31; //8 月的天数 15. case 8: ans+=31; //7 月的天数 16. case 7: ans+=30; //6 月的天数 17. case 6: ans+=31; //5 月的天数 18. case 5: ans+=30; //4 月的天数 19. case 4: ans+=31; //3 月的天数 84 20. case 3: ans+=28; //2 月的天数 21. case 2: ans+=31; //1 月的天数 22. case 1: ans+=day; 23. } 24. if(flag==1 && month>2) 25. ans++; 26. cout<