Java编程思想 | 第3章 控制程序流程

"就像任何有感知的生物一样,程序必须能操纵自己的世界,在执行过程中做出判断与选择。"

在 Java 里我们利用运算符操纵对象和数据,并用执行控制语句做出选择。

3.1 Java运算符 赋值

赋值是用等号运算符(=)进行的。它的意思是"取得右边的值,把它的复制到左边"。右边的值可以是任何常数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4),但不可将任何东西赋给一个常数( 比如4=A)。

对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设主类型使用"A=B",那么B处的内容就复制到了A。若接着修改了A,那么B根本不会受到这种修改的影响。作为一名程序员,这应当成为自己的常识。

但在为对象"赋值"的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。所以倘若"从一个对象到另一个对象"赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为对象使用"C=D",那么C和D最终都会指向最初只有D指向的那个对象。

class Number {
 int i;
}
public class Assignment {
 public static void main(String[] args) {
 Number n1 = new Number();
 Number n2 = new Number();
 n1.i = 9;
 n2.i = 47;
 System.out.println("1: n1.i: " + n1.i +
 ", n2.i: " + n2.i);
 n1 = n2;
 System.out.println("2: n1.i: " + n1.i +
 ", n2.i: " + n2.i);
 n1.i = 27;
 System.out.println("3: n1.i: " + n1.i +
 ", n2.i: " + n2.i);
 }
} 

Number 类非常简单,它的两个实例(n1 和n2)是在 main()里创建的。每个Number 中的i 值都赋予了一个不同的值。随后,将 n2 赋给n1,而且 n1 发生改变。在许多程序设计语言中,我们都希望n1 和n2 任何时候都相互独立。但由于我们已赋予了一个句柄,所以下面才是真实的输出:

1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27

看来改变n1 的同时也改变了n2!这是由于无论n1 还是n2 都包含了相同的句柄,它指向相同的对象(最初的句柄位于 n1 内部,指向容纳了值9 的一个对象。在赋值过程中,那个句柄实际已经丢失;它的对象会由“垃圾收集器”自动清除)。

这种特殊的现在通常也叫做"别名",是Java操作对象的一种基本方式。但假若不愿意在这种情况下出现别名,又该怎么操作呢?可放弃赋值,并写入下述代码:

n1.i = n2.i;

这样便可保留两个独立的对象,而不是将 n1 和n2 绑定到相同的对象。但您很快就会意识到,这样做会使对象内部的字段处理发生混乱,并与标准的面向对象设计准则相悖。

3.1.2 三元 if-else 运算符

布尔表达式 ?值 0 :值 1

若"布尔表达式"的结果为 true,就计算"值0",而且它的结果成为最终由运算符产生的值。但若“布尔表达式”的结果为 false,计算的就是“值 1”,而且它的结果成为最终由运算符产生的值。

3.2 执行控制

Java 使用了 C 的全部控制语句。大多数程序化的编程语言都提供了某种形式的控制语句,这在语言间通常是共通的。在Java 里,涉及的关键字包括if-else、while、do-while、for 以及一个名为 switch 的选择语句。然而,Java 并不支持非常有害的goto(它仍是解决某些特殊问题的权宜之计)。仍然可以进行象goto 那样的跳转,但比典型的 goto 要局限多了。

3.2.1 真和假

所有条件语句都利用条件表达式的真或假来决定执行流程。条件表达式的一个例子是 A==B。它用条件运算符“==”来判断A 值是否等于 B 值。该表达式返回 true 或 false。本章早些时候接触到的所有关系运算符都可拿来构造一个条件语句。注意 Java 不允许我们将一个数字作为布尔值使用,即使它在 C 和 C++里是允许的(真是非零,而假是零)。若想在一次布尔测试中使用一个非布尔值——比如在 if(a)里,那么首先必须用一个条件表达式将其转换成一个布尔值,例如 if(a!=0)。

3.2.2 if-else

if-else 语句或许是控制程序流程最基本的形式。其中的else是可选的。所以按一下两种形式来使用 if :

if(布尔表达式)
语句
OR
if(布尔表达式)
语句
else
语句

条件必须产生一个布尔结果。“语句”要么是用分号结尾的一个简单语句,要么是一个复合语句——封闭在括号内的一组简单语句。在本书任何地方,只要提及“语句”这个词,就有可能包括简单或复合语句。作为if-else 的一个例子,下面这个 test()方法可告诉我们猜测的一个数字位于目标数字之上、之下还是相等:

static int test(int testval) {
 int result = 0;
 if(testval > target)
 result = -1;
 else if(testval < target)
 result = +1;
 else
 result = 0; // match
 return result;
}

最好将流程控制语句缩进排列,使读者能方便地看出起点与终点。

1.return

return 关键字有两方面的用途:指定一个方法返回什么值(假设它没有void返回值),并立即返回那个值。可据此改写上面的 test() 方法,使其利用这些特点:

static int test2(int testval) {
 if(testval > target)
 return -1;
 if(testval < target)
 return +1;
 return 0; // match
}

不必加上 else ,因为方法在遇到 return 后便不再继续。

3.2.3 反复

while,do-while 和 for 控制着循环,有时将其划分为"反复语句"。除非用于控制反复的布尔表达式得到"假"的结果,否则语句会重复执行下去。while 循环的格式如下:

while(布尔表达式)
语句

在循环刚开始时,会计算一次“布尔表达式”的值。而对于后来每一次额外的循环,都会在开始前重新计算一次。

下面这个简单的例子可产生随机数,直到符合特定的条件为止:

public class WhileTest {
 public static void main(String[] args) {
 double r = 0;
 while(r < 0.99d) {
 r = Math.random();
 System.out.println(r);
 }
 }
}

它用到了Math 库里的 static(静态)方法 random()。该方法的作用是产生 0 和1 之间(包括 0,但不包括1)的一个double 值。while 的条件表达式意思是说:“一直循环下去,直到数字等于或大于 0.99”。由于它的随机性,每运行一次这个程序,都会获得大小不同的数字列表。

3.2.4 do-while

do-while 的格式如下:

do
语句
while(布尔表达式)

while 和 do-while 唯一的区别就是 do-while 肯定会至少执行一次:也就是说,至少会将其中的语句"过一遍"——即便表达式第一次便计算为false。而在while循环结构中,若条件第一次就为 false,那么其中的语句根本不会执行。在实际应用中,while 比 do-while 更常用一些。

3.2.5 for

for循环在第一次反复之前要进行初始化。随后,它会进行条件测试,而且在每一次反复的时候,进行某种形式的"步进"(Stepping)。for循环的形式如下:

for(初始表达式:布尔表达式:步进)
语句

无论初始表达式,布尔表达式,还是步进,都可以置空。每次反复前,都要测试一下布尔表达式。若获得的结果是 false,就会继续执行紧跟在 for 语句后面的那行代码。在每次循环的末尾,会计算一次步进。

for循环通常执行"计数任务":

public class ListCharacters {
 public static void main(String[] args) {
 for( char c = 0; c < 128; c++)
 if (c != 26 ) // ANSI Clear screen
 System.out.println(
 "value: " + (int)c +
 " character: " + c);
 }
} 

注意变量c 是在需要用到它的时候定义的——在for 循环的控制表达式内部,而非在由起始花括号标记的代码块的最开头。c 的作用域是由for 控制的表达式。

以于象 C 这样传统的程序化语言,要求所有变量都在一个块的开头定义。所以在编译器创建一个块的时候,它可以为那些变量分配空间。而在 Java 和C++中,则可在整个块的范围内分散变量声明,在真正需要的地方才加以定义。这样便可形成更自然的编码风格,也更易理解。

可在for 语句里定义多个变量,但它们必须具有同样的类型:

for(int i = 0, j = 1;
 i < 10 && j != 11;
 i++, j++)

其中,for语句内的 int 定义同时覆盖了 i 和 j 。只有 for 循环才具备在控制表达式里定义变量的能力。对于其他任何条件或循环语句,都不可采用这种方法。

3.2.6 中断和继续

在任何循环语句的主体部分,亦可用break 和continue 控制循环的流程。break 用于强行退出循环,不执行循环中剩余的语句。而 continue 则停止执行当前的反复,然后退回循环起始,开始新的反复。

public class BreakAndContinue {
 public static void main(String[] args) {
 for(int i = 0; i < 100; i++) {
 if(i == 74) break; // Out of for loop
 if(i % 9 != 0) continue; // Next iteration
 System.out.println(i);
 }
 int i = 0;
 // An "infinite loop":
 while(true) {
 i++;
 int j = i * 27;
 if(j == 1269) break; // Out of loop
 if(i % 10 != 0) continue; // Top of loop
 System.out.println(i);
 }
 }
} 

在这个 for 循环中,i 的值永远不会到达 100。因为一旦 i 到达74,break 语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需象这样使用 break。只要 i 不能被 9 整除,continue 语句会使程序流程返回循环的最开头执行(所以使 i 值递增)。如果能够整除,则将值显示出来。

第二部分向大家揭示了一个“无限循环”的情况。然而,循环内部有一个 break 语句,可中止循环。除此以 外,大家还会看到 continue 移回循环顶部,同时不完成剩余的内容(所以只有在i 值能被 9 整除时才打印出值)。输出结果如下:

0
9
18
27
36
45
54
63
72
10
20
30
40

之所以显示0,是由于 0%9 等于 0.

无限循环的第二种形式是 for(;;)。编译器将 while(true)与for(;;)看作同一回事。所以具体选用哪个取决于自己的编程习惯。

3.2.7 开关

"开关" (Switch) 有时也被划分为 "选择语句"。根据一个整数表达式的值,switch 语句可从一系列代码选出一段执行。它的格式如下:

switch(整数选择因子) {
case 整数值 1 : 语句; break;
case 整数值 2 : 语句; break;
case 整数值 3 : 语句; break;
case 整数值 4 : 语句; break;
case 整数值 5 : 语句; break;
//..
default:语句;
}        

其中,“整数选择因子”是一个特殊的表达式,能产生整数值。switch 能将整数选择因子的结果与每个整数值比较。若发现相符的,就执行对应的语句(简单或复合语句)。若没有发现相符的,就执行default 语句。

在上面的定义中,大家会注意到每个case 均以一个break 结尾。这样可使执行流程跳转至 switch 主体的末尾。这是构建switch 语句的一种传统方式,但break 是可选的。若省略 break,会继续执行后面的case 语句的代码,直到遇到一个break 为止。尽管通常不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的default 语句没有 break,因为执行流程已到了 break 的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在default 语句的末尾放置一个break,尽管它并没有任何实际的用处。switch 语句是实现多路选择的一种易行方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择 因子,并且必须是 int 或char 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在switch 语句里是不会工作的。对于非整数类型,则必须使用一系列 if 语句。

下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:

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 的末尾,否则控制流程会简单地下移,并继续判断下一个条件是否相符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值