Java支持简单语句和复合语句。比如赋值和子函数调用这样的简单语句是构建程序的基础模块。像while循环和if语句这样的复合语句用来将简单语句组织成复杂的结构。这种结构被称作控制结构,用来控制语句的执行顺序。下面的五个章节会讨论Java中的控制结构,从本节的while语句和do…while语句开始介绍。与此同时,我们会给出每种控制结构的示例程序并将其应用到 前一节 算法设计例子中。
在之前的 3.1节 中已经介绍了while循环,结构如下:
while ( boolean-expression ) statement
while语句是一个代码块,由一组语句组合在一起并包含在一对括号中。结构中的statement被称作循环体。当boolean-expression为true时,会循环执行循环体中的语句。这个布尔表达式被称作循环条件,执行简单的测试。有几点需要澄清。在循环体一次都没有执行前,循环条件为false时会发生什么?这种情况下,循环体永远不会执行。while循环的主体可能会任性任意多次,当然也可能是0次。当执行到循环体 中间 某条语句时循环条件由true变为false时会怎么样?会马上结束循环吗?不会,因为计算机会一直执行到循环结束为止。只有当重新跳转到循环开始并且测试循环条件时,循环才会结束。
让我们来看看使用while循环解决问题的一个典型示例:用户输入一组正整数,算出它们的平均值。平均值的算法,是所有整数的和除以整数个数。程序会要求用户每次输入一个整数,记录输入整数的个数,然后计算所有已录入数字的和。下面是这段程序的伪代码:
Let sum = 0 // 保存用户输入所有整数的和 Let count = 0 // 保存用户输入的整数个数 当还有整数要处理时: 读入一个整数 加到sum变量 为count增加计数 用sum除以count得到平均值 输出平均值
如何知道还有待输入的整数呢?一种典型的解决方法是,让用户在所有数据录入完毕时输入0。这种方法在所有数据都是正整数的情况下有效,这时0不是一个合法的数值。0本身不作为计算平均数集合中的数据,仅仅作为真实数据录入结束的标志。这种方法一些场合下被称为使用哨兵值。所以,现在while循环的测试条件变为“当输入的整数非0”。但是这里有另外一个问题!第一次判断循环条件时,循环体尚未执行,这时还没有读取任何数值。也就是说,还没有“输入的整数”。这时判断输入整数是否为0是没有意义的。因此,我们需要在循环执行 前 做一些处理,把必要的信息准备好。这里,我们只要在循环的前面读取第一个整数就可以了。下面是修改后的算法:
Let sum = 0 Let count = 0 读取一个整数 整数非0时: 将整数加到sum 增加count计数 读取一个整数 用sum除以count得到平均值 输出平均值
请注意,这里我重新调整了循环体。由于在循环开始前就读取了整数,在循环一开始就需要处理读到的整数。在循环的最后,计算机会读取一个新的整数。计算机会跳转到循环开始的地方,用新读取的值测试循环条件。注意:当计算机读取最后的哨兵值以后,会直接结束循环而不对其进行处理。既不会累计到sum中,也不会算到count里。这就是算法的工作机制。哨兵值不作为数据的一部分。在最初的算法中,不做循环前的准备会累计所有的值包括哨兵值,所以结果是错的。(哨兵值为0,因此sum的结果是正确的,但是count会多算了1个。这种常见的错误被称作“差一错误”。循环中进行计数会比看上去难得多!)
可以很容易地把算法转换成一个完整的程序。注意,在程序中不能用“average = sum/count;”这样的语句计算平均值。因为sum和count都是int类型,sum/count的结果是整数,而平均值应该是实数。我们之前已经遇到过了这个问题:把其中一个int值转换为double类型,强制计算机把商值作为实数计算。具体的做法,将其中一个变量强制类型转换为double。“(double)sum”将sum值转为实数,因此计算平均数可以改成“average = ((double)sum) / count;”。还有一种方法,把声明变量时就声明为double类型。
程序还存在另一个问题:用户一开始输入0时,程序不会再处理其它输入。这种用例可以来测试循环结束时count是否还等于0。看起来这是细节问题,但是仔细的程序员应该考虑所有的情况。
下面是程序的完整代码:
/** * 这个程序会读取一个用户输入的正整数序列, * 输出所有整数的平均值。 * 程序会提示用户每次输入一个整数。 * 当用户输入0时表示输入结束。 * (0不会作为数据加入平均值计算) * 该程序不会检查用户输入是否为正整数, * 即使输入负数也会加入计算。 */ public class ComputeAverage { public static void main(String[] args) { int inputNumber; // 用户输入 int sum; // 正整数和 int count; // 输入的正整数个数 double average; // 平均数 /* 初始化sum和count变量*/ sum = 0; count = 0; /* 读取并处理用户输入 */ System.out.print("Enter your first positive integer: "); inputNumber = TextIO.getlnInt(); while (inputNumber != 0) { sum += inputNumber; // 把inputNumber累加到sum count++; // count加1 System.out.print("Enter your next positive integer, or 0 to end: "); inputNumber = TextIO.getlnInt(); } /* 打印计算结果 */ if (count == 0) { System.out.println("You didn't enter any data!"); } else { average = ((double)sum) / count; System.out.println(); System.out.println("You entered " + count + " positive integers."); System.out.printf("Their average is %1.3f.n", average); } } // end main() } // end class ComputeAverage
有时候,在循环结尾测试循环条件会比像while循环那样开头判断更加方便。do..while语句与while循环非常类似,区别在于“while”及循环体被移到了结尾,在循环的开头添加了“do”。do..while语句的结构如下:
do statement while ( boolean-expression );
通常情况下,statement可能是一个代码块:
do { statements } while ( boolean-expression );
注意:在do..while的结尾有一个分号“;”。这个分号也是语句的一部分,与赋值语句或声明语句结尾的分号一样。漏写分号会造成语法错误。(通常, 每个 Java语句都会以分号或右花括号“}”结尾)
执行do循环时,计算机会首先执行循环体中的语句,然后判断循环条件。如果循环条件表达式为true,计算机会回到do循环开头继续执行;如果为false,计算机会终止循环继续执行程序的其它部分。由于只有在循环末尾才会进行条件判断,do循环至少会执行一次循环体。
例如,下面的伪代码是一个游戏程序。使用do循环比起while循环更有意义,至少能玩一盘游戏。程序的开始版本,循环判断条件没有意义:
do { 玩一盘游戏 询问是否想要再玩一盘 读取反馈 } while ( 用户的反馈是 yes );
把上面的伪代码转成Java。开始不去讨论游戏的细节,让我们定义一个Checkers类,其中包含了一个static成员函数叫做playGame(),和程序的使用者玩跳棋游戏。伪代码“玩一盘游戏”被转换为调用“Checkers.playGame();”。我们需要一个变量存储用户的反馈。TextIO类通过一个boolean变量存储 yes/no 的回复结果。“Yes”表示true,“no”表示false。因此算法的代码会变成:
boolean wantsToContinue; // True表示用户想要再玩一盘 do { Checkers.playGame(); System.out.print("Do you want to play again? "); wantsToContinue = TextIO.getlnBoolean(); } while (wantsToContinue == true);
当boolean变量的值变为false,表示循环应当结束。在程序的一个地方赋值,在另一个地方作为判断条件——当boolean变量这样使用时,被称为标志(flag)或标志变量(表示信号标志)。
顺便说一下,程序员通常会鄙视这样的写法“while (wantsToContinue == true)”。这种方式过于教条,可以用“while (wantsToContinue)”代表同样的含义。类似的,还有“flag == false”这样的写法,flag是一个boolean变量。“flag == false”与“!flag”完全等价,这里的感叹号!表示对boolean值进行取反操作。所以,可以用“while (!flag)”取代“while (flag == false)”,用“if (!flag)”取代“if (flag == false)”。
尽管do..while语句有时候比while语句更加方便,但是两种循环并没有让语言更强大。任何可以用do..while循环解决的问题都可以用while完成,反之亦然。事实上doSomething可以代码任何一个代码块:
do { doSomething } while ( boolean-expression );
与下面的代码功能一致:
doSomething while ( boolean-expression ) { doSomething }
类似的,
while ( boolean-expression ) { doSomething }
可以替换为:
if ( boolean-expression ) { do { doSomething } while ( boolean-expression ); }
程序的功能没有任何变化。
while循环与do..while循环会在程序的开始或结尾测试循环条件。有时候,在循环体中间或者几个不同的地方测试条件会更加合理。Java提供了在循环体中跳出循环的通用方法,叫做break语句,形式如下:
break;
当计算机在循环体重执行break语句时,会立刻跳出循环。接下来会继续执行循环后面的语句。考虑下面的示例:
while (true) { // 看起来会一直循环下去! System.out.print("Enter a positive number: "); N = TextIO.getlnInt(); if (N > 0) // 输入OK,跳出循环 break; System.out.println("Your answer must be > 0."); } // continue here after break 在break执行后,从这里开始继续执行
如果用户输入的数值大于0,会执行break语句跳出循环。否则,计算机会输出“Your answer must be > 0.”然后跳转到循环的开头继续读取其它用户输入的值。
循环的第一行,“while (true)”可能会有一点奇怪,但确是合法的。while循环的条件可以是任意boolean类型的表达式。计算机会判断检查式的值看是true还是false。boolean值“true”也是一个boolean表达式,值为true。所以,“while (true)”表示无限循环,可以通过break语句终止无限循环。
break语句会立刻终止包含了该语句的循环。Java支持循环嵌套,即一个循环中包含另一个循环。如果在嵌套的循环内调用break语句,只会跳出该层循环,而非跳出外层循环。还有一种跳转叫做标签中断(labeled break),可以指定希望跳出的循环。这种用法并不常见,这里我会快速带过。标签(Label)的工作方式如下:可以在任何循环前面加上标签。标签由一个简单标识符带一个冒号组成。例如,带label的while循环看起来像这样“mainloop: while…”,在循环内部,你可以使用带标签的跳转语句,比如“break mainloop;”来跳转带标签的循环。例如,下面这段代码检查两个字符串,s1和s2,有一个共同的字符。如果找到共同字符,标志变量nothingInCommon会置为false,通过标签中断结束处理:
boolean nothingInCommon; nothingInCommon = true; // 假设s1和s2没有共同字符 int i,j; // 在s1和s2中用来循环的迭代变量 i = 0; bigloop: while (i < s1.length()) { j = 0; while (j < s2.length()) { if (s1.charAt(i) == s2.charAt(j)) { // s1和s2有共同的字符 nothingInCommon = false; break bigloop; // 跳出所有2层循环 } j++; // 继续处理s2中的下一个字符 } i++; // 继续处理s1中的下一个字符 }
continue语句与break类似,但是很少使用。continue语句告诉计算机跳过本次循环剩余语句的执行。然而,与跳出循环不同,continue会跳转到循环开始继续下一次迭代(包括判断循环变量的值是否需要继续迭代)。与break语句类似,在嵌套循环中执行continue语句时,会直接转到包含该语句的循环开始;“标签继续(labeled continue)“会转到指定的循环继续执行。
break和continue语句可以用在while循环与do..while循环中。它们也可以在 接下来的章节 中介绍的循环中使用。在 3.6节 ,我们会看到在switch语句中使用break。break还可以在if语句中使用,前提是if语句嵌套在循环火种switch语句中。在这种情况下,break并 不 意味着会跳出if语句,而是跳出包含着if语句的循环或switch语句。在if语句中使用continue也是类似的用法。
原文链接: math.hws.edu 翻译:ImportNew.com -唐尤华
译文链接:[]