第3章 表达式和流程控制语句 3.1 表?达?式 表达式由运算符和操作数组成,对操作数进行运算符指定的操作,并得出一个结果。Java?运算符按功能可分为:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和条件运算符;除此之外,还有几个特殊用途运算符,如数组下标运算符等。操作数可以是变量、常量或方法调用等。 如果表达式中仅含有算术运算符,如“*”,则为算术表达式,它的计算结果是一个算术量(“+”用于字符串连接除外)。如果表达式中含有关系运算符,如“>”,则为关系表达式,它的计算结果是一个逻辑值,即true或false。如果表达式中含有逻辑运算符,如“&&”,则为逻辑表达式,相应的计算结果为逻辑值。 3.1.1 操作数 1. 常量 常量操作数很简单,只有基本数据类型和String类型才有相应的常量形式。 例3-1 常量示例。 常量 含义 23.59 double型常量 -1247.1f float型常量 true boolean型常量 "This is a String" String型常量 2. 变量 变量是存储数据的基本单元,它可以作为表达式中的操作数。变量在使用之前要先说明,变量说明的基本格式如下: 类型 变量名1[ = 初值1][,变量名2 [= 初值2]]...; 其中,类型是变量所属的类型,既可以是基本数据类型,如int和float等;也可以是类类型。有时也把类类型的变量称为引用。 变量说明的地方有两处,一处是在方法内,另一处是在类定义内。方法内定义的变量称为自动变量,有的人也喜欢称之为局部变量、临时变量或栈变量。不管叫什么,说的都是一个意思。这里所说的方法,包括程序员定义的各个方法。类中定义的变量就是它的成员变量。 说明基本数据类型的变量之后,系统会自动在内存中为其分配相应的存储空间。说明引用后,系统只分配引用空间,程序员要调用new来创建实例对象,然后才分配相应的存储空间。 3. 变量初始化 Java程序中不允许将未经初始化的变量用于操作数。对于基本数据变量,在说明它们的同时可以进行初始化,如: int x = 3; 创建一个对象后,使用new运算符分配存储空间时,系统按表3-1中的值自动初始化成员变量。 表3-1 变量初始值 类 型 初始值 类 型 初始值 byte (byte)0 double 0.0 short (short)0 char '\u0000'(null) int 0 boolean false long 0L 所有引用类型 null float 0.0f 具有null值的引用不指向任何对象。如果使用它指向的对象,则导致一个异常。异常是运行时发生的一个错误,有关内容在第6章介绍。 自动变量在使用之前必须进行初始化。编译器扫描代码,判定每个变量在首次使用前是否已被显式初始化。如果编译器发现某个变量没有初始化,则会发生编译时错误。 例3-2 变量初始化示例。 int x = (int)(Math.random() * 100); int y; int z; if (x > 50) { y = 9; } z = y + x; //可能在初始化之前使用,导致编译错误 例3-2中程序的前3行说明了3个整型变量x、y、z。x初始化为表达式的值,y和z都没有进行初始化。y的赋值包含在if语句块中,而该块是否执行要依x的值而定。x是随机数,当它小于或等于50时,程序流跳过if块,不会给y赋值,而是执行if块后的赋值语句。此时因y没有进行初始化,这条语句将导致一个编译错误。 4.变量作用域 变量的作用域是指可访问该变量的代码范围。类中定义的成员变量的作用域是整个类。方法中定义的局部变量的作用域是从该变量的说明处开始到包含该说明的语句块结束处,块外是不可使用的。 块内说明的变量将屏蔽其所在类定义的同名变量。但同一块中如果定义两个同名变量则引起冲突。Java允许屏蔽,但冲突会引起编译错误。请看程序3-1。 程序3-1 1 class Customer { 2 /* 说明变量屏蔽及作用域实例 3 */ 4 public static void main(String [] args) { 5 Customer customer = new Customer(); 6 String name = "John Smith"; 7 { 8 //下列说明是非法的,仍在前一个name的作用域内 9 String name = "Tom David"; 10 customer.name = name; 11 System.out.println( 12 "The customer's name: " + customer.name); 13 } 14 } 15 private String name; 16 } 程序3-1中定义了Customer类,其中有成员变量name,如第15行所示。在main()方法中定义了局部变量name,并赋初值“John Smith”。该局部变量屏蔽了同名的类成员变量。第9行又说明变量name,与第6行说明的变量冲突,导致编译错误,如图3-1所示,错误的含义是指变量name在本方法中已定义。 图3-1 程序3-1的运行结果 修改程序3-1,把第3个局部变量的说明改为赋值语句,得到程序3-2。 程序3-2 1 class Customer { 2 /* 说明变量屏蔽及作用域实例 3 */ 4 public static void main(String [] args) { 5 Customer customer = new Customer(); 6 String name = "John Smith"; 7 { 8 name = "Tom David"; 9 customer.name = name; 10 System.out.println( 11 "The customer's name: " + customer.name); 12 } 13 } 14 private String name; 15 } 该程序编译正确。它的运行结果如图3-2所示。 图3-2 程序3-2的运行结果 再看程序3-3。 程序3-3 1 class Customer { 2 /* 说明变量屏蔽及作用域实例 3 */ 4 public static void main(String [] args) { 5 Customer customer = new Customer(); 6 { String name = "Tom David"; 7 customer.name = name; 8 System.out.println("The customer's name: " + 9 ???????????????????customer.name); 10 } 11 //下面的再说明是正确的,前一个name的作用域已结束 12 String name = "John Smith"; 13 customer.name = name; 14 System.out.println( 15 "The customer's name: " + customer.name); 16 } 17 private String name; 18 } 程序3-3是正确的。虽然main()方法中两次说明了同名局部变量name,但第6行说明的变量只在第6~10行的块内有效,在块外该变量消失,第12行不在其作用域内。该方法的输出结果如图3-3所示。 图3-3 程序3-3的运行结果 3.1.2 运算符 Java的大多数运算符在形式上和功能上都与C和C++的运算符非常类似,若熟悉C和C++就不会对此感到陌生。 1. 算术运算符 算术运算符包括通常的加(+)、减(–)、乘(*)、除(/)、取余(%),完成整型和浮点型数据的算术运算。许多语言中的取余运算只能用于整型量,Java对此有所扩展,它允许对浮点类型量进行取余操作。取余运算还可以用于负数,结果的符号与第一个运算数的符号一致。看下面的例子: 3/2 //结果是1 15.2 % 5 //结果是0.2 5%-3 //结果是2 -5%-3 //结果是-2 此外,算术运算符还有“++”“–?–”两种,分别表示加1和减1操作。与C++类似,++i和i++的执行顺序稍有不同,前者在i使用之前加1,后者先使用再加1。–?–i与i–?–的情况与此类似。 2. 关系运算符 关系运算符用来比较两个值,包括大于(>)、大于或等于(>=)、小于(<)、小于或等于(<=)、等于(==)、不等于(!=)6种。关系运算符都是二元运算符,运算的结果是一个逻辑值。二元运算符也称为双目运算符。 Java?允许“==”和“!=”两种运算用于任何数据类型。例如,可以判定两个实例是否相等。 3. 逻辑运算符 逻辑运算符包括逻辑与(&&)、逻辑或(||)和逻辑非(!)。前两个是二元运算符,最后一个是一元运算符。一元运算符也称为单目运算符。 Java?对逻辑与和逻辑或提供短路操作功能。进行运算时,先计算运算符左侧表达式的值,如果使用该值能得到整个表达式的值,则跳过运算符右侧表达式的计算,否则计算运算符右侧表达式,并得到整个表达式的值。 例3-3 短路操作示例。 String unset = null; if ((unset != null) && (unset.length() > 5)) { //对unset进行某种操作 } 空串unset不能使用,因此不能访问unset.length(),但该if()语句中的逻辑表达式是合法的,且完全安全。这是因为第一个子表达式(unset != null)结果为假,马上可知整个表达式的结果为假,所以&&运算符跳过不必要的(unset.length() > 5)计算,因为没有计算它,从而避免了空指针异常。 4. 位运算符 位运算符用来对二进制位进行操作,包括按位取反(~)、按位与(&)、按位或(|)、异或(^)、右移(>>)、左移(<<)及无符号右移(>>>)。位运算符只能对整型和字符型数据进行操作。 Java提供两种右移运算符。 运算符“>>”较为常见,它执行算术右移,使用最高位填充移位后左侧的空位。右移的结果为:每移一位,第一个操作数被2除一次,移动的次数由第二个操作数确定。 例3-4 算术右移示例。 128 >> 1 得到 64 256 >> 4 得到 16 -256 >> 4 得到 -16 逻辑右移(无符号右移)运算符>>>只对位进行操作,而没有算术含义,它用0填充左侧的空位。 例3-5 逻辑右移示例。 (byte)0x80 >> 2 得到 -32 0xa2 >>> 2 得到 40 (byte) 0xa2 >> 2 得到 -24 (byte) 0xa2 >>> 2 得到 1073741800 算术右移不改变原数的符号,而逻辑右移不能保证这一点。 移位运算符约简其右侧的操作数,当左侧操作数是int类型时,右侧以32取模;当左侧是long类型时,右侧以64取模。因此,执行 int x; x = x >>> 32; 后,x的结果不改变,而不是通常期望的0。这样可以保证不会将左侧操作数的各位完全移走。 “>>>”运算符只用于整型,它只对int或long值起作用。如果用于short或byte值,则在进行“>>>”操作之前,使用符号扩展将其提升为int型,然后再移位。 5. 其他运算符 Java中的运算符还包括扩展赋值运算符(+=、–=、*=、/=、%=、&=、|=、^=、>>=、<<=)及(>>>=)、条件运算符(?:)、点运算符(.)、实例运算符(instanceof)、new运算符、数组下标运算符([])等。 扩展赋值运算符是在赋值号(=)前加上其他运算符,是对表达式的一种简写形式。例如,在如下赋值语句中: var = var op expression; var是变量,op是算术运算符或位运算符,expression为表达式。使用扩展赋值运算符可表示为 var op= expression; 例3-6 扩展赋值运算符示例。 int x = 3; x = x * 3; 等价于 int x = 3; x *= 3; 条件运算符(?:)是三元运算符,它的一般形式为 逻辑表达式 ? 表达式1 : 表达式2; 逻辑表达式得到一个逻辑值,根据该值的真假决定执行什么操作。如果值为真,则计算表达式1,否则计算表达式2。注意,表达式1和表达式2需要返回相同的类型,且不能是void。 6. 运算符的优先次序 在对一个表达式进行计算时,如果表达式中含有多种运算符,则要按运算符的优先级依次从高向低进行,同级运算符则按结合律进行。括号可以改变运算次序。运算符的优先次序见表3-2。 表3-2 运算符的优先次序 优先级 运算符 运 算 结合律 1 [] 数组下标 自左至右 . 对象成员引用 (参数) 参数计算和方法调用 ++ 后缀加 –?– 后缀减 ???????????????????????????????????????(续表)?? 优先级 运算符 运 算 结合律 2 ++ 前缀加 自右至左 –?– 前缀减 + 一元加 – 一元减 ~ 位运算非 ! 逻辑非 3 new 对象实例 自右至左 (类型) 转换 4 * 乘法 自左至右 / 除法 % 取余 5 + 加法 自左至右 + 字符串连接 – 减法 6 << 左移 自左至右 >> 用符号位填充的右移 >>> 用0填充的右移 7 < 小于 自左至右 <= 小于或等于 > 大于 >= 大于或等于 instanceof 类型比较 8 == 等于 自左至右 != 不等于 9 & 位运算与 自左至右 & 布尔与 10 ^ 位运算异或 自左至右 ^ 布尔异或 11 | 位或 自左至右 | 布尔或 12 && 逻辑与 自左至右 13 || 逻辑或 自左至右 14 ?: 条件运算 自右至左 15 = 赋值 自右至左 += 加法赋值 += 字符串连接赋值 –?= 减法赋值 ???????????????????????????????????????(续表)?? 优先级 运算符 运 算 结合律 15 *= 乘法赋值 自右至左 /= 除法赋值 %= 取余赋值 <<= 左移赋值 >>= 右移(符号位)赋值 >>>= 右移(0)赋值 &= 位与赋值 &= 布尔与赋值 ^= 位异或赋值 ^= 布尔异或赋值 |= 位或赋值 |= 布尔或赋值 3.1.3 表达式的提升和转换 Java语言不支持变量类型间的自动任意转换,有时必须显式地进行变量类型的转换。一般的原则是,变量和表达式可转换为更一般的形式,而不能转换为更受限制的形式。例如,int型表达式可看作是long型的;而long型表达式当不使用显式转换时是不能看作int型的。通常,如果变量类型至少与表达式类型一样(即位数一样多),就可以认为表达式是赋值相容的。 例3-7 类型转换示例。 long bigval = 6; //6是整型量,该语句正确 int smallval = 99L; //99L是长整型量,该语句错误 float z = 12.414F; //12.414F是浮点量,该语句正确 float z1 = 12.414; //12.414是双精度量,该语句错误 99L是长整型量,smallval是int型量,赋值不相容。同样,12.414是双精度型的,不能赋给单精度变量z1。 当表达式不是赋值相容时,有时需进行转换以便让编译器认可该赋值。例如,为让一个long型值“挤”入int型变量中,显式转换如下: long bigValue = 99L; int squashed = (int) (bigValue); 转换时,目标类型用括号括起来,放到要修改的表达式的前面。为避免歧义,被转换的整个表达式最好也用括号括起来。 3.1.4 数学函数 表达式中还经常出现另一类元素,这就是数学函数。Java语言提供了数学函数类 Math,其中包含了常用的数学函数,读者可以按需调用。下面列出几个常用的函数调用情况: Math.sin(0) //返回0.0,这是double类型的值 Math.cos(0) //返回1.0 Math.tan(0.5) //返回0.5463024898437905 Math.round(6.6) //返回7 Math.round(6.3) //返回6 Math.ceil(9.2) //返回10.0 Math.ceil(-9.8) //返回-9.0 Math.floor(9.2) //返回9.0 Math.floor(-9.8) //返回-10.0 Math.sqrt(144) //返回12.0 Math.pow(5,2) //返回25.0 Math.exp(2) //返回7.38905609893065 Math.log(7.38905609893065) //返回2.0 Math.max(560,289) //返回560 Math.min(560,289) //返回289 Math.random() //返回0.0到1.0之间双精度的一个随机数值 3.2 流?控?制 Java程序中的语句指示计算机完成某些操作,一个语句的操作完成后会把控制转给另一个语句。 语句是Java的最小执行单位,语句间以分号(;)作为分隔符。语句分为简单语句及复合语句,简单语句就是通常意义下的一条语句,即单语句;而复合语句是一对大括号“{}”括起来的语句组,也称为“块”,块中往往含有多个语句,块后没有分号。 下面介绍几种类型的语句,包括表达式语句、块、分支语句和循环语句等。 3.2.1 表达式语句 在Java程序中,表达式都可当做一个值,而有的表达式也可当作语句。 语句与表达式有相同的地方也有不同的地方。首先,有的表达式可以当作语句,但并不是所有的语句都是表达式;另外,每个表达式都会得到一个值,即表达式的计算结果。虽然语句也会有一个值,但这个值并不是语句的计算结果,而是运行结果。 下面是一些表达式语句: customer1 = new Customer(); point2 = new Point(); x = 12; x++; 方法调用通常返回一个值,一般用在表达式中。有的方法调用可直接当做语句。 例如: System.out.println("Hello World!"); 3.2.2 块 块是一对大括号“{}”括起来的语句组。例如,下面是两个块: { } { Point point1 = new Point(); int x = point1.x; } 第一个块是空块,其中不含任何语句。第二个块含两条语句。 方法体是一个块。块还用在流控制的语句(如if语句、switch语句及循环语句)中。 3.2.3 分支语句 分支语句根据一定的条件,动态决定程序的流程方向,从程序的多个分支中选择一个或几个来执行。分支语句共有两种:if语句和switch语句。 1.if语句 if语句是单重选择,最多只有两个分支。if语句的基本格式是 if (逻辑表达式) 语句1; [else 语句2; ] if语句中的语句1或是语句2可以是任意语句。其中else子句是可选的,当然还可以是if语句,这样的if语句称为嵌套的if语句。使用嵌套的if语句可以实现多重选择,即可以有多个分支。 if关键字之后的逻辑表达式必须得到一个逻辑值,不能像其他语言那样以数值代替。因为Java不提供数值与逻辑值之间的转换。例如,C语言中的语句形式: int x = 3; if (x) {...} 在Java程序中应该写作: int x = 3; if (x!=0) {...} if语句的含义是:当逻辑表达式结果为true时,执行语句1,然后继续执行if后面的语句。当逻辑表达式为false时,如果有else子句,则执行语句2,否则跳过该if语句,继续执行后面的语句。语句1和语句2既可以是单语句,也可以是语句块。 下面的示例是if语句的常见形式,其中形式3就是常见的if语句的嵌套。 形式1: if (逻辑表达式) { //逻辑表达式为true时要执行的语句; } 形式2: if (逻辑表达式) { //逻辑表达式为true时要执行的语句; } else { //逻辑表达式为false时要执行的语句; } 形式3: if (逻辑表达式1) { //逻辑表达式1为true时要执行的语句; } else if (逻辑表达式2) { //逻辑表达式1为false,且逻辑表达式2为true时要执行的语句; } ... else { //前面的逻辑表达式全为false时要执行的语句; } 例3-8 if语句示例。 1 int count; 2 count = getCount(); //程序中定义的一个方法,返回一个整型值 3 if (count < 0) { 4 System.out.println("Error: count value is negative!"); 5 } 6 else { 7 System.out.println("There will be " + count + 8 " people for lunch today."); 9 } if语句可以嵌套,嵌套时,由于else子句是可选的,故if与else的个数可能不一致,这就存在配对匹配的问题。else对应的是哪个if呢?在什么条件下执行其后的语句呢?如果不明确else与哪个if对应,就不能做出正确的判断,程序的运行结果也会有差异。与大多数语言的规定相同,Java规定else子句属于逻辑上离它最近的if语句。 例3-9 嵌套 if语句示例。 1 if (firstVal == 0) 2 if (secondVal == 1) 3 firstVal++; 4 else 5 firstVal--; 第4行的else子句与第2行的if配对,当firstVal为0且secondVal不为1时,执行firstVal??语句。如果想改变else的匹配关系,可以使用“{ }”改变语句结构。 例3-10 改变匹配关系。 1 if (firstVal == 0){ 2 if (secondVal == 1) 3 firstVal++; 4 } 5 else 6 firstVal--; 这次,else子句与第1行的if配对,当firstVal不为0时执行firstVal??操作。 2.switch语句 前面已经看到,使用if语句可以实现简单的分支判断,并进而执行不同的语句。当需要进行多种条件的判断时,可以使用嵌套的if语句来实现。当然,这样的语句写起来较烦琐,最主要的是它的条件判定不太直观。实际上,为了方便地实现多重分支,Java语言还提供了switch语句。它的含义与嵌套的if语句类似,只是格式上更加简洁。switch语句的语法格式是 switch (表达式) { case c1: 语句组1; break; case c2: 语句组2; break; ... case ck: 语句组k; break; [default: 语句组; break;] } 这里,表达式的计算结果必须是int型或字符型,即是int型赋值相容的。当用byte、short或char类型时,要进行提升。Java规定switch语句不允许使用浮点型或long型表达式。c1,c2,…,ck是int型或字符型常量。default子句是可选的,并且,最后一个break语句完全可以不写。 switch语句的语义是:计算表达式的值,用该值依次和c1,c2,…,ck相比较。如果该值等于其中之一,例如ci,那么执行case ci之后的语句组i,直到遇到break语句跳到switch之后的语句。如果没有相匹配的ci,则执行default之后的语句。也可以将default看作一个分支,即前面的条件均不满足时要执行的语句。switch语句中各ci之后的语句既可以是单语句,也可以是语句组。不论执行哪个分支,程序流都会顺序执行下去,直到遇到break语句为止。 例3-11 switch语句示例。 //colorNum是整型变量 switch (colorNum) { case 0: setBackground(Color.red); break; case 1: setBackground(Color.green); break; default: setBackground(Color.black); break; } 例3-11根据colorNum的值来设置背景色,其值为0时设置为红色,其值为1时设置为绿色,否则设置为黑色。这里使用了Java预设的一个类Color。 实际上,switch语句和if语句可以互相代替。例如例3-11也可以用if语句实现。 例3-12 替换为if语句。 if (colorNum == 0) setBackground(Color.red); else if (colorNum == 1) setBackground(Color.green); else setBackground(Color.black); 例3-13的两段程序实现的逻辑相同,都是根据month的值返回该月的天数,当然这里只处理平年的情况,没有考虑闰年的特殊处理。 例3-13 switch语句与if语句的等价性示例。 使用if语句: static int daysInMonth(int month) { if (month == 2) return(28); if ((month == 4) || (month == 6) || (month == 9) || (month == 11)) return(30); return(31); } 使用switch语句: static int daysInMonth(int month) { int days; switch(month) { case 2: days = 28; break; case 4: case 6: case 9: case 11: days = 30; break; default: days = 31; } return(days); } 程序3-4是switch语句的应用实例。该程序输出第一个命令行参数首字符的分类信息,并进一步输出该字符。 程序3-4 1 class SwitchTest{ 2 public static void main(String [] args) { 3 char ch = args[0].charAt(0); 4 switch (ch) { 5 case '0' : case '1' : case '2' : case '3': 6 case '4' : case '5' : case '6' : case '7': 7 case '8' : case '9' : 8 System.out.println( 9 "The character is digit " + ch); 10 break; 11 12 case 'a' : case 'b' : case 'c' : case 'd': 13 case 'e' : case 'f' : case 'g' : case 'h': 14 case 'i' : case 'j' : case 'k' : case 'l': 15 case 'm' : case 'n' : case 'o' : case 'p': 16 case 'q' : case 'r' : case 's' : case 't': 17 case 'u' : case 'v' : case 'w' : case 'x': 18 case 'y' : case 'z' : 19 System.out.println( 20 "The char is lowercase letter " + ch); 21 break; 22 23 case 'A' : case 'B' : case 'C' : case 'D': 24 case 'E' : case 'F' : case 'G' : case 'H': 25 case 'I' : case 'J' : case 'K' : case 'L': 26 case 'M' : case 'N' : case 'O' : case 'P': 27 case 'Q' : case 'R' : case 'S' : case 'T': 28 case 'U' : case 'V' : case 'W' : case 'X': 29 case 'Y' : case 'Z' : 30 System.out.println( 31 "The char is uppercase letter " + ch); 32 break; 33 default: System.out.println("The character" + ch 34 + " is neither a digit nor a letter."); 35 } 36 } 37 } 当主程序执行时,如果第一个命令行参数的首字符分别是数字、小写字母及大写字母,系统就会显示这个首字符。如果输入的是非数字或字母,则提示该字符不是数字或字母。输出如图3-4所示。 图3-4 程序3-4的运行结果 如果不写上述方法中的最后一个break语句(第32行),则程序执行完第30、31行后将不停止,一直执行下去。程序的输出如图3-5所示。 图3-5 去掉break语句的运行结果 3.2.4 循环语句 循环语句控制程序流多次执行一段程序。Java?语言提供?3?种循环语句:for?语句、 while语句和do语句。 1.for循环 for语句的语法格式是 for (初始语句; 逻辑表达式; 迭代语句) 语句; //循环体 初始语句和迭代语句中可以含有多个语句,各语句间以逗号分隔。for语句括号内的3个部分都是可选的,逻辑表达式为空时,默认规定为恒真。 for语句的语义是:先执行初始语句,判断逻辑表达式的值。当逻辑表达式为真时,执行循环体语句,执行迭代语句,然后再去判断逻辑表达式的值。这个过程一直进行下去,直到逻辑表达式的值为假时,循环结束,转到for之后的语句。for语句中定义的循环控制变量只在该块内有效。 例3-14 for语句示例。 1 for (int i = 0; i < 3; i++) { 2 System.out.println("Are you finished yet?"); 3 } 4 System.out.println("Finally!"); 该段程序共执行3次第2行的输出语句(i为0、1、2时)。当i等于3时,逻辑表达式的值为假,退出循环,执行第4行语句。程序输出结果为 Are you finished yet? Are you finished yet? Are you finished yet? Finally! 如果逻辑表达式的值永远为真,则循环会无限制地执行下去,直到系统资源耗尽为止。 例3-15 无限循环示例。 for ( ; ; ) System.out.println("Always print!"); 上述语句等价于: for ( ; true ; ) System.out.println("Always print!"); 这段循环不会停止。 例3-16是初始语句及迭代语句包含多个语句时的情况。 例3-16 初始语句和迭代语句示例。 int sumi = 0, sumj = 0; for (int i = 0, j = 0; j < 10; i++, j++) { sumi += i; sumj += j; } 2.while循环 for语句中常常用循环控制变量显式控制循环的执行次数。当程序中不能明确地指明循环的执行次数时,可以仅用逻辑表达式决定循环的执行与否。这样的循环可用while语句实现。 while语句的语法格式是 while (逻辑表达式) 语句; //循环体 与if语句一样,while语句中的逻辑表达式亦不能用数值代替。 while语句的语义是:计算逻辑表达式,当逻辑表达式为真时,重复执行循环体语句,直到逻辑表达式为假时结束。如果第一次检查时逻辑表达式为假,则循环体语句一次也不执行。如果逻辑表达式始终为真,则循环不会终止。 例3-14的for语句可以改写为例3-17中的while语句。 例3-17 while语句示例。 int i = 0; while (i < 3) { System.out.println("Are you finished yet?"); i++; } System.out.println("Finally!"); 3.do循环 do语句与while语句很相似。它把while语句中的逻辑表达式移到循环体之后。do语句的语法格式是 do 语句; //循环体 while (逻辑表达式); do语句的语义是:首先执行循环体语句,然后判断逻辑表达式的值。当表达式为真时,重复执行循环体语句,直到表达式为假时结束。不论逻辑表达式的值是真是假,do循环中的循环体都至少执行一次。 例3-18 do语句示例。 //do 语句 int i = 0; do { System.out.println("Are you finished yet?"); i++; } while (i < 3); System.out.println("Finally!"); 实际上,for、while及do语句可互相替代。例如: do 语句1; while (逻辑表达式); 等价于: 语句1; while(逻辑表达式) 语句1; 3.2.5 break语句与continue语句 Java语言抛弃了有争议的goto语句,代之以两条特殊的流控制语句:break语句和continue语句,它们用在分支语句或循环语句中,使得程序员更方便控制程序执行的方向。 1. 标号 标号可以放在for、while或do语句之前,其语法格式为 标号:语句; 2.break语句 break语句可用于3类语句中,一类是switch语句,一类是for、while及do等循环语句,还有一类是块语句。在switch语句及循环语句中,break的语义是跳过本块中余下的所有语句,转到块尾,执行其后的语句。 例3-19 break语句示例。 for (int i = 0; i < 100; i++) { if (i == 5) break; System.out.println("i= " + i); } 循环控制条件控制循环应该执行100次(i为0~99)。当i等于5时,执行break语句,它跳过余下的语句,结束循环。实际上,循环体System.out.println只执行了5次(i从0到4)。 break语句的第3种使用方法是在块中和标号配合使用,其语法格式为 break 标号; 其语义是跳出标号所标记的语句块,继续执行其后的语句。这种形式的break语句多用于嵌套块中,控制从内层块跳到外层块之后。 再看例3-20和程序3-5。 例3-20 break语句与标号示例。 int x; out: for (i=0; i<10; i++) { x=10; while (x<50) { x++; if (i*x == 400) break out; } } 程序3-5 class Break { public static void main (String args[]){ int i, j = 0, k = 0, h; label1: for(i = 0; i < 100; i++, j += 2) label2: { label3: switch(i%2) { case 1: h=1; break; default:h=0; break; ? } ? if(i==50) break label1; } System.out.println("i=" + i); } } 程序执行后,根据i的值进行判断。如果i是奇数,则h为1;如果i是偶数,则h为0。switch语句中的break语句都只是跳过switch本身,并没有跳出for循环。当i增到50时,进入if语句块,执行的结果是跳出label1标记的语句块,即for语句块。接下来的语句是打印语句,输出i 的值。运行结果如图3-6所示。 图3-6 程序3-5的运行结果 3.continue语句 在循环语句中,continue可以立即结束当次循环而执行下一次循环,当然执行前要先判断循环条件是否满足。 continue语句也可以和标号一起使用,其语法格式为 continue 标号; 它立即结束标号标记的那重循环的当次执行,开始下一次循环。这种形式的语句多用于多重循环中。 例3-21 continue语句示例。 outer: for (int i = 0; i < 10; i++) {