Java编程思想第4版[中文版](PDF格式)-第34部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
大家要记住的重点是:在Java 里唯一需要用到标签的地方就是拥有嵌套循环,而且想中断或继续多个嵌套级
别的时候。
在Dijkstra 的“Goto 有害”论中,他最反对的就是标签,而非 goto。随着标签在一个程序里数量的增多,
他发现产生错误的机会也越来越多。标签和goto 使我们难于对程序作静态分析。这是由于它们在程序的执行
流程中引入了许多“怪圈”。但幸运的是,Java 标签不会造成这方面的问题,因为它们的活动场所已被限
死,不可通过特别的方式到处传递程序的控制权。由此也引出了一个有趣的问题:通过限制语句的能力,反
而能使一项语言特性更加有用。
3。2。7 开关
“开关”(Switch)有时也被划分为一种“选择语句”。根据一个整数表达式的值,switch语句可从一系列
代码选出一段执行。它的格式如下:
switch(整数选择因子) {
case 整数值 1 : 语句; break;
case 整数值 2 : 语句; break;
case 整数值 3 : 语句; break;
case 整数值 4 : 语句; break;
case 整数值 5 : 语句; break;
//。。
default:语句;
91
…………………………………………………………Page 93……………………………………………………………
}
其中,“整数选择因子”是一个特殊的表达式,能产生整数值。switch 能将整数选择因子的结果与每个整数
值比较。若发现相符的,就执行对应的语句(简单或复合语句)。若没有发现相符的,就执行default语
句。
在上面的定义中,大家会注意到每个case 均以一个break 结尾。这样可使执行流程跳转至 switch主体的末
尾。这是构建 switch 语句的一种传统方式,但break 是可选的。若省略 break,会继续执行后面的case 语
句的代码,直到遇到一个break 为止。尽管通常不想出现这种情况,但对有经验的程序员来说,也许能够善
加利用。注意最后的default语句没有 break,因为执行流程已到了break 的跳转目的地。当然,如果考虑
到编程风格方面的原因,完全可以在default 语句的末尾放置一个break,尽管它并没有任何实际的用处。
switch语句是实现多路选择的一种易行方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择
因子,并且必须是 int 或char 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它
们在 switch语句里是不会工作的。对于非整数类型,则必须使用一系列 if语句。
下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:
//: VowelsAndConsonants。java
// Demonstrates the switch statement
public class VowelsAndConsonants {
public static void main(String'' args) {
for(int i = 0; i 《 100; i++) {
char c = (char)(Math。random() * 26 + 'a');
System。out。print(c + 〃: 〃);
switch(c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System。out。println(〃vowel〃);
break;
case 'y':
case 'w':
System。out。println(
〃Sometimes a vowel〃);
break;
default:
System。out。println(〃consonant〃);
}
}
}
} ///:~
由于Math。random()会产生 0 到 1 之间的一个值,所以只需将其乘以想获得的最大随机数(对于英语字母,
这个数字是 26),再加上一个偏移量,得到最小的随机数。
尽管我们在这儿表面上要处理的是字符,但switch 语句实际使用的字符的整数值。在 case 语句中,用单引
号封闭起来的字符也会产生整数值,以便我们进行比较。
请注意 case 语句相互间是如何聚合在一起的,它们依次排列,为一部分特定的代码提供了多种匹配模式。也
应注意将break 语句置于一个特定 case 的末尾,否则控制流程会简单地下移,并继续判断下一个条件是否相
符。
1。 具体的计算
92
…………………………………………………………Page 94……………………………………………………………
应特别留意下面这个语句:
char c = (char)(Math。random() * 26 + 'a');
Math。random()会产生一个 double值,所以 26 会转换成double 类型,以便执行乘法运算。这个运算也会产
生一个 double 值。这意味着为了执行加法,必须无将'a'转换成一个 double。利用一个“造型”,double 结
果会转换回 char。
我们的第一个问题是,造型会对char 作什么样的处理呢?换言之,假设一个值是29。7,我们把它造型成一
个 char,那么结果值到底是30 还是 29 呢?答案可从下面这个例子中得到:
//: CastingNumbers。java
// What happens when you cast a float or double
// to an integral value?
public class CastingNumbers {
public static void main(String'' args) {
double
above = 0。7;
below = 0。4;
System。out。println(〃above: 〃 + above);
System。out。println(〃below: 〃 + below);
System。out。println(
〃(int)above: 〃 + (int)above);
System。out。println(
〃(int)below: 〃 + (int)below);
System。out。println(
〃(char)('a' + above): 〃 +
(char)('a' + above));
System。out。println(
〃(char)('a' + below): 〃 +
(char)('a' + below));
}
} ///:~
输出结果如下:
above: 0。7
below: 0。4
(int)above: 0
(int)below: 0
(char)('a' + above): a
(char)('a' + below): a
所以答案就是:将一个 float 或 double 值造型成整数值后,总是将小数部分“砍掉”,不作任何进位处理。
第二个问题与Math。random()有关。它会产生0 和 1之间的值,但是否包括值'1'呢?用正统的数学语言表
达,它到底是(0;1),'0;1',(0;1',还是'0;1)呢(方括号表示“包括”,圆括号表示“不包括”)?同样
地,一个示范程序向我们揭示了答案:
//: RandomBounds。java
// Does Math。random() produce 0。0 and 1。0?
public class RandomBounds {
static void usage() {
System。err。println(〃Usage: nt〃 +
93
…………………………………………………………Page 95……………………………………………………………
〃RandomBounds lowernt〃 +
〃RandomBounds upper〃);
System。exit(1);
}
public static void main(String'' args) {
if(args。length != 1) usage();
if(args'0'。equals(〃lower〃)) {
while(Math。random() != 0。0)
; // Keep trying
System。out。println(〃Produced 0。0!〃);
}
else if(args'0'。equals(〃upper〃)) {
while(Math。random() != 1。0)
; // Keep trying
System。out。println(〃Produced 1。0!〃);
}
else
usage();
}
} ///:~
为运行这个程序,只需在命令行键入下述命令即可:
java RandomBounds lower
或
java RandomBounds upper
在这两种情况下,我们都必须人工中断程序,所以会发现 Math。random() “似乎”永远都不会产生0。0 或
1。0。但这只是一项实验而已。若想到 0 和 1 之间有 2 的 128次方不同的双精度小数,所以如果全部产生这些
数字,花费的时间会远远超过一个人的生命。当然,最后的结果是在Math。random() 的输出中包括了0。0。或
者用数字语言表达,输出值范围是 '0;1)。
3。3 总结
本章总结了大多数程序设计语言都具有的基本特性:计算、运算符优先顺序、类型转换以及选择和循环等
等。现在,我们作好了相应的准备,可继续向面向对象的程序设计领域迈进。在下一章里,我们将讨论对象
的初始化与清除问题,再后面则讲述隐藏的基本实现方法。
3。4 练习
(1) 写一个程序,打印出 1 到 100 间的整数。
(2) 修改练习(1),在值为47 时用一个break 退出程序。亦可换成 return 试试。
(3) 创建一个 switch 语句,为每一种case 都显示一条消息。并将 switch置入一个 for 循环里,令其尝试每
一种case。在每个case 后面都放置一个break,并对其进行测试。然后,删除break,看看会有什么情况出
现。
94
…………………………………………………………Page 96……………………………………………………………
第 4 章 初始化和清除
“随着计算机的进步,‘不安全’的程序设计已成为造成编程代价高昂的罪魁祸首之一。”
“初始化”和“清除”是这些安全问题的其中两个。许多C 程序的错误都是由于程序员忘记初始化一个变量
造成的。对于现成的库,若用户不知道如何初始化库的一个组件,就往往会出现这一类的错误。清除是另一
个特殊的问题,因为用完一个元素后,由于不再关心,所以很容易把它忘记。这样一来,那个元素占用的资
源会一直保留下去,极易产生资源(主要是内存)用尽的后果。
C++为我们引入了“构建器”的概念。这是一种特殊的方法,在一个对象创建之后自动调用。Java 也沿用了
这个概念,但新增了自己的“垃圾收集器”,能在资源不再需要的时候自动释放它们。本章将讨论初始化和
清除的问题,以及 Java 如何提供它们的支持。
4。1 用构建器自动初始化
对于方法的创建,可将其想象成为自己写的每个类都调用一次 initialize()。这个名字提醒我们在使用对象
之前,应首先进行这样的调用。但不幸的是,这也意味着用户必须记住调用方法。在 Java 中,由于提供了名
为“构建器”的一种特殊方法,所以类的设计者可担保每个对象都会得到正确的初始化。若某个类有一个构
建器,那么在创建对象时,Java 会自动调用那个构建器——甚至在用户毫不知觉的情况下。所以说这是可以
担保的!
接着的一个问题是如何命名这个方法。存在两方面的问题。第一个是我们使用的任何名字都可能与打算为某
个类成员使用的名字冲突。第二是由于编译器的责任是调用构建器,所以它必须知道要调用是哪个方法。C++
采取的方案看来是最简单的,且更有逻辑性,所以也在Java 里得到了应用:构建器的名字与类名相同。这样
一来,可保证象这样的一个方法会在初始化期间自动调用。
下面是带有构建器的一个简单的类(若执行这个程序有问题,请参考第3 章的“赋值”小节)。
//: SimpleConstructor。java
// Demonstration of a simple constructor
package c04;
class Rock {
Rock() { // This is the constructor
System。out。println(〃Creating Rock〃);
}
}
public class SimpleConstructor {
public static void main(String'' args) {
for(int i = 0; i 《 10; i++)
new Rock();
}
} ///:~
现在,一旦创建一个对象:
new Rock();
就会分配相应的存储空间,并调用构建器。这样可保证在我们经手之前,对象得到正确的初始化。
请注意所有方法首字母小写的编码规则并不适用于构建器。这是由于构建器的名字必须与类名完全相同!
和其他任何方法一样,构建器也能使用自变量,以便我们指定对象的具体创建方式。可非常方便地改动上述
例子,以便构建器使用自己的自变量。如下所示:
class Rock {
Rock(int i) {