走进控制语句
通过本章学习,你能够学会:
1.举例说明条件表达式和逻辑表达式的特点以及如何应用。
2.举例解释比较基本变量和比较字符串的不同。
3.应用if-else编写代码解决实际问题。
4.应用switch编写代码解决实际问题。
5.应用3种循环语句编写代码解决实际问题。
一般的语句,如赋值、输入、输出、方法调用等,都是按先后次序执行的。控制语句用来控制代码运行的流程,从而改变语句的执行次序。Java中的控制语句主要包括分支语句if-else,多项选择或开关语句switch,3种循环语句while、do-while以及for,还有continue和break。正确和灵活应用控制语句能够使我们模拟现实世界甚至超现实的复杂功能和处理操作。这一章将详细讨论这些控制语句以及它们的各种应用。
4.1 条件表达式
控制语句需要应用条件表达式来判断某个条件的成立与否,来决定程序运行的次序或者流程。这些条件包括数据的比较结果、逻辑关系的判断结果等。对数据的比较,包括对基本数据类型和字符串类型的比较,我们称之为关系表达式,因为比较的结果是“真”或是“假”,即true或false。逻辑关系包括“和”的关系、“与”的关系,以及“非”的关系等。判断逻辑关系的表达式我们称之为逻辑表达式,其判断结果是一个布尔值,“真”或者“假”,即true或false。表达式中既包括关系表达式也有逻辑表达式,被称为复合关系表达式。以上这些表达式统称为条件表达式。许多API类的方法,例如Scanner的hasInt(),也返回一个布尔值,则是条件表达式的特例。
在这一章节首先介绍关系表达式以及怎样使用关系表达式对数据型变量和字符串进行比较,然后讨论这些关系表达式在各种控制语句中的应用,并用实际例子来帮助你理解和掌握它们的编程技巧。我们将在本章稍后的小节逐步深入地讨论逻辑表达式、复合表达式、多分支控制语句、各种循环语句,以及嵌套循环语句和它们的应用实例。
4.1.1 关系表达式
关系表达式是条件表达式的一种。它是由关系操作符和操作数构成的。因为关系操作符要求有两个操作数,我们称这种操作符为二元操作符。你可以回顾一下,还有哪些我们讨论过的操作符是二元操作符呢?
表4.1中列出了Java的关系操作符以及每个操作符的含义。
接下来讨论怎样比较基本型数据。
4.1.2 比较基本型数据
Java的8种基本型数据都可以进行关系比较。来看下面的一些例子
在最后的例子中相比较的是字符的Unicode代码。字符'b'的代码是98,所以结果为假,即false。
3W 关系表达式使用关系操作符比较两个操作数,其结果为布尔值。使用关系表达式的目的是控制程序的执行次序,即流程。
注意,在比较两个浮点变量的布尔关系时,一定要考虑浮点变量的有效值范围。如果两个数值相等的操作数的数值超出有效值范围,一般超过两位以上,即单精度浮点数超过9位,双精度浮点数超过18位时,则会产生错误的比较结果。Java编译器在对超值的浮点数编译时并不产生编译错误信息;而JVM也不会产生任何错误或异常处理。例如下列两个超值的单精度浮点数的比较:
System.out.println("123.456789f == 123.4567892f: " + (123.456789f == 123.4567892f));运行结果为真,即true。
实际上,Java编译器在编译超值的单精度浮点数的布尔关系表达式时,对其第9位“有效数”进行“3舍4入”的操作。例如:
System.out.println("123.45678f == 123.456783f: " + (123.45678f == 123.456783f));
的运行结果为真,即true;而
System.out.println("123.45678f == 123.456784f: " + (123.45678f == 123.456784f));
的运行结果为假,即false。
Java在处理双精度浮点数值布尔关系时,则对其第18位“有效数”采取“3舍4入”的原则进行比较。例如:
的运行结果为真。而
的运行结果则为假。
对整数型操作数,包括byte、char、short、int和long,Java编译器将对超值的数值定义产生编译错误。所以在关系比较时,不存在类似浮点型操作数的问题。
更多信息 Java语言中规定,不可以像C或C++那样,用整数值0代表假;而用除0之外的任何整数值代表真。Java中只允许用true或false代表真或假。
4.1.3 比较字符串
确切地说,在Java语言中,对字符串的比较有两种:对字符串内容的比较,以及对字符串地址的比较。4.1.2节讨论过的比较操作符,如==和!=等,对字符串的地址进行比较,而不是对内容进行比较。
在Java中,字符串实际上是一种系统预设的API类。使用字符串类的方法对字符号内容进行关系比较,产生布尔结果。表4.2列出了对字符串进行比较的两个常用方法。
接下来讨论一些具体的例子:
将显示false。这是因为字符串str1和str2的内容虽然相等,但有一个大小写字母的不同,所以这个比较结果为假。而System.out.println(str1.equlasIgnoreCase(str2)); //输出 true将显示true。这是因为在这个比较中,大小写字母被认为是相同的字符。
注意,在对字符串的关系比较中,使用关系运算符,例如==、!=等,所比较的是两个字符串的地址,而不是内容。即:
str1 == str2
是对str1和str2的地址的比较,而不是对内容的比较。接着上面的例子:
第一行将打印false,因为它比较的是地址。字符串str1和str2是两个不同的字符串对象引用,因而它们有不同的存储地址,比较的结果为假;第二行显示true,因为在忽略大小写字母后,它们的内容相同,比较结果为真。
Java初学者容易混淆,对字符串内容的比较和字符串地址的比较。
后续章节将进一步讨论字符串对象引用、字符串对象、字符串的比较,以及和字符串内容和地址有关的问题。
下面的代码是测试和理解字符串内容比较的例子。这个例子询问用户输入两个字符串,然后显示比较的结果,以便加深理解和掌握对字符串内容的比较操作。虽然这个程序只运行一次就结束,但在后续内容中讨论过分支语句和循环语句后,就可以修改这个程序,使它能够循环多次运行。
以下是一个典型运行结果。
4.2 逻辑表达式和应用
逻辑表达式是条件表达式的又一种形式。如果说关系表达式是判断两个数据的大小、相等与否的布尔关系,逻辑表达式则用来表达两个或多个关系表达式之间的“与”“或”“非”的布尔关系,或称逻辑关系。所以,用逻辑表达式可以构成复杂多样的复合表达式,应用于对代码的执行次序和流程控制。
4.2.1 逻辑表
表4.3列出了Java的3种逻辑运算真值表。Java用“&&”表示逻辑与,用“||”表示逻辑或,用“!”表示逻辑非。
可以看到,在逻辑与&&的运算中,只有两个操作数的值都为真时,其运算结果才为真。在逻辑或||的运算中,只要有一个操作数的值为真,其运算结果便为真。而逻辑非!只有一个操作数,实际上是对这个操作数求反。
Java应用短路运算(short-circuit)对逻辑与以及逻辑或进行操作。
即对逻辑与x && y运算时,如果第一个操作数x的值是假,则不再对第二个操作数y的值进行判断,结果已经为假;在对逻辑或x || y运算时,如果第一个操作数x的值为真,则不再对第二个操作数y的值进行判断,结果已经为真。而其他编程语言则使用全程运算(full evaluation),即对所有操作数进行判断后,才决定逻辑与或者逻辑或的结果。这种全程运算在处理复杂的复合表达式时,显然浪费时间和空间。
4.2.2 复合表达式及运算次序
运用逻辑运算符和关系表达式可以构成复杂的复合表达式,例如:a < b + 1 || a >= c – 1&& !(b-d)
它的运算次序是如何进行的呢?根据Java规则,复合表达式的运算符优先级列表如表4.4所示。
注意,如果运算优先级相同,则按从左至右次序进行。
回到上面的例子,根据运算优先级,其运算次序是:
先做括号里的b – d;
求其逻辑反;
进行b + 1以及c – 1数学运算;
进行关系运算;
进行逻辑与运算;
再进行逻辑或运算。
4.2.3 你的程序逻辑清楚吗
“假如你的期中和期末考试成绩都是90分或90分以上,就可以申请
奖学金,否则不够资格。”这是一个典型逻辑与的因果关系。可以将它表述为如下复合表达式:
(midtermScore >= 90) && ( finalScore >= 90)
其中,midtermScore和finalScore均为整型变量。
除逻辑非“!”具有更高运算优先级外,逻辑与和或的运算级均低于
关系运算和数学运算,所以上面的复合表达式可以不要括号,即:
midtermScore >= 90 && finalScore >= 90
可以将这个表达式应用在如下if-else语句中(4.2.4节讨论):
设想一下,如果不用逻辑表达式,将如何完成这个运算。
“如果你的期中或者期末考试成绩高于90分以上,就可以得到2500元的奖学金。”这是一个典型的逻辑或的因果关系,可以将其表述在以下if语句中:
“如果你这门课的成绩不是A,你就得不到奖学金,否则就可以申请它。”这一表述可以用以下if-else语句中的逻辑非来表达:
其中,myGrade是储存这个学生成绩的字符串变量。
这个逻辑关系也可用如下关系表达式来表示:myGrade.compareIngoreCase("A") != true试想是否可以用其他方式来表达这个逻辑关系。
假设a、b、c为整型变量。表4.5中列出了更多逻辑表达式的应用例子。
更多信息 Java还提供控制用的按位逻辑运算与“&”、或“|”、异或“^”,以及非“~”。即如果两个输入位都是1,“&”产生一个输出位1,否则输出位为0。如果输入位中有一个为1,“|”产生输出位1,否则为0。如果输入位都是1,或者只有一个是1,另外一个是0,“^”产生一个输出位1,否则为0。如果一个输入位为1,“~”产生一个输出位0,否则为1。按位进行的逻辑运算超出本书范围,详细讨论见有关文献。
下面是一个用来测试和理解逻辑表达式程序例子的部分代码:
//完整程序以及以上讨论过的程序在本书配套资源目录Ch4中,名为TestLogicApp.java
//test out the basics
System.out.print("Please enter boolean value of x: ");
x = sc.nextBoolean();
System.out.print("Please enter boolean value of y: ");
y = sc.nextBoolean();
System.out.print("Please enter boolean value of z: ");
z = sc.nextBoolean();
System.out.println("x && y: " + (x && y));
System.out.println("x || y: " + (x || y));
System.out.println("!x: " + !x);
System.out.println("x != y && x != z: " + (x != y && x != z));System.out.println("x == y || x == z: " + (x == y || x == z));
4.3 简单if语句
“我的存款现在是10万元。如果存款额达到15万元,我一定买一辆大众。”这就是if语句。可以看出,这里只说明了买一辆大众车的条件,但没有指明如果存款没有达到15万元将怎样。也许什么也不做吧。所以if语句是单项决定语句,或单分支语句。它只处理如果条件成立将做什么。
“如果周末下雨,我们就在家搓麻,否则我们就去钓鱼。”这便是ifelse语句。与上面的例子相比,这里把条件成立或不成立时要做的决定都包括了进来。所以if-else语句是双项决定语句,或称双分支语句。
最常用也是最基本的控制运行流程的语句非if和if-else语句莫属。这两个语句在任何计算机高级语言中都存在,可见其应用的广泛性和重要性。我们在本章先讨论它们的简单形式和应用。在以后的章节中将进一步讨论更为复杂的编写方式。
首先讨论单分支if语句。其语法格式为:
或
这里:
□ conditional_expression——指任何一个条件表达式。注意,这个表达式必须包括在括号中。□ statement——指任何一行程序语句。
□ statements——指任何多于一行的程序语句,或程序块。如果是程序块,必须使用花括号。
含义为:如果conditional_expression为真,则执行statement,或者statements,否则不执行这个或者这些语句。可以看到,在布尔表达式为真的情况下,执行的只是一行语句,那么这行语句的花括号可以省略。
图4.1列出了if语句的典型代码和它的流程图。
3W Uniform Modeling Language(UML),是用来表示类的结构、类之间的关系,以及执行流程的图示化语言。应用它的目的是更清楚地表达设计结构和执行流程。本书应用UML的目的是帮助你更好地理解表达式的逻辑关系和执行结果。
下面讨论一些具体例子。例子之一:if语句中只有一行执行语句。
在这个例子中,如果变量total的值大于或等于150,discountRate将被赋予新的值0.2,然后执行输出语句;否则,如果total的值小于150,则跳过这一行,执行输出语句,这时的discountRate仍然是0.1。
例子之二:if语句中有多行执行语句。
在这个例子中,如果变量total的值大于或等于150,则执行花括号里的程序块,即discountRate被赋予新的值0.2,变量bulkOrder加1,然后执行输出语句;否则,如果total的值小于150,则跳过花括号,而去执行输出语句,这时的discountRate仍然是0.1,bulkOrder亦不变。
注意if语句的编写格式。适当使用空格会增强程序的可读性。如果把上面的例子写成如下形式:
//不提倡的编写格式
if (total >= 150)
discountRate = 0.2;
System.out.println("discount is " + total * discountRate);
或者:
//这样的写法一定要不得
if (total >= 150)
{ discountRate = 0.2;bulkOrder ++;
}
System.out.println("Discount is " + total * discountRate);
System.out.println("Number of bulk order is " + bulkOrder);
不言而喻,这将大大削弱程序的可读性,使读者很难理解你的程序。
3W if语句是单分支语句,用来判断条件成立时要执行的操作。if语句使用条件表达式作为条件判断。这个表达式必须使用括号。程序体多于一行时必须使用花括号。
4.4 简单if-else语句
if语句只是进行单分支判断,并没有提供如果布尔表达式条件不成立或为假时,应该执行哪行或者哪些语句。而if-else则提供了双分支执行功能,使我们在编写程序时更加方便和有效。if-else双分支的语法格式如下:
或者:
同单分支if语句一样,如果布尔表达式为真或者为假时所执行的语句多于一行时,必须用花括号括起来。这里,else表示如果布尔表达式结果为假,则执行else所指定的语句行。图4.2列出了if-else语句的典型代码和它的流程图。
下面通过例子来学习和掌握if-else语句的编写方法。
例子之一:简单if-else语句。
与if语句例子相比,if-else多了一次进行决策的机会,即如果表达式total小于150时,我们有机会可以方便地将discountRate赋予另外一个值,即0.15。否则,我们将需要两个if语句来完成这样的操作,例如:
再讨论另外一个例子。
例子之二:if-else语句中有多行程序代码。
因为在这个例子中if和else的执行程序体都多于一行,所以必须使用花括号。
3W if-else语句是双分支语句,用来判断条件成立或者不成立两种情况下要执行的操作。if-else语句使用条件表达式作为条件判断,这个表达式必须使用括号。程序体多于一行时必须使用花括号。
4.5 嵌套if-else语句
回顾一下前面讨论过的简单if-else语句,它只能产生两种情况的分支。使用嵌套if-else语句,则可产生多种分支和判断。使用嵌套if-else再加复合表达式,则会使我们的编程世界更加丰富多彩,能够处理更加综合复杂的逻辑问题。
4.5.1 用多种格式编写嵌套if-else根据处理的问题,可以有多种格式。以下介绍3种典型的基本格式,你可以举一反三,根据情况,灵活运用。
格式1:
这种形式的嵌套可以产生多项选择性的条件判断,即多项开关。例如:
格式2:
这种形式在if中,或者在else中,又嵌套了另外一个或多个if,或if-else,产生了一层套一层的复杂判断。注意在应用这种格式时,每一个else都必须有一个if与之对应,否则就会产生语法错误。以下例子为这种格式的正确写法:
格式3:
可以看出,这种形式是格式1和格式2的综合。
4.5.2 应用实例
应用嵌套if-else的经典编程例子是根据成绩给学生分配字母学分。
例如:
解决这个问题的主要代码如下:
给出一个挑战性问题:是否可以用不同的嵌套if-else来解决以上问题?
4.6 条件运算符?:
Java提供条件运算符? :,可以用来代替if-else语句。例如:
discountRate = total >= 150 ? 0.2 : 0.15;
它相当于:
其语法格式为:
conditionalExpression ? expression1 : expression2
其中:
□ conditionalExpression——任何布尔表达式。
□ expression1和expression2——常量、变量或者表达式。
含义为:如果conditionalExpression为真,则执行expression1,否则执行expression2。因为条件运算具有3个运算符,它也称为三元操作。
条件运算符经常用在快捷判断的情况下,使代码编写简洁,例如:
System.out.print(grade >= 60 ?"passed" : "failed");
试想怎样把它改写为if-else。注意,不当使用条件运算符会降低代码的可读性。
4.7 多项选择——switch语句
尽管嵌套if-else可以构成多项选择功能,但Java提供了专门的语句switch来完成这一操作,使得多项选择更具有针对性。
4.7.1 典型switch语句格式
多项选择switch语句可以有多种格式,典型的语法格式如下:
其中,integralExpression和integralValue_n必须是整数型常量、变量或表达式,例如byte、short、int或char,但不允许是long。default是可选项。其执行流程如下。
如果integralExpression等于case中的某个integralValue,则执行这个选项中的所有语句,直到break语句,控制跳出switch语句。
如果integralExpression不等于任何case中的integralValue,则执行default中的所有语句,然后switch语句结束。
如果没有default选项,integralExpression不等于任何case中的integralValue,则不执行任何case选项,控制跳出switch语句。
控制跳出switch语句后,执行其后的第一行语句。
例如:
注意,如果case中没有break语句,将会继续执行下一个case选项。
有经验的编程人员可以善加利用这一特性,构成更加巧妙有效的代码。例如:
在这个例子中,有目的地利用了switch语句中没有break将自动执行下一个case的特点,如果dayOfWeek的值是2~6的任何一个数(代表星期一到星期五),字符串变量day将被赋予“工作日”;如果dayOfWeek的值是1和7(代表星期天和星期六),day将得到字符串值“周末休息”。
如果dayOfWeek的值不在以上范围,控制则跳出这个switch语句,从switch之后的第一行语句开始执行。也可以在这个switch中加上default选项,用来显示可能的出错信息。这个练习留给你在本章的练习题中尝试。
图4.3显示了switch语句的典型代码以及流程图。
更多信息 switch中case的值只能是一个确定的整数值,而不像在其他语言,如Pascal中,可以是一个整数范围。
4.7.2 应用实例
利用switch语句,也可以编写在4.5.2节中用嵌套if-else解决给学生分配字母学分的问题。不难看出,字母学分共有5种选项。假设学生的成绩范围是0~100,如果对成绩除以10,即得到如下分配方案:
将其他情况考虑为default选项,这个switch语句可以编写如下:
4.7.3 JDK14新增的switch-case语句及其应用
在JDK12、JDK13(预览讨论版本)以及JDK14(确定版本),新增了一个增强性的switch-case语句。与传统的Java语法不同,这个语句吸收了其他语言的语法,一个典型应用如下:
如果day所代表的case等于“M”“W”以及“F”,result=“MWF”;如果case等于“T”,“TH”,以及“S”,result=“TTS”;如果day为空,result="Please insert a valid day.";如果day不属于case的不为空的其他字符串,则result = "Looks like a Sunday. "。
4.8 你的程序需要继续运行吗——循环语句
很难想象一个没有循环语句的计算机语言的存在。循环语句就像一幢摩天大厦里的电梯一样重要。没有它,怎样有效地到达顶点呢?“请不要停止程序的运行。我需要重复运行这个程序。我有各种数据,需要知道它们各自的执行结果”。这是我们在解决问题时经常遇到的。解决方式:加入循环语句。从上面讨论过的例子中可以看出,那些程序只能执行一次。我们需要循环语句,使程序的某个程序段或者整个程序能够重复运行,直到用户输入某个键或某个条件满足为止。
Java提供3种循环语句,即while循环、do-while循环以及for循环。
本节首先介绍Java语句中的基本循环语句——while循环语句及其4种编程方式。它是其他循环语句的基础。理解和掌握了while循环语句,学习其他循环语句就不难了。然后讨论其他两种循环语句do-while和for,以及嵌套循环的概念、编写和应用实例。
4.8.1 走进while循环
while循环语句的语法格式如下:
如同if语句,while循环语句首先判断条件表达式是否成立。如果为真,则执行在花括号中的语句,或称为循环体;如果为假,这个循环语句结束,控制跳出循环,执行循环体之外的语句。如果循环体只有一行语句时,花括号可以省略。
图4.4列出了while循环语句的典型代码以及流程图。我们称x为循环控制变量,它在循环语句前必须定义和赋值;称表达式x < 10为循环的条件,必须在关键字while的括号中;称++x为循环变量的更新,一般在循环体中。这3个单元称为循环三要素,它们规范了循环的行为。
在实际应用中,while循环可以有以下4种不同的编写方式来控制对循环体的重复执行:
□ 用计数器控制。
□ 由用户输入控制。
□ 用标记值控制。
□ 用改变循环条件控制。
下面用实例详细讨论while循环的4种编程方式。
例子之一:用计数器控制while循环。这种编程方式是事先已知循环的次数,例如计算累加、累减,或固定循环次数的处理和操作。以下程序利用while循环计算1~5的和。
循环结束后,sum的结果为15。这个例子的具体循环过程可由表4.6表示。
3W while循环语句用来重复执行一段程序体。它由循环控制变量、循环条件,以及循环变量更新三要素构成。程序体多于一行时必须使用花括号。While循环是学习do-while和for循环的基础;学会了while循环,其他循环语句就容易掌握了。
更多信息 循环语句有三要素:循环控制变量、循环条件,以及循环变量更新。循环控制变量必须在循环前定义并赋予初始值;循环条件可以是任何条件表达式,产生布尔值(true或者false),用来控制循环继续与否;循环变量的更新改变条件表达式的值,从而控制循环的行为。
例子之二:由用户输入控制的while循环。这也是一种常见的控制while循环的编程方式,如提示用户是否继续“Continue (y/n)?”等。这种方式的特点是循环次数是不固定的,完全由用户的输入值决定是否继续循环。以下是由用户输入是否继续运行“Continue (y/n)?”的例子。
这个例子演示了用户怎样控制循环的执行。因为循环控制变量choice预设的值是“y”,所以程序首先进入循环体运行。用来记录循环次数的变量count加1,并打印这一信息。接下来的输出语句提示用户输入是否继续循环,如果用户输入的是“y”或“Y”,循环将继续,否则,choice的值不是“y”或“Y”,choice.equalsIgnoreCase("y")返回值为假,即false,循环语句停止。循环体外的输出语句将被执行。
值得一提的是,用来判断循环是否继续的表达式在这个例子中不是传统的条件表达式,而是调用字符串的方法equalsIgnoreCase()。由这个方法返回布尔值(true/false)真或假来决定循环体是否继续执行。这是一个经常被使用的例子。
例子之三:用标记值控制的while循环(sentinel-controlledwhile
loop)。这种对循环的控制是利用一个用来作为循环结束的标记值,如-1或-99,或一个不会出现在运算操作的值来控制循环是否继续。如在4.7.2节讨论过的根据考试成绩分配字母学分的例子,学生的成绩不可能是负数,根据这个特点,修改后利用标记值控制while循环的程序如下:
在这种循环格式中,如果用户输入的学生成绩score是一个负数,例如:
score = Integer.parseInt(str); //转换成整数
中,如果转换的值为负数,如–1,则意味着学生成绩已经输入完毕,用这个标记值来要求循环结束。这个格式经常用在对文件的读入和大批数据的统计处理中。其循环次数取决于数据量和标记值的读入。试想,在以前讨论过的例子中哪些可以利用标记来控制while的循环呢?
例子之四:用改变循环条件控制的while循环。程序如下:
这种循环格式是动态控制while循环的典型例子。在程序运行过程中,取决于代码的逻辑关系,由循环体中的if语句对某个条件进行判断,并对其更新,来改变循环的行为。具体实例如下:
以上的分析只是提纲挈领、抛砖引玉,对while循环做一个规范化的总结。如同我们解决的问题多种多样,循环的格式也会五花八门,绝不拘泥于以上的模式。在编程中可灵活巧妙应用,举一反三。在后面的小节介绍continue和break语句后,你可以在循环中更加方便地使用它们,以便有效地解决问题,同时保证代码的可读性。
更多信息 与其他循环语句相比,while循环语句更适用于解决循环体次数不确定的问题。
4.8.2 走进do-while循环
作一个形象的比喻,如果说在while循环中,进入循环体的门卫(循环条件)站在大门口,没有通行证不许入内,但在do-while循环中,这个门卫则站在循环体的后门,即使没有通行证,你也可以进来,至少进来一次。即,在do-while循环中,无论循环的条件是否成立,循环体至少被执行一次。
言归正传,接下来分析一下典型do-while循环的语法格式:
do {
statements;
} while (conditionalExpression);其中,如果statements只有一行语句,花括号可以省略。
conditionalExpression可以是任何布尔表达式。
图4.5列出了do-while循环语句的典型代码以及流程图。
下面是do-while循环的一些具体应用例子。
例子之一:计算从1~5的和。4.8.1节讨论过任何利用while循环计算1~5之和。现在利用do-while循环来执行这一操作:
循环结束后,sum的结果为15。可以比较一下4.8.1节用while循环计算1~5之和的例子,分析它们有什么区别。
例子之二:用do-while执行用户输入“Continue (y/n)?”来控制是否继续循环。4.8.1节讨论过如何利用while循环执行这个操作。以下例子演示如何利用do-while循环解决同样的问题:
例子之三:用do-while循环分配学生的学分成绩。4.8.1节讨论过如何利用while循环执行这个操作。以下例子演示如何利用do-while循环解决同样的问题:
因为在实际应用中,输入或者读入学生成绩都是正数,这样才有意义。这就意味着循环体至少被执行一次,所以用do-while循环语句来解决这个问题比较适当。
可以看到,循环三要素同样适用于do-while语句。3W do-while循环语句和while循环相似,用来执行和控制循环体的各行语句。与while循环不同的是,其循环体至少被执行一次。
更多信息 do-while循环语句更适用于解决循环体至少要执行一次的问题。
4.8.3 走进for循环
for循环与计数器控制的while循环很相似,它将循环三要素以明显集中的方式体现在循环代码的开始。其典型语法格式如下:
显而易见,for循环语句括号中列出了循环三要素,每个要素用分号隔开,即:
□
loopControlVarInitialization——循环变量初始化。
□ loopCondition——继续循环的条件。
□ loopControlVarUpdate——循环变量更新。
在编译过程中,编译器自动以while循环的语法格式,将循环变量初始化放在循环体之外;将循环变量更新放在循环体的最后;而在每次循环开始,对是否继续循环的条件进行判断。但与while循环不同的是,在for循环中,因某种条件,如continue语句迫使控制执行下次循环时,循环变量会被自动更新。这种区别将在介绍continue语句时详细讨论。
更多信息 Java还提供新版本的for循环语句,使用它可以简化对数组元素的运算。第10章中将讨论这种新的for循环语句。
3W for循环语句是计数器控制的while循环的另一种形式。它将循环三要素明显、集中地列表于for循环的括号内,并以分号隔开。
其执行流程图和while循环相同。任何一个for循环都可以while循环来表示。for循环适用于解决循环次数确定的问题。
以下是for循环的一些具体应用例子。
例子之一:计算从1~5的和。
可以看到,for循环在解决这类问题时具有简单、清楚、可读的优点。以上代码也可以改写为从5到1递减的方式求和,例如:
例子之二:假设每个班级的学生数为已知数。用for循环编写分配学生学分成绩的操作:
以上代码的运行结果与利用while和do-while循环相同。
for循环可以有各种不同格式,例如循环控制变量的更新方式(递增或递减,以及更新量)、省略分号中的表达式、在分号中加入其他语句、要素分离等。表4.7列出了for循环语句的各种编写方式和含义。
表4.7 for循环语句的各种编写方式和含义
有些格式,如表4.7中最后3个例子,虽然语法正确,但失去了可读性,不值得推荐。再如,for循环括号中的循环三要素甚至可以完全分离,即:
for (; ; )
仍属合法,这时编译器认为其循环条件永远为真,除非用其他语句,例如在4.9.1节将介绍的break语句,用于终止循环的运行。这种代码写法实际应用价值比较低。
更多信息 据统计,while循环和for循环是最常用的循环语句。
在运行中,如果遇到无限循环的状态,可同时按下Ctrl和C或Ctrl和B组合键,停止循环的继续。
4.8.4 走进嵌套循环
如同在前面讨论过的嵌套if-else语句一样,可以利用嵌套循环语句构成更复杂的代码,解决更复杂的编程问题。顾名思义,嵌套循环就是在循环中加入另外一个或者多个循环语句。例如前面讨论过的用for循环处理学生成绩的例子中,如果询问用户是否继续处理下一个班的成绩,即构成了一个嵌套循环的例子。
例子之一:while和for循环构成的嵌套循环处理学生的成绩。
这里,称用户控制运行是否继续的循环为外循环;称嵌套在这个循环中用来分配学生成绩的循环为内循环。在上面的例子中,利用while循环作为外循环;利用for循环作为内循环。嵌套循环可以利用任何适合的循环语句,如do-while、for或者其他综合形式来完成。
如果说单循环解决重复性的一维线性问题,双嵌套循环则可以用来解决二维列表或者数组问题。我们可以像嵌套if-else一样,构成三维或者更高维的嵌套循环。这里主要讨论二维嵌套循环。你可以举一反三,将嵌套循环的原理运用到多维嵌套中去。
例子之二:一个分析性例子,讨论双for嵌套循环是怎样工作的。
运行结果为:
从这个运行结果不难看出,当一个内循环结束,即col > 5时,控制跳出内循环,返回到外循环的开始,判断row <= 3是否为真。如果不成立,控制将执行例子中的第一个输出语句,并重新开始执行内循环。当row > 3时,控制跳出所有循环,这个嵌套循环宣告执行完毕。我们称由这两个嵌套循环构成的列表为3×5数组的应用,将在以后的章节专门讨论。
例子之三:打印乘法九九表。这是典型的应用双循环的例子。外循环控制行数,内循环控制列数。因为循环次数是固定的,可以用for循环来实现。具体代码如下:
运行结果为:
4.9 更多控制语句
在程序的执行流程控制中经常使用break语句,来中断正在执行的某种操作。与此相反,也使用continue语句继续或者重复某个操作的运行。本节讨论在编程中如何正确地利用这两个控制语句。
在4.7节讨论switch语句时,使用break语句控制对case的执行流程,终止switch的执行。在Java中,break也可用来控制循环的执行行为。与break相反的是continue语句。下面将介绍break和continue语句在循环中的应用。还将讨论带有标识符的break和continue语句。这些语句使我们在编程时,可更加灵活地控制循环的执行,但也增加了代码出错的机会,使用时应慎重。
4.9.1 break语句
Java只允许在switch和循环中使用break语句。如果break用在循环中,当某个条件成立时,将立即终止这个循环的运行,否则将继续运行这个循环直到循环结束:
即如果对象a的方法doSomething()返回的值小于0时,break将使控制跳出这个循环。
下面是另外一个例子:
如果choice的值为“n”或“N”,break语句将使循环终止。
例子之一:利用break和双嵌套循环查找2~20中的素数。
运行结果为:
当n可以被x整除时,执行break语句,这时将终止内循环,控制从外循环的下一次迭代开始;如果n = x,说明找到了素数,打印信息。
注意,当循环被break语句终止时,其循环控制变量没有被更新。例如:
例子之二:从上例可以看出,循环被break终止后,count虽然仍等于2,但count是一个属于for循环的内部使用的局部变量,所以循环终止后(无论是被break终止还是终止循环的条件已经满足,即count = 6),这个变量就不能再被使用。如果需要继续使用这个变量,必须将这个程序修改如下:
警告 只有在必要时才使用break语句,否则会降低代码的可读性,并增加代码错误的机会。
4.9.2 continue语句
continue语句只能用在循环中。利用它可以在循环中避免执行continue之后某些语句行。
例子之一:利用continue对循环体中的某些语句设置执行条件。例如:
当i < 4为真,continue被执行,控制将从while循环的下一次迭代继续循环,否则,i >= 4时,将执行continue后的语句,即:
b.doSomethingElse();注意,这个例子中如果a.doSomething()的返回值永远小于4时,将产生无限循环错误。
例子之二:利用continue和循环打印1~3的倒数表:
运行结果为:
3.0 的倒数是 0.3333333333333333
2.0 的倒数是 0.5
1.0 的倒数是 1.0
-1.0 的倒数是 -1.0
-2.0 的倒数是 -0.5
-3.0 的倒数是 -0.3333333333333333
在上面的例子中,continue语句使执行跳过i为0的循环,继续下一个迭代,否则打印结果。
注意,for循环因continue语句的执行,继续到下一个迭代,是因为在执行了continue之后,循环还自动更新循环变量。但这种情况在while循环中则不会发生。编程时必须慎重,否则会造成无限循环。例如:
但在continue之前对count加1则可纠正这个可能的错误。
警告 不正确使用continue会降低代码的可读性,并增加编写错误的机会。一些有经验的软件开发工程师甚至反对使用这些语句,并认为它们和goto语句一样,会造成程序的混乱。作者建议尽量回避使用。
实战项目:投资回报应用开发(1)
项目描述:
计算投资者的投资回报值。根据用户每月投资额、投资期限以及投资回报率,要求程序将投资回报值显示到屏幕上。程序可以重复运行直到用户希望停止。
程序分析:
根据问题描述,假设用户每月投资一个固定的数额,投资期限按年计算,投资回报率以年回报率计算,利息按每月结算。这样,计算投资回报值的数学公式为:
月投资回报额=(上月月投资回报额+当前月投资额)×月投资回报
率+(当前月投资汇报额+当前月投资额)
即:月投资回报额=(上月月投资回报额+当前月投资额)×(1+月
投资回报率)
例如,月投资额为10元;投资年为1年(12个月);年回报率为
12%,则月利息率为0.12/12 =0.01。则:
第一个月的投资回报额=(0+10)×(1+0.01)=10.10
第二个月的投资回报额=(10.10+10)×(1+0.01)=20.301
第三个月的投资回报额=(20.301+10)×(1+0.01)=30.60401
……一年结束时的投资回报额=∑(上月月投资回报额+当前月投资额)
×(1+月投资回报率)
其中,∑从1到12。
即:
futureValue = ∑ (futureValue + monthlyInvest) * (1 + monthlyRate)
□ futureValue:月投资回报额。
□ monthlyInvest:月投资额。
□ monthlyRate:月利息率。
这个公式可以用如下while循环语句表示:
也可以用for循环编写:
类的设计:
按照Java命名规范,称用来计算投资回报的类为FutureValue。
(1)类的属性或实例变量(全部具有private访问权)
□ Name:用户名,字符串变量。
□ monthlyInvest:月投资额,double类型变量。
□ yearlyRate:用户输入的年息利率,double类型变量。□ monthlyRate:月利息率=年利息率/12,double类型,只用在计
算投资回报的方法中,可以定义为本地变量。
□ years:用户输入的投资多少年,int整数类型变量。
□ months:换算后的投资长度;months = years × 12,int整数类型,只用在计算投资回报的方法中,可以定义为本地变量。
□ futureValue:投资回报结果,double类型变量。
(2)功能或方法设计(全部具有public访问权)除futureValue、monthlyRate以及months之外,所有其他变量都有各自的设置器setter和返回器getter方法。futureValue只设计getter方法。
monthlyRate和months是内部用来计算的变量,可以不涉及setter和getter方法。
double
futureValueComputing():用来计算投资回报,返回计算结果。
输入输出设计:
利用API类JOptionPane所提供的方法处理输入和输出操作。
□ 输入:提示用户输入姓名(name)、月投资额(monthlyInvest)、投资年度(years)、年利息率(yearlyRate),以及提示用户是否继续程序运行“Continue(y/n)?”
□ 输出:用户姓名、月投资额、投资年、投资年利息以及投资回报额。
异常处理:
包括怎样处理用户输入错误。将在以后章节讨论了异常处理后,在投资回报应用程序开发(2)包括这个功能。软件测试:
编写测试类FutureValueApp进行测试和必要的修改。输入各种数据,检查输出结果是否正确;提示是否清楚;输出结果的显示是否满意。
软件文档:
对类、变量、方法和重要语句行注释。必要时利用Eclipse的Javadoc功能创建文档。
根据以上分析,可以编写计算投资回报值的类如下:
可以使用JOptionPane提供的输入、输出方法来编写这个类的驱动程序,并且利用while循环语句,由用户控制程序的运行。即当使用者输入是否继续程序运行的提示后,程序根据用户的输入信息“y”或“n”来决定程序是否继续运行。例如:
巩固提高练习和实战项目大练兵
1.举例说明什么是关系表达式、什么是逻辑表达式、什么是复合表达式、什么是布尔表达式以及条件表达式和这些表达式的关系。
2.指出下列表达式的运算次序和结果:
(1)((total/scores) > 60) && ((total/(scores –1) <= 100)),其中total =121,scores = 2。
(2)(x >= y) && !(y >=z),其中x = 10,y = 10,z = 10。
(3)(x || !y) && (!x || z),其中x = false,y = true,z = false。
(4)x > y || x <= y && z,其中x = 2,y = 2,z = true。3.在4.5.2节中,应用嵌套if-else给学生分配字母学分。思考为什么不用下列方法来解决这个问题:
为什么使用嵌套if-else?它有什么优越性?
4.给出下列可能性,应用嵌套if-else和其他格式的if-else编写解决这个问题的部分代码:
5.利用switch语句编写与4.5.2节实例相反的操作,即知道字母学分,给出它所代表的分数范围。
6.给出下列成绩和结果,编写解决这个问题的代码:
(1)利用嵌套if-else语句编写。(2)利用switch语句编写。
7.将上面的练习题完善成为一个完整的可执行程序。利用while循环继续这个程序的运行,直到用户希望停止。测试结果,并保存这个源代码文件。
8.将上面练习第7题中的程序改写为用do-while循环完成。测试结果,并保存这个源代码文件。
9.将上面的练习题改写如下:类Result用来执行对数据的处理和判断,并产生结果;类ResultApp为这个类的测试程序。显示结果,完善注释,并保存所有源代码文件。
10.打开本书配套资源Ch4中,名为SwitchTestApp.java的程序,将其修改为用do-while循环。测试修改后的代码,完善注释,并保存源代码文件。
11.给出下列应用循环的例子,指出使用什么循环语句较为合适:
(1)计算30个学生的平均成绩。
(2)计算一个班学生的平均成绩,但每个班的学生人数不等。
(3)循环的运行次数是随机的。
(4)无论什么条件,循环一定要执行,但执行次数不确定。
12.将下面给出的循环改写成指定的循环,假设所有变量都已初始化。
(1)改写成do-while循环:
(2)挑战题:可否将上面的练习题改写成for循环?
(3)改写成while循环:
(4)改写成for循环:
13.利用for循环编写一个计算阶乘的程序。提示:5!=5×4×3×2×1。
14.将上面这个练习题完善成一个名为FactorialApp的可执行程序。测试结果,完善注释,并保存源代码文件。
15.指出执行下列代码后的输出结果:
16.假设一个循环语句中包含switch语句。如果switch语句中的break被执行,请判断控制是否会跳出这个循环?为什么?
17.利用循环打印从1到10的平方和立方表如下:
18.将上面这个练习题完善成一个名为SquareAndCube的类,打印任何一段数值的平方和立方表。编写一个名为SquareAndCubeApp的测试类,用户可以打印表的开始数值和结束数值,并创建SquareAndCube的对象,调用适当的方法打印类似第17题中的表。测试结果,完善注释,并保存源代码文件。
19.修改第18题,该程序将继续运行,直到提示“Continue (y/n)?”时用户输入“n”或者“N”结束程序的运行。测试结果,完善注释,并保存源代码文件。
20.修改第19题,加入一个外循环用来控制该程序的运行。当用户输入选择停止时,程序将终止继续运行。测试结果,完善注释,并保存源代码文件。
21.打开本书配套资源Ch4中名为PrintReciprocalApp.java的程序,将其修改为用while循环。测试修改后的代码,完善注释,并保存源代码文件。
22.打开本书配套资源Ch4中名为PrimeNumberApp.java的程序,将其修改为用while循环。测试修改后的代码,完善注释,并保存源代码文件。23.打开本书配套资源Ch4中名为BreakTestApp.java的程序,将其修改为用do-while循环。测试修改后的代码,完善注释,并保存源代码文件。
24.打开本书配套资源Ch4中名为PrimeNumberApp.java的程序,将其修改为用do-while循环。测试修改后的代码,完善注释,并保存源代码文件。
25.实战项目大练兵:参考第3章和本章实战项目软件开发的分析和编程过程,修改距离转换应用程序,提示用户“Continue (y/n)?”,当用户输入“n”或者“N”时停止程序运行,否则继续。编写一个测试程序测试修改后的代码,完善注释和文档,并保存源代码文件。
26.实战项目大练兵:参考第3章和本章实战项目软件开发的分析和编程过程,修改温度转换应用程序,提示用户“Continue (y/n)?”,当用户输入“n”或者“N”时停止程序运行,否则继续。编写一个测试程序测试修改后的代码,完善注释和文档,并保存源代码文件。
27.实战项目大练兵:参考第3章和本章实战项目软件开发的分析和编程过程,修改重量转换应用程序,提示用户“Continue (y/n)?”,当用户输入“n”或者“N”时停止程序运行,否则继续。编写一个测试程序测试修改后的代码,完善注释和文档,并保存源代码文件。