Java编程入门(3.3):while与do..while语句

Java支持简单语句和复合语句。比如赋值和子函数调用这样的简单语句是构建程序的基础模块。像while循环和if语句这样的复合语句用来将简单语句组织成复杂的结构。这种结构被称作控制结构,用来控制语句的执行顺序。下面的五个章节会讨论Java中的控制结构,从本节的while语句和do…while语句开始介绍。与此同时,我们会给出每种控制结构的示例程序并将其应用到前一节算法设计例子中。

3.3.1  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

3.3.2  do..while语句

有时候,在循环结尾测试循环条件会比像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 );
}

程序的功能没有任何变化。

3.3.3  break和continue语句

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 - 唐尤华
译文链接: http://www.importnew.com/19967.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 唐尤华

我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其实我是一个程序员。(新浪微博:@唐尤华

查看唐尤华的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部