第3 章〓控制语句与算法 本章练习 Java语言与所有的程序设计语言一样,使用选择语句与循环语句控制程序的执行流程。本章主要介绍选择语句、循环语句的语法格式,以及这些语句的用法,并结合具体的例子介绍如何使用这些语句进行算法设计。 观看视频 3.1Java程序的执行流程 3.1.1算法的执行 Java语言是一种典型的面向对象程序设计语言,整个程序都是由对象组成的,对象中包含属性与方法两部分。在方法部分编程时,只有输入、计算与输出语句是无法实现复杂的算法功能的,例如,对于求和运算f(n)=∑ni=11i ,求出f(n)>=10的最小n。针对这个问题,可以设计如下的算法。 (1) 初始化变量: “f=0,i=0;”。 (2) 如果f<10,则转(3),否则转(6)。 (3) i=i+1。 (4) f=f+1/i。 (5) 转(2)。 (6) n=i,返回n。 从这个简单的算法可以看出,算法的执行是从上到下逐条顺序执行的,在这个顺序执行过程中,需要进行选择判断,并根据选择判断结果控制执行流程(语句)。语句(2)为判断语句,语句(3)~(5)是一个循环累加过程。 Java语言提供了实现上述基本控制结构的语句,有了这些流程控制语句,就可以为任何复杂算法编写程序。 3.1.2语句块与块作用域 在Java程序中,语句块的概念很重要。语句块又称为复合语句,是指由一对{}括起来的0到多条语句。一个语句块中的语句可以看作一个整体,它们要么都执行,要么都不执行,一个语句块可以嵌套在另一个语句块内。 语句块确定了块内变量的作用域。下面的代码是在main()方法中嵌套一个语句块,在该语句块中定义了一个块内变量i。 public static void main(String[] args) { float f=0; … { int i=0; … }//变量i只在本语句块内可见 … } 在上面的代码中,变量f在整个main()方法内可见,即可以访问,而变量i只在嵌套的语句块内可见,在语句块外是不存在的。 Java不允许在嵌套的两个块中声明同名的变量,例如,下面的代码会报编译错误。 public static void main(String[] args) { float f=0; … { int i=0; float f;//编译出错,f不能重复定义 … } … } 3.2选择语句 选择语句又称为条件语句、分支语句。该语句提供了基于条件的不同,选择不同执行路径的能力。Java语言的选择语句有两种,分别是if语句与switch语句。 3.2.1if语句 if语句有三种具体格式,分别是简单if语句、ifelse语句和复合if语句。图31给出了这三种if语句的流程图。 图31if语句的流程图 1. 简单if语句 简单if语句的格式为 if (<逻辑表达式>) <语句块> 其中,<逻辑表达式>的取值只能是true或false,而且必须用圆括号括起来。该if语句的执行流程是先计算<逻辑表达式>的值,如果该值为true,则执行<语句块>中的语句,否则不执行<语句块>。如果<语句块>只有一条语句,{}可以省略。例如: if (a>b) System.out.println("a 大于 b"); 与 if (a>b) {System.out.println("a 大于 b");} 是等价的。 2. ifelse语句 ifelse语句的格式为 if (<逻辑表达式>) <语句块1> else <语句块2> 该语句执行时,首先计算<逻辑表达式>的值,如果该值为true,则执行<语句块1>,否则执行<语句块2>。ifelse语句又称为二择一选择语句。这里<语句块1>与<语句块2>是指用{}括起来的0到多条语句,如果只有一条语句,{}可以省略。 3. 复合if语句 复合if语句又称为多择一if语句,其格式为 if (<逻辑表达式1>) <语句块1> else if (<逻辑表达式2>) <语句块2> … else <语句块n> 该语句执行时,首先计算<逻辑表达式1>的值,如果该值为true,则执行<语句块1>,否则计算<逻辑表达式2>的值,如果该值为true,则执行<语句块2>,以此类推。如果所有的逻辑表达式的值都为假,则执行<语句块n>。这种复合的if语句实际上是从多个条件中从上到下逐个判断,择一执行。 【例31】输入三个数,按由小到大的顺序输出。 import java.util.Scanner; public class Example3_1 { public static void main(String [] args){ float a,b,c,t; Scanner sc=new Scanner(System.in); a=sc.nextFloat(); b=sc.nextFloat(); c=sc.nextFloat(); if (a>b) {t=a; a=b; b=t; } if (c<a) System.out.println("三个数由小到大:"+c+" "+a+" "+b ); else if (c>=b) System.out.println("三个数由小到大:"+a+" "+b+" "+c ); else System.out.println("三个数由小到大:"+a+" "+c+" "+b ); } } 在Example3_1中,用Scanner对象sc从键盘上读取三个数据,输入的三个数分别存到变量a、b、c中。在输入数据时,三个数据用空格分隔,最后按Enter键。下面是该程序运行时的数据输入与结果输出。 231 三个数由小到大: 1.02.03.0 程序中,if(a>b){…}是一个简单的if语句,该语句实现把两个数中大的放到a,小的放到b。第二个if语句是一个多择一的复合if语句。 观看视频 3.2.2switch语句 在处理多个选择时,使用多择一的复合if语句有时显得有些笨拙。为此,许多编程语言提供了switch语句,该语句的语法结构如下。 switch (<switch表达式>) { case value1:<语句块1>; break; case value2:<语句块2>; break; … case valueN:<语句块N>; break; default:<其他语句块>; } 图32给出了switch语句的执行流程。从该流程中可以看出,在switch语句执行时,首先计算<switch表达式>的值 ,然后判断该值是否等于value1,如果相等,则执行<语句块1>与break; 否则判断该值是否等于value2,如果相等,则执行<语句块2>与break,以此类推。如果该值与所有的value值都不等,则执行default后的<其他语句块>。 图32switch语句的执行流程 这里需要特别说明的是在每个case语句末尾的break语句,break语句的功能是跳出该switch语句,执行流程跳到switch之后的语句。如果在case之后没有break语句,程序会继续执行下一条case语句后的<语句块>,直到遇到break或到达switch的末尾。 switch语句有如下规则。 (1) switch语句可以拥有多条case语句。每个case后面跟一个要比较的值和冒号。 (2) switch语句中的switch 表达式的类型只能为byte、short、int、char与字符串,且case语句中值的数据类型必须与之相同。 (3) 当switch表达式的值与case语句的值相等时,case语句之后的语句开始执行,当遇到break语句时,程序跳转到switch语句后面的语句执行。 (4) case语句不必须包含break语句,但如果没有break语句,程序会继续执行下一条case语句后的语句块。 (5) switch语句的最后可以包含一个default分支,其含义是在没有case语句的值和switch 表达式值相等时,执行其后的<其他语句块>。 【例32】编写一个程序,把百分制的成绩转换成五级分制。 在学生成绩登记中,百分制向五级分制的转换规则是90~100分为优、80~89分为良、70~79分为中、60~69分为及格、60分以下为不及格。下面的程序就是按照这个规则实现转换的。 import java.util.Scanner; public class Example3_2 { public static String ptoword(float grad) {/* 输入分数转换为文字 */ String result = null; switch ((int)grad/10) { case 10: case 9: result = "优"; break; case 8: result = "良"; break; case 7: result = "中"; break; case 6: result = "及格"; break; default: result = "不及格"; } return result; } public static void main(String[] args) {float point=0; Scanner scan=new Scanner(System.in); while (true) { System.out.println("Please input score 0--100:"); point=scan.nextFloat(); if (point>=0 & point <=100) System.out.println("The point is: "+ptoword(point)); else System.out.println("The score should be 0--100"); } } } 观看视频 3.3循环语句 当程序中出现了大量的重复且有规律的代码段时,可以用循环语句控制重复代码段的执行。例如,如果需要输出100次“欢迎,欢迎,热烈欢迎” ,不用循环语句会写成下面的形式。 System.out.println("1. 欢迎,欢迎,热烈欢迎!"); System.out.println("2. 欢迎,欢迎,热烈欢迎!"); … System.out.println("100. 欢迎,欢迎,热烈欢迎!"); 显然这种写法显得太笨拙了,如果使用循环语句,代码会变得非常简洁。上面的代码用循环语句实现如下: for (int i=1;i<=100;i++) System.out.println(i+". 欢迎,欢迎,热烈欢迎!"); Java语言的循环语句有三种,分别是for循环、while循环与dowhile循环。三种循环语句各有所适应的循环场景,编程人员可以根据实际问题选择合适的循环语句。 3.3.1while语句 while语句是一种常用的循环语句,其语法格式如下。 while (<逻辑表达式>) { <循环体>; } 图33while语句的执行流程 其中,<逻辑表达式>是循环控制条件,其值是布尔值true或false。在循环开始执行时,先计算一次<逻辑表达式>的值,若条件为false,则while语句结束; 否则执行循环体中的语句并返回到while的开始,继续计算<逻辑表达式>并根据该值确定是否继续循环。图33给出了while语句的执行流程。 【例33】f(n)=∑ni=11i ,编程求出f(n)≥10的最小n。 public class SmallestN { public static void main(String[] args) { double f=0; long n,i=0; while(f<10){ i=i+1; f=f+1.0/i; } n=i; System.out.println("Smallest n="+n); System.out.println("f(n)="+f); } } 该程序的运行结果如下。 Smallesst n=12367 f(n)=10.000043008275778 3.3.2dowhile语句 dowhile语句的语法格式如下: do <循环体> while(<逻辑表达式>); 图34dowhile语句的执行流程 其中,<逻辑表达式>是循环控制条件,其值是布尔类型。dowhile语句的执行与while语句稍有不同。在while语句中,如果一开始的<逻辑表达式>为false,则循环体一次都不执行; 而dowhile语句的执行流程是先执行循环体,然后再计算<逻辑表达式>,如果该值为true,则继续循环,直到该值为false。因而,dowhile语句至少执行一次<循环体>中的语句。这里的<循环体>通常是一个语句块,dowhile语句的执行流程见图34。 【例34】从键盘上输入若干数,求这组数据的平方和s,当平方和s>108或者输入的数为0时,输出数据个数与平方和s,程序结束。 import java.util.Scanner; public class Example3_4 { public static void main(String[] args) { double s=0,x; int i=0; Scanner sc=new Scanner(System.in); do { x=sc.nextDouble(); i=i+1; s=s+Math.pow(x,2); } while (x!=0 && s<=1.0e+8); System.out.println("输入数据的平方和="+s); System.out.println("输入数据个数="+i); } } 在以上程序中,用Scanner对象sc从键盘上读取double类型的值并赋值给x,然后计算x的平方并累加到s中,接下来计算逻辑表达式(x!=0 && s<=1.0e+8)的值,如果该值为true则继续循环,否则,循环结束,执行后面的输出语句。 需要注意的是,判断输入的double类型x是否为0,应当用Math.abs(x)小于一个足够小的数(这里用1.0e-10,即10-10),最好不用x!=0,这是因为在编程语言中,由有限的二进制位数表示浮点数会存在误差,为了代码安全,当比较两个浮点数是否相等时,通常不用“==”判断,而是用它们的差足够小来判断。 Math.pow(x,2)、Math.abs(x)是Math类中的静态方法,分别是计算x2与|x|。 3.3.3for语句 for语句是一种常用的循环语句,语法形式如下: for(<初始化表达式>; <循环条件表达式>; <循环后操作表达式>) <循环体> 观看视频 for循环把循环的初始化操作、循环条件、每次循环后的操作都放在for后面的圆括号中,表达式之间用英文分号分隔。这里的<循环体>为迭代执行的语句块,是每次循环执行的语句序列。for语句的执行流程如图35所示,具体的执行过程如下。 图35for语句能够执行流程 第一步,先执行初始化表达式操作,对循环变量赋初值。在整个循环过程中,对循环变量赋初值操作只执行一次。 第二步,计算<循环条件表达式>的值。若该值为true,表示循环条件成立,则执行<语句块>,然后执行<循环后操作表达式>,再回到本步的开头进行下一次循环。若该值为false,则循环执行结束,执行流程跳到for循环的后继语句。 for语句中的三个表达式可以都为空,即for(;;),表示是一个无限循环(又称死循环)。在这种情况下,循环体中需要根据执行情况,用break语句控制跳出循环,否则,程序会进入死循环。 【例35】编程计算1+11!+12!+…+110!。 public class Example3_5 { public static void main(String[] args) { double s=1; long f=1; for (int i=1;i<=10;i++) {f=f*i; s=s+1.0/f; } System.out.println("Eular number="+s); } } 在程序中,用f存放i!的值,即当i=1时,f中是1!; 在下次循环时,当i变成2时,执行f=f*i,把2!赋值给f,以此类推,当i循环到10时,f中存放的是10!。 3.4跳转语句 在循环语句中使用break与continue语句可以提供对循环过程的额外控制。在某些情况下,使用break与continue语句可以简化编程。尽管Java提供了这种跳转语句,但由于使用它们可能会使程序难以阅读和调试,在编程时,尽量不要使用它们。 3.4.1break语句 在循环执行过程中,如果想在特定条件下退出循环的执行,可以调用break语句。下面的程序给出了在循环中使用break语句的效果。 public class BreakDemo { public static void main(String[] args) { int sum = 0; int number = 0; while (number < 20) { number++; sum += number; if (sum >= 100) break; } System.out.println("The number is " + number); System.out.println("The sum is " + sum); } } 将整数从1到20相加,直到和大于或等于100时,循环结束。如果没有if语句,程序将计算1~20的数字之和。在使用if语句时,当和sum大于或等于100时,break语句控制执行流程跳出循环。 3.4.2continue语句 continue语句可以用在任何循环控制结构中,其功能是跳过循环体中continue后剩余的语句而强行执行下一次循环,即终止当前迭代,进入下一次循环。它用在for循环体与while循环体中,执行过程略有不同。 (1) 在for循环中,continue语句将导致控制流程立即跳到<循环后操作表达式>,更新循环变量,然后进行下一次循环条件判断。 (2) 在while循环或者dowhile 循环中,continue语句控制流程立即跳到<逻辑表达式>计算,并根据该值决定是否继续循环。 下面的程序对1~99中的每个数进行判断,如果这个数是7的倍数或者个位为7,则执行continue语句,跳转到while<逻辑表达式>计算,并根据该<逻辑表达式>的值决定是否继续循环。 public class ContinueDemo { public static void main(String[] args) { int sum = 0; int number = 0; while (number < 100) { number++; if (number % 7 == 0 || number % 10 == 7) continue; sum += number; } System.out.println("The sum is " + sum); } } 该程序最终的运行输出是“The sum is 3879”,该值是1~99中不是7的倍数并且末位不是7的所有数字之和。 3.5控制语句编程举例 本节通过例子介绍常用的算法设计。 【例36】计算一元二次方程ax2+bx+c=0的根,系数a、b、c从键盘输入。 该程序需要考虑以下几种情况。 (1) a、b都为0,程序应输出“不是方程”信息。 (2) a为0并且b不为0,程序是一元一次方程。 (3) a、b都不为0,这时要判断b2-4ac的值是否大于或等于0。若b2-4ac≥0,则输出两个实数,否则,显示方程“没有实数根”。 程序编写如下: import java.util.*; public class Example3_6 { public static void main(String[] args) { double a,b,c,x1,x2; Scanner scan=new Scanner(System.in); System.out.println("input a,b,c: "); a=scan.nextDouble(); b=scan.nextDouble(); c=scan.nextDouble(); if (a==0) if (b==0) System.out.println(" a,b 都为0,不是方程"); else System.out.println(" a为0,只有一个根 x="+(-c/b)); else if (b*b-4*a*c>=0) { x1=(-b+Math.sqrt(b*b-4*a*c))/2/a; x2=(-b-Math.sqrt(b*b-4*a*c))/2/a; System.out.println("x1="+x1+" x2="+x2); } else System.out.println("没有实数根"); } } 在该程序中,在if(a==0)…else…之后各嵌入了一条ifelse语句。在这种ifelse的嵌套中,ifelse的配对方法是else总是和上方还没有配对的if配对。 【例37】求两个正整数的最大公约数。 对于从键盘上输入的两个整数n1与n2,需要先判断是不是都是正整数,如果不是,则显示"n1 和 n2 必须是大于0的整数"。如果两个数都是正整数,程序中先把两个数的较小者赋值给变量n。由于两个数的最大公约不会大于变量n的值,因而可以构建循环,判断变量n,变量n-1,…,1能不能整除n1和n2。编写程序如下: import java.util.Scanner; public class Example3_7 { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("输入第一个整数: "); int n1 = input.nextInt(); System.out.print("输入第二个整数: "); int n2 = input.nextInt(); int gcd = 1; int n; if (n1 > 0 && n2 > 0) { if (n1 >= n2) n = n2; else n = n1; while (n>=1) { if (n1 % n == 0 && n2 % n == 0) {gcd = n; break; } n--; } System.out.println( n1 + " 和 " + n2 + " 最大公约数是: " + gcd); } else System.out.println("n1 和 n2 必须是大于0的整数"); } } 【例38】在屏幕上输出九九乘法口诀表。 要输出一个九九乘法口诀表,需要二重循环。程序代码与输出如下: import java.util.Scanner; public class Example3_8 { public static void main(String[] args) { int i,j,n; Scanner scan=new Scanner(System.in); System.out.print("输入 n"); n=scan.nextInt(); System.out.println("九九乘法口诀表"); i=1; System.out.print(" "); while (i<=n) {System.out.printf("%6d",i); i++; } System.out.println(); i=1; while (i<=n) {System.out.print("-------"); i++; } System.out.println(); i=1; while (i<=n) {System.out.printf("%2d",i); System.out.print("| "); j=1; while (j<=n) { System.out.printf("%6d",i*j);// 使用格式化输出 j++; } System.out.println(); i++; } } } 为了使输出的九九乘法口诀表每列对得整齐,需要让每列数据占相同的宽度,为此程序中使用printf("%6d",x)格式化输出整数x,其含义是以6为宽度、右对齐的格式输出整数x。程序的运行结果如下: 输入n: 10 九九乘法口诀表 12345678910 1|12345678910 2|2468101214161820 3|36912151821242730 4|481216202428323640 5|5101520253035404550 6|6121824303642485460 7|7142128354249566370 8|8162432404856647280 9|9182736455463728190 10|102030405070708090100 【例39】求所有的水仙花数。 水仙花数是指一个3位整数,它的每个位上的数字的3次幂之和等于它本身,例如: 13+53+33=153。本程序采用穷举法,逐个测试三位整数(100~999)是不是水仙花数。程序代码如下: public class Example3_9 { public static void main(String[] args) { System.out.print("水仙花数有:"); int i,j,k; int num1,num2; for (i=1;i<=9;i++) for (j=0;j<=9;j++) for (k=0;k<=9;k++){ num1=(int)(Math.pow(i,3)+Math.pow(j,3)+Math.pow(k,3)); num2=i*100+j*10+k; if (num1==num2) System.out.printf("%7d",num2); } } } 运行该程序,输出结果如下: 水仙花数有: 153370371407 【例310】输出斐波那契数列的前20项。 斐波那契数列指的是这样一个数列: 1,1,2,3,5,8,13,21,34,55,89,…,这个数列从第3项开始,每一项都等于前两项之和。基于这一描述,可以用循环迭代的方法,每次循环中,用序列中前面两个值计算当前值,输出该序列,程序代码如下: public class Example3_10 { public static void main(String[] args) { int a=1; int b=1; int t; System.out.printf("%6d%6d",a,b); for (int i=2;i<=20;i++){ t=a+b; System.out.printf("%6d",t); a=b; b=t; } } } 对于斐波那契数列,还可以采用如下的递归形式进行定义。 f(n)=1,n=1或n=2 f(n-1)+f(n-2),n>2 Java允许递归调用,即一个方法直接或间接地调用自己。采用递归编程,程序代码如下。 public class FibonacciRecursion { public static int fib(int n){ if (n<=0) return -1; else if (n<=2) return 1; else return fib(n-1)+fib(n-2); } public static void main(String [] args){ int i; System.out.println("斐波那契数列:"); for (i=1;i<=20;i++) System.out.printf("%6d",fib(i)); } } 在编程时,虽然递归算法使程序看来非常简洁,但是递归程序需要频繁地进行方法调用,运行效率比较低,计算时间比较长。 习题 1. 阅读以下Java语言程序的片段,写出程序的输出结果。 int t = 198; do{ System.out.println(t); t = t / 2; } while(t > 0); System.out.println("t="+t); 2. 阅读以下Java语言程序的片段,写出程序的输出结果。 for (int i = 1; i < 4; i++) { for (int j = 1; j < 4; j++) { if (i * j > 2) continue; System.out.println(i * j); } System.out.println(i); } 3. 把下面的代码转换成switch语句。 if (a == 1) x += 5; else if (a == 2) x += 10; else if (a == 3) x += 16; else if (a == 4) x += 34; else x += 100; 4. while循环和dowhile循环的区别是什么?将以下循环转换为dowhile循环执行。 int sum = 0; int number = input.nextInt(); while (number != 0) { sum += number; number = input.nextInt(); } 5. 在循环中,break与continue的作用是什么?下面的两程序段哪个存在问题?为什么? int x=200;int x=200; while(true){while(true){ if(x<9)if(x<0) break;continue; x=x-8;x=x-8; }} System.out.println("x is"+x);System.out.println("x is"+x); 6. Math.random()可以产生0~1随机的小数,通过运算可以把该数转换成0~100的随机整数。先编程产生0~100的随机整数x,然后让用户输入对该数的猜测值guess。如果guess等于x,则显示“猜测正确”,否则显示guess值是大了还是小了,提示用户继续猜测,直到猜测正确为止。 7. 编写一个程序,从键盘读取一个整数,编写程序显示其所有最小的因子。例如,如果输入整数为48,则输出如下: 2、2、2、2、3。 8. 从键盘输入一个整数,判断并显示该数是不是素数。 9. 回文素数是指一个整数n从左向右和从右向左读其结果值相同且是素数。编程求出1000~100000的回文素数。 10. 输入三角形的三条边a、b、c,判断并输出能否构成三角形(任意两条边的和大于第三条边),如果能构成三角形,输出是什么类型的三角形(等边、等腰、直角)。 11. 编写一个程序,提示用户输入一个1~20的数,并显示一个金字塔,示例运行结果如下所示。 "C:\\Program Files\\Java\\jdk1.8.0_281\\bin\\java.exe"… Please input n:6 1 212 32123 4321234 543212345 65432123456 12. 编写一个程序,提示用户输入'A'~'Z'中的一个字符,并显示一个金字塔。例如,输入H,则显示由A到H形成的金字塔,示例如下。 "C:\\Program Files\\Java\\jdk1.8.0_281\\bin\\java.exe"… 请输入字符: H A BAB CBABC DCBABCD EDCBABCDE FEDCBABCDEF GFEDCBABCDEFG HGFEDCBABCDEFGH 13. 给出递归计算n!的方法factorial(int n),并编写主程序调用该递归方法。 14. 用Java语言编写程序,计算e=1+1/1!+1/2!+…+1/n!。要求e值精确到小数点后第6位。试比较n!计算调用递归方法与不用递归方法的运行时间差异。 15. 完全数又称完美数,即如果一个正整数等于它的所有正因数的和,则它被称为完全数。例如,6是第一个完全数,因为6=3+2+1,下一个完全数是28(28=14+7+4+2+1)。编写一个程序来找到1~10000所有的完全数。 16. 在数学上可以用下面的公式计算π。 π=41-13+15-17+…+12i-1-12i+1 编程显示i=100、1000、10000、100000时的π值。 17. 编程统计全班的90~100分、80~89分、70~79分、60~69分、不及格的人数。学生成绩由键盘输入,输入-1表示结束。