第???3???章? 程序控制结构 3.1 引例与概述 3.1.1 引例 例3.1.1?能否绝地反击 第47届ICPC亚洲区域赛采用线上赛的形式举行,各赛站的参赛队伍大增。为保障奖牌含金量,组委会规定至多只有210支参赛队伍能获奖。在某赛站比赛封榜(比赛共5小时,最后1小时不更新榜单)前ZMF队解出s道题,耗时t分钟,排名已在210名之外。已知最终排名210的队伍的解题数和耗时(含罚时,正确解题前的每次错误提交罚时20分钟)。若ZMF队坚韧不拔,顽强拼搏之后又解出了n道题,那么该队能绝地反击排在210名之前而获奖吗?排名时,解题数多的排名靠前,若解题数相同,则耗时少的排名靠前。若ZMF队的解题数和耗时都与排名210的队伍相同,则也无法获奖。   输入格式: 输入2行,第1行输入2个正整数,表示最终排名在210的队伍的解题数和耗时;第2行先输入3个正整数s,t,n,然后再输入n个整数,表示ZMF队封榜后解出n道题中各题的耗时。   输出格式: 若ZMF队能够获奖,则输出Yes,否则输出No。   输入样例: 4 700 2 31 2 250 300   输出样例: Yes 设210名的队伍的解题数为a,耗时为b,则本题可先计算ZMF队的解题数为s=s+n,再循环n次(使用for循环语句),把n道题的耗时逐个累加到t中,最后根据s与a及t与b的比较情况(使用if语句)依题意输出Yes或No。具体代码如下: //C++风格代码 #include using namespace std; int main() { int a,b,c,n,s,t; cin>>a>>b;????????? //输入210名的题数和耗时 cin>>s>>t>>n;??????? //输入ZMF队封榜前的解题数、耗时及封榜后的解题数 s+=n;???????????? //解题数加n for(int i=1;i<=n;i++) { //循环n次,累加封榜后的耗时 ????cin>>c; ????t+=c; ??} //若解题数超过a,或解题数等于a且耗时小于b,则输出Yes,否则输出No ??if(s>a || (s==a&&t int main() { ??int a,b,c,i,n,s,t; scanf("%d%d",&a,&b);??? //输入210名的题数和耗时 scanf("%d%d%d",&s,&t,&n); //输入ZMF队封榜前的解题数、耗时及封榜后的解题数 s+=n;???????????? //解题数加n for(i=1;i<=n;i++) {???? //循环n次,累加封榜后的耗时 ????scanf("%d",&c); ????t+=c; ??} //若解题数超过a,或解题数等于a且耗时小于b,则输出Yes,否则输出No ??if(s>a || (s==a&&t>a>>b;???????? //输入 cout< using namespace std; int main() { int a,b,c; cin>>a>>b; c=a;??????????? //假设第一个数大,并放到c中 if(b>c)c=b;?????? //若第二个数大于假设的最大数c,则把第二个数放到c中 cout< int main() { int a,b,c; scanf("%d%d",&a,&b); c=a;??????????? //假设第一个数最大,并放到c中 if(b>c)c=b;????? //如果第二个数大于假设的最大数c,则把第二个数放到c中 printf("%d\n",c); return 0; } 思路2:直接比较两个数,若前一个数大,则把它作为结果输出,否则输出后一个数。可用双分支if语句实现,具体代码如下: //C++风格代码,双分支选择结构 #include using namespace std; int main() { int a,b; cin>>a>>b; if(a>=b)??????? //若第一个数大于或等于第二个数 cout< int main() { int a,b; scanf("%d%d",&a,&b); if(a>=b)??????????? //若第一个数大于或等于第二个数 printf("%d\n",a);? //输出第一个数 else printf("%d\n",b);? //输出第二个数 return 0; } 本题两种思路的代码中分别使用了单分支、双分支if语句。   1. 基本的if语句 基本if 语句格式如下: if (条件) 语句1 [ else 语句2 ] 描述语法时,[ ]表示[ ]中的内容是可选项,即if语句可以是单分支if语句,具体如下: if (条件) 语句1 此if语句在满足条件时执行语句1,否则不执行任何语句,其流程图如图3-3所示。 if语句也可以是双分支if语句,格式如下: if (条件) 语句1 else 语句2 此if语句在满足条件时执行语句1,否则执行语句2,其流程图如图3-4所示。 图3-3?单分支if语句流程图 图3-4?双分支if语句流程图 可见,if语句可带else子句(双分支选择结构),也可不带(单分支选择结构)。 if语句的条件需用圆括号括起来,该条件一般是一个逻辑表达式或关系表达式,否则一切0值转换为false,一切非0值转换为true。 语句1和语句2可以是基本语句,也可以是复合语句。例如: if(x)??????????????? //x相当于x!=0 a=x*x;???????????? //基本语句 else a=x; if(x==1) {???????????? //复合语句 a=1; b=2; } else { a=-1; b=-2; }   2. 嵌套的if 语句 嵌套的if语句是指在if语句中又使用if语句。if语句可以嵌套在if子句中,也可以嵌套在else子句中。 从第一个else开始,else总与离它最近的且未被匹配的if配对(最近匹配原则)。 观察如下代码,思考能否达到“当n小于或等于0时把z赋值为b,否则当a大于b时,把z赋值为a”的目的。 if(n>0) if(a>b) z=a; else z=b; 上面的else虽然在缩排上与if(n>0)对齐,但根据最近匹配原则,这个else与if(a>b)匹配,达到的效果是“当n大于0且a小于或等于b时把z赋值为b”。为避免类似问题,建议在else子句中嵌套if语句。 思考:如何才能使得else与if(n>0)匹配? 可以把第2个if语句用{}括起来: if(n>0) { if(a>b) z=a; } else z=b; 或者,把if语句嵌套到else子句中: if(n<=0) z=b; else if(a>b) z=a;   3.??if 选择结构示例 例3.2.2?三者的最大值 输入3个整数,找出其中最大的一个并显示出来。 思路:假设第一个数最大并放到结果变量d中,若后面的数大于d,则把d变为该数。可用单分支语句实现,代码如下: //C++风格代码 #include using namespace std; int main() { int a, b, c, d; cin>>a>>b>>c; d=a;????????????? //假设第一个数最大,把其存放在d中 if(b>d) d=b;??????? //若第二个数大于假设的最大数d,则把d改为该数 if(c>d) d=c;??????? //若第三个数大于假设的最大数d,则把d改为该数 cout< int main() { int a, b, c, d; scanf("%d%d%d",&a,&b,&c); d=a;????????????? //假设第一个数最大,把其存放在d中 if(b>d) d=b;??????? //若第二个数大于假设的最大数d,则把d改为该数 if(c>d) d=c;??????? //若第三个数大于假设的最大数d,则把d改为该数 printf("%d\n",d); return 0; } 这种思路比较简单,而且容易扩展到多个数的情况(结合循环结构)。读者可以思考本题还有哪些其他实现方法,并自行编写代码实现。 例3.2.3?三数排序 输入3个整数,然后按从大到小的顺序把它们显示出来。 这个问题该如何实现呢?仔细思考,可以想到多种方法。下面给出两种方法,分别用到选择排序和冒泡排序的思想。 思路1:采用选出当前最大者放到当前最前面位置(选择排序)的思想。具体代码如下: //C++风格代码 #include using namespace std; int main() { int a,b,c,d; cin>>a>>b>>c; if(a=b d=a,a=b,b=d; if(a int main() { int a,b,c,d; scanf("%d%d%d",&a,&b,&c); if(a=b d=a,a=b,b=d; if(a using namespace std; int main() { int a,b,c,d; cin>>a>>b>>c; if(a=b d=a,a=b,b=d; if(b int main() { int a,b,c,d; scanf("%d%d%d",&a,&b,&c); if(a=b d=a,a=b,b=d; if(b using namespace std; int main() { int score; char rank; cin>>score; if(score>=90) rank='A'; else if(score>=80) rank='B'; else if(score>=70) rank='C'; else if(score>=60) rank='D'; else rank='E'; cout< int main() { int score; char rank; scanf("%d",&score); if(score>=90) rank='A'; else if(score>=80) rank='B'; else if(score>=70) rank='C'; else if(score>=60) rank='D'; else rank='E'; printf("%c\n",rank); return 0; } 3.2.2 switch语句及其使用 例3.2.4中的成绩转换是多分支选择结构,也可使用switch语句实现,具体代码如下: //C++风格代码 #include using namespace std; int main() { int score; char rank; cin>>score; switch(score/10) {??????? //对百分制成绩作除以10的运算,减少常量个数 case 9: case 10: rank='A';break; case 8: rank='B';break; case 7: rank='C';break; case 6: rank='D';break; default: rank='E'; } cout< int main() { int score; char rank; scanf("%d",&score); switch(score/10) {??????? //对百分制成绩作除以10的运算,减少常量个数 case 9: case 10: rank='A';break; case 8: rank='B';break; case 7: rank='C';break; case 6: rank='D';break; default: rank='E'; } printf("%c\n",rank); return 0; } 上面的程序根据表达式score/10的值去匹配case后面的常量值,匹配成功则执行其后的语句,执行到break语句时则跳出switch语句;若都匹配不上,则执行default(对应0、1、2、3、4、5等常量)后的语句。另外,case标号后的多条语句不需用“{}”括起来;多个case标号可共有一组语句。 switch语句常用于实现多分支选择结构,其语句格式如下: switch(表达式) { case 常量表达式1:语句序列1 case 常量表达式2:语句序列2 …… case 常量表达式n:语句序列n ? [ default:语句序列n+1 ] } switch 语句中的表达式及常量表达式一般应为整型或字符型。执行时,按表达式的值与case后的常量表达式相匹配,若匹配成功则执行相应语句序列,否则执行default后的语句序列,流程图如图3-5所示。 各个case块(含default块)的顺序可以交换。一般每个语句序列(处于最后的除外)都是由包含break的多条语句构成,但不需要使用“{}”括起来。 若各case块的语句序列中没有用break语句跳出switch语句,则语句序列将顺序执行下面的语句(不再考虑表达式与常量值的匹配)。例如: //C++风格代码 int n; cin>>n; switch(n) { case 10: cout<<'A'<>n; switch(n) { case 10: cout<<'A'< using namespace std; int main() { int year,month,days; cin>>year>>month; switch(month) { case 2: if(year%4==0&&year%100!=0||year%400==0) days=29; else days=28; break; case 4:case 6:case 9:case 11: days=30; break; default: days=31; break; } cout< int main() { int year,month,days; scanf("%d%d",&year,&month); switch(month) { case 2: if(year%4==0&&year%100!=0||year%400==0) days=29; else days=28; break; case 4:case 6:case 9:case 11: days=30; break; default: days=31; break; } printf("%d\n",days); return 0; } 3.3 循环结构 3.3.1 引例 例3.3.1?n个整数中的最大值 首先输入一个整数n,然后输入n个整数,请输出这n个整数中的最大值。 在例3.2.2中,使用2条类似的单分支if语句可求得3个数的最大值。现在要求n个数中最大值,若程序运行前已知n的值,则也可以写n-1个单分支语句完成,但n是一个变量,在程序运行中输入后才能确定其值,因此无法在程序运行前写好n-1个if语句。由于n-1个if语句是重复的,可以写一条if语句并使其执行n-1次。这可以使用循环结构完成。循环结构中的for语句常用于实现次数固定且重复执行的要求。求n个整数中的最大值的具体代码如下: //C++风格代码 #include using namespace std; int main() { int n,t,max; cin>>n; ? //先输入数据个数 cin>>t; ? //先输入第1个数 max=t; ? //假设第一个数最大,并放到max中 for(int i=1; i>t; if(t>max) ? //若后面的数大于当前的最大数 max=t; ? //把当前的最大数赋值为后面的数 } cout< int main() { int i,n,t,max; scanf("%d",&n); //先输入数据个数 scanf("%d",&t); //先输入第1个数 max=t; ? //假设第一个数最大,并放到max中 for(i=1; imax) ? //若后面的数大于当前的最大数 max=t; ? //则把当前的最大数赋值为后面的数 } printf("%d\n",max); return 0; } 上面的代码中,for语句中循环变量i从1到n-1进行循环,共执行n-1次循环体(输入t,再比较max和t,把大者保存在max中)。 3.3.2 三种循环语句   1.??for语句 for 循环语句的基本格式如下: for(表达式1; 表达式2; 表达式3) 循环体 for后( )中两个分号必不可少,三个表达式可以根据具体情况省略。其中,表达式1通常是循环变量初始化或给循环变量赋值,可用逗号表达式给多个变量赋值;表达式2是循环条件,在满足循环条件时反复执行循环体,否则结束循环;表达式3通常是循环变量调整,可以从小往大调整也可以从大往小调整,如果调整多个循环变量,则构成逗号表达式。for循环的流程图如图3-6所示。 注意,在标准编译器下,循环变量初始化中的变量仅在该for语句中有效。若希望循环变量在for语句中及其后都能使用,则把该循环变量定义在for语句之前。 图3-6?for语句循环流程图 最简单的for语句形式如下:   for(;;) 循环体 即三个表达式同时省略,此时循环体中一般要有结束循环的语句。例如,跳出循环的break语句,否则将成为无限循环(死循环)。因为表达式2缺省时表示循环条件为true,是永真条件。 例3.3.2?求和 输入一个正整数n,求出由1加至n的总和。测试数据保证结果不大于2147483647。 本题可以直接使用等差数列的求和公式n(n+1)/2求解,但需要注意的是,n(n+1)可能产生溢出。例如,n=50000,则50000×50001=2500050000,超出2147483647,即产生溢出。读者可以自行思考如何避免溢出。具体代码留给读者自行实现。 下面采用直接循环逐项累加的方法求解。 本例使用for语句的具体代码如下: //C++风格代码 #include using namespace std; int main() { int sum=0,n,i; cin>>n; for(i=1; i<=n; i++) sum+=i; cout< int main() { int sum=0,n,i; scanf("%d",&n); for(i=1; i<=n; i++) sum+=i; printf("%d\n",sum); return 0; }   2.??while语句 while 循环语句的格式如下: while(循环条件) ? 循环体 while语句在满足循环条件时反复执行循环体,否则结束循环,流程图如图3-7所示。 图3-7?while语句循环流程图 while语句循环的循环体中一般需有改变循环变量使循环趋于结束或跳出循环的语句,以避免死循环。若循环体有多条语句,则必须以“{}”括起来构成复合语句。 例3.3.2使用while语句的具体代码如下: //C++风格代码 #include using namespace std; int main() { int sum=0,n,i; cin>>n; i=1; while(i<=n) { sum+=i; i++; } cout< int main() { int sum=0,n,i; scanf("%d",&n); i=1; while(i<=n) { sum+=i; i++; } printf("%d\n",sum); return 0; }   3.??do…while语句 do…while 循环语句格式如下:   do 循环体 while(循环条件); do…while循环体中需要有改变循环变量使循环趋于结束或跳出循环的语句,以避免死循环。若循环体有多条语句,则必须以花括号{}括起来构成复合语句。do…while语句先做一次循环体,再判断循环条件,在满足循环条件时反复执行循环体,否则结束循环,流程图如图3-8所示。 do…while语句较之while语句的区别如下。 若一开始循环条件就不成立,do…while语句执行1次循环体,而while语句执行0次循环体。 例3.3.2使用do…while语句的具体代码如下: //C++风格代码 #include using namespace std; int main() { int sum=0,n,i; cin>>n; i=1; do { sum+=i; i++; } while(i<=n); cout< int main() { int sum=0,n,i; scanf("%d",&n); i=1; do { sum+=i; i++; } while(i<=n); printf("%d\n",sum); return 0; } 思考:如何保证输入的数据一定在[1,10]内? 显然,当不在[1,10]时需重新输入,用do…while语句可方便实现。具体代码如下: //C++风格代码 do { cin>>n; } while(n<1||n>10); //当满足此条件时执行循环体 //C风格代码 do { scanf("%d",&n); } while(n<1||n>10); //当满足此条件时执行循环体 for、while、do…while这三种循环语句可以相互转换,解题时可酌情选用。 3.3.3 continue语句与break语句 在C/C++语言中,continue语句用于提前结束本轮循环的执行,即本轮循环不执行continue之后的语句,而是继续进行下一次循环的准备与条件判断;break语句用来跳出其所在的循环,接着执行该循环之后的语句。 例3.3.3?偶数之和 输入n,求[1,n]内的所有偶数之和。 本题可使循环变量i从2开始,依次递增2,逐个把i加到求和单元中。具体代码留给读者自行实现。 这里采用循环变量从1开始,依次递增1,但在循环体中增加判断语句,跳过累加奇数,具体代码如下: //C++风格代码 #include using namespace std; int main() { int n,sum=0; cin>>n; for(int i=1;i<=n;i++) { if(i%2==1) //i为奇数 continue; ? //跳过本次循环continue之后的语句 sum+=i; } cout< int main() { int i,n,sum=0; scanf("%d",&n); for(i=1;i<=n;i++) { if(i%2==1) //i为奇数 continue; ? //跳过本次循环continue之后的语句 sum+=i; } printf("%d\n",sum); return 0; } 例3.3.4?最大公约数 求两个正整数m、n的最大公约数(Greatest Common Divisor,GCD)。 本题的一种方法是直接根据定义求解,即从m、n两个数中的小者到1逐个尝试(穷举法),找第一个能同时整除m和n的因子(找到则直接用break语句跳出循环),具体代码如下: //C++风格代码 #include using namespace std; int main() { int m,n,gcd; cin>>m>>n; int k=m; if(n=1;i--) { if(m%i==0&&n%i==0) { gcd=i; break; } } cout< int main() { int i,k,m,n,gcd; scanf("%d%d",&m,&n); k=m; if(n=1;i--) { if(m%i==0&&n%i==0) { gcd=i; break; } } printf("%d\n",gcd); return 0; } 上述穷举法代码在线提交可能得到超时反馈。另一种方法是利用欧几里得(Euclid)算法,这种方法可避免在线提交得到超时反馈。欧几里得算法又称为辗转相除法,用于计算两个整数m、n的最大公约数。其计算原理依赖gcd(m, n) = gcd(n, m%n)定理。 例如,求m=70和n=16的最大公约数,计算过程如图3-9所示。 计算时,m、n的值不断用新值代替旧值(迭代法),直到n为0时,m为最大公约数。具体代码如下: //C++风格代码 #include using namespace std; int main() { int m,n,gcd; cin>>m>>n; while(n>0) { int t=m%n; m=n; n=t; } gcd=m; cout< int main() { int m,n,t,gcd; scanf("%d%d",&m,&n); while(n>0) { t=m%n; m=n; n=t; } gcd=m; printf("%d\n",gcd); return 0; }