Java核心技术卷1-3.8 控制流程

    与任何程序设计语言一样,Java使用条件语句和循环机构确定控制流程。本节先讨论条件语句,然后讨论循环语句,最后介绍看似有些笨重的switch语句,当下需要对某个表达式的多个值进行检测时,可以使用switch语句。

C++注释:Java的控制流程结构与C和C++的控制流程结构一样,只有很少的例外情况。没有goto语句,但break语句可以带标签,可以利用它实现从内层循环跳出的目的(这种情况C语言采用goto语句实现)。另外,Java SE 5.0还添加了一种变形的for循环,在C或C++中没有这类循环。它有点类似于C#中的foreach循环3

3.8.1 块作用域

    在深入学习控制结构之前,需要了解块(block)的概念。

块(即复合语句)是指由一对花括号括起来的若干条简单的Java语句。块确定了变量的作用域。一个块可以嵌套在另一个块中。下面就是在main方法块中嵌套另一个语句块的示例。

public static void main(String[] arg) 
{
    int n;
    ...
    {
        int k;
        ...    
    }//k is only defined up to here
}
复制代码

但是,不能再嵌套的两个块中声明同名的变量。例如,下面的代码就有错误,而无法通过编译:

public static void main(String[] arg) 
{
    int n;
    ...
    {
        int k;
        int n;//error--can't redefine n in inner block
        ...    
    }
}
复制代码

C++注释:在C++中,可以在嵌套的块中重定义一个变量。在内层定义的变量会覆盖在外层定义的变量。这样,有可能会导致程序设计错误,因此在Java中不允许这样做

3.8.2 条件语句

在Java中,条件语句的格式为

if(condition) statement
复制代码

这里的条件必须用括号括起来。

    与绝大多数程序设计语言一样,Java常常希望在某个条件为真时执行多条语句。在这种情况下,应该使用块语句(block statement),格式为

{
 statment1,
 statment2   
 }
复制代码

例如:

if (yourSales >= target) 
{
    performance = "Satisfactory";
    bonus = 100;    
}
复制代码

当yourSales大于或等于target时,将执行括号中的所有语句(请参看图3-7)。

                                          

注释:使用块(有时称为复合语句)可以在Java程序结构中原本只能放置一条简单语句的地方可以放置多条语句。

    在Java中,比较常见的条件语句格式如下所示(请参看图3-8):

if (condition) statement1 else statement2
复制代码

                                     

例如:

if (yourSales >= target) 
{
    performance = "Satisfactory";
    bonus = 100; 
}
else 
{
    performance = "Unsatisfactory";
    bonus = 0;
}
复制代码

其中else部分可选的。else子句与最邻近的if构成一组。因此,在语句

if (x <= 0) if (x==0) sign = 0; else sign=-1;
复制代码

中else与第2个if配对。当然,用一对括号将会使这段代码更加清晰:

if (x <= 0) { if (x==0) sign = 0; else sign=-1; }
复制代码

重复地交替出现if...else if...是一种很常见的情况(请参看图3-9)。例如:

                                    

if (yourSales >= 2 * target) 
{
    performance = "Excellent";
    bonus = 1000; 
}
else if (yourSales >= 1.5 * target) 
{
    performance = "Fine";
    bonus = 500;
}
else if (yourSales >= target) 
{
    performance = "Satisfactory";
    bonus = 100;
}
else 
{
  System.out.println("You're fired");  
}
复制代码

3.8.3 循环

    当条件为true时,while循环执行一条语句(也可以是一个语句块)。常用的格式为

while (condition) statement
复制代码

如果开始循环条件的值就为false,则while循环体一次也不执行(请参看图3-10)。

                                          

    程序清单3-3中的程序将计算需要多长时间才能够存储一定数量的退休金,假定每年存入相同数量的金额,而且利率是固定的。

    在这个示例中,增加了一个计数器,并在循环体重更新当前的累计数量,直到总值超过目标值为止。

while (balance < goal) 
{
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;
    years++;
}
System.out.println(years + "years.");
复制代码

(千万不要使用这个程序安排退休计划。这里忽略了通货膨胀和所期望的生活水准。)

  **while循环语句首先检测循环条件。因此,循环体中的代码有可能不被执行。如果希望循环体至少执行一次,则应该讲检测条件放在最后。使用do/while循环语句可以实现这种操作方式。**它的语法格式为:

do statment while (condition);
复制代码

    这种循环语句先执行语句(通常是一个语句块),再检测循环条件;然后重复语句,在检测循环条件,以此类推。在例3-4的代码中,首先计算退休账户中的余额,然后在询问是否打算退休:

do 
{
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;
    year++;
    //print current balance
    ...
    //ask if ready to retire and get input
    ...
}
while (input.equal("N"));
复制代码

只要用户回答"N",循环就重复执行(见图3-11)。这是一个需要至少执行一次的循环的很好示例,因为用户必须先看到余额才能知道是否满足退休所用

程序清单 3-3 Retirement/Retirement.java

import java.util.Scanner;

public class Retirement {
    public static void main(String[] args) {
        //read inputs
        Scanner i = new Scanner(System.in);
        System.out.println("How much money do you need to retire?");
        double goal = i.nextDouble();

        System.out.println("How much money wille you contribute every year?");
        double payment = i.nextDouble();

        System.out.println("Interest rate in %:");
        double interestRate = i.nextDouble();

        double balance = 0;
        int years = 0;

        //update account balance while goal isn't reached
        while (balance < goal) {
            //add this year's payment and interest
            balance += payment;
            double interest = balance * interestRate / 100;
            balance += interest;
            years++;
        }
        System.out.println("You can retire in " + years + "years.");
    }
}
复制代码

                                          

程序清单 3-4 Retirement/Retirement2.java

import java.util.Scanner;

public class Retirement2 {
    public static void main(String[] args) {
        //read inputs
        Scanner in = new Scanner(System.in);

        System.out.println("How much money wille you contribute every year?");
        double payment = in.nextDouble();

        System.out.println("Interest rate in %:");
        double interestRate = in.nextDouble();

        double balance = 0;
        int year = 0;

        String input;

        //update account balance while goal isn't reached
        do {
            //add this year's payment and interest
            balance += payment;
            double interest = balance * interestRate / 100;
            balance += interest;

            year++;

            //print current balance
            System.out.printf("After year %d, your balance is %,.2f%n", year , balance);

            // aks if ready to retire and get input
            System.out.println("Ready to retire? (Y/N) ");
            input = in.next();
        } while (input.equals("N"));
    }
}
复制代码

3.8.4 确定循环

    for循环语句是支持迭代的一种通用结构,利用每次迭代之后更新的计数器或类似的变量来控制迭代次数。如图3-12所示,下面的程序将数字1-10输出到屏幕上。

for (int i = 0; i <= 10; i++) 
    System.out.println(i);
复制代码

    for语句的第1部分通常用于对计数器初始化;第2部分给出每次新一轮循环执行前要检测的循环条件;第3部分指示如何更新计数器。

                                        

    与C++一样,尽管Java允许在for循环的各个部分放置任何表达式,但有一条不成文的规则:for语句的3个部分应该对同一个计数器变量进行初始化、检测和更新。若不遵守这一规则,编写的循环常常晦涩难懂。

    即使遵守了这条规则,也还有可能出现很多问题。例如,下面这个倒计数的循环:

for (int i = 10; i > 0; i--) 
    System.out.println("Counting down..." + i);
System.out.println("Blastoff!");
复制代码

警告:在循环中,检测两个浮点数是否相等需要格外小心。下面的for循环

    for (double x = 0; x != 10; x += 0.1) ...

可能永远不会结束。由于舍入的误差,最终可能得不到精确值。例如,在上面的循环中,因为0.1无法精确地用二进制表示,所以,x将从9.999 999 999 999 98跳到10.099 999 999 999 98。

    当在for语句的第1部分中声明了一个变量之后,这个变量的作用域就为for循环的整个循环体。

for (int i = 0; i <= 10; i++) {
    ...
}
// i no longer defined here
复制代码

    特别指出,如果在for语句内部定义一个变量,这个变量就不能再循环体之外使用。因此,如果希望在for循环体之外使用循环计数器的最终值,就要确保这个变量在循环语句的前面且在外部声明!

int i;
for (i = 0; i <= 10; i++) {
    ...
}
// i still defined here
复制代码

    另一方面,可以在各自独立的不同for循环中定义同名的变量:

for (int i = 0; i <= 10; i++) {
    ...
}
...
for (int i = 10; i <= 20; i++) {//ok to define another variable named i
    ...
}
复制代码

for循环语句只不过是while循环的一种简化形式。例如,

for (int i = 10; i > 0; i--) {
    System.out.println("Counting down..." + i);
}
复制代码

可以重写为:

int i = 10;
while (i > 0) {
    System.out.println("Counting down..." + i);
    i--;
}
复制代码

    程序清单3-5给出了一个应用for循环的典型示例。这个程序用来计算抽奖中奖的概率。例如,如果必须从1~50之间的数字中取6个数字来抽奖,那么会有 (50×49×48×47×46×45)/(1×2× 3×4×5×6) 种可能的结果,所以中奖的几率是1/15 890 700。祝你好运!

    一般情况下,如果从n个数字中抽取k个数字,就可以使用下列公式得到结果。

                         

下面的for循环语句计算了上面这个公式的值:

int lotteryOdds = 1;
for (int i = 1; i < k; i++) {
    lotteryOdds = lotteryOdds * (n-i+1) / i;
}
复制代码

注释:稍后将会介绍“通用for循环”(又称为for each循环),这是Java SE 5.0新增加的一种循环结构。

程序清单3-5 LotteryOdds/LotteryOdds.java

import java.util.Scanner;

public class LotteryOdds {
    public static void main(String[] args) {

        Scanner in = new Scanner(System.in);

        System.out.println("How much number do you need to draw?");
        int k = in.nextInt();

        System.out.println("What is the highest number you can draw?");
        int n = in.nextInt();
        /**
         * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
         */
        int lotteryOdds = 1;
        for (int i = 1; i < k; i++) {
            lotteryOdds = lotteryOdds * (n-i+1) / i;
        }
        System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
    }
}
复制代码

3.8.5 多重选择:switch 语句

在处理多个选项时,使用if/else结构显得有些笨拙。Java又一个与C/C++完全一样switch语句。

    例如,如果建立一个如图3-13所示的包含4个选项的菜单系统,就应该使用下列代码:

Scanner in = new Scanner(System.in);
System.out.print("Select an option (1,2,3,4) ");
int choice = in.nextInt();
switch (choice) {
    case 1:
        ...
        break;
    case 2:
        ...
        break;
    case 3:
        ...
        break;
    case 4:
        ...
        break;
    default:
        //bad input
        ...
        break;
}
复制代码

                                           

switch语句将从与选项值相匹配的case标签处开始执行直到遇到break语句,或者执行到switch语句的结束为止。如果没有相匹配的case标签,而有default子句,就执行这个子句。

警告:有可能触发多个case分支。如果在case分支语句的末尾没有break语句,那么就会接着执行下一个case分支语句。这种情况相当危险,常常会引发错误。为此,我们在程序中从不使用switch语句。

    如果你比我们喜欢switch语句,编译代码时可以考虑加上-Xlint:fallthrough选项,如下所示:

    javac -Xlint:fallthrough Test.java

    这样一来,如果某个分支最后缺少一个break语句,编译器就会给出一个警告信息。

    如果你确实正是想使用这种"直通式"(fallthrough)行为,可以为其外围加一个标注@SuppressWarnings("fallthrough")。这样就不会对这个方法生成警告了。(标注是为编译器或处理器Java源文件或类文件的工具挺高信息的一种机制。我们将在卷II的第13章详细讨论标注)

case标签可以是:

  • 类型为char、byte、short或int(或其包装器类Character、Byte、Short和Integer,这些包装器类将在第4章介绍)的常量表达式。
  • 枚举常量
  • 从Java SE 7开始, case标签还可以是字符串字面量。

例如:

String input = ...;
switch (input.toLowerCase()) {
    case "yes"://OK since Java SE 7
        ...
        break;
    ...
}
复制代码

    当在switch语句中使用枚举常量时,不必在每个标签中指明枚举名,可以由switch的表达式值确定。例如:

Size sz = ...;
switch (sz) {
    case SMALL://no need to use Size.SMALL
       ...
       break;
    ...
}
复制代码

3.8.6 中断控制流程语句

    尽管Java的设计者将goto作为留着字,但实际上并没有打算在语言中使用它。通常goto语句被认为一种拙劣的程序设计风格。当然,也有一些程序员认为反对goto的呼声似乎有些过分(例如,Donald Knuth就曾编著过一篇名为《Structured Programming with goto statements》的著名文章。这篇文章说:无限制地适用got语句确实是导致错误的根源,但在有些情况下,偶尔适用got跳出循环还是有益处的。Java设计者同意种看法,甚至在Java语言中增加了一条带标签break,以此来支持这种程序设计风格

    下面首先先看一下不带标签的break语句。与用于退出switch语句的break语句一样,它也可以用于退出循环语句。例如,

while(years <= 100) {
    balance += payment;
    double interest = balance * interestRate /100;
    balance += interest;
    if (balance >= goal) break;
    year++;
}
复制代码

    在循环开始时,如果years>100,或者在循环体中balnce>=goal,则退出循环语句。当然,也可以在不使用break的情况下计算years的值,如下所示:

while(years<=100 && balance < goal>) {
    balance+=payment;
    double interest = balance * interestRate /100;
    balance += interest;
    if (balance < goal)
        year++;
}
复制代码

    但是需要注意,在这个版本中,检测了两次balnce<goal。为了避免重复检测,有些程序员更加偏爱使用break语句。

    与C++不同,Java还提供了一种带标签的break语句,用于跳出多重嵌套循环语句。有些时候,在嵌套很深的循环语句中会发生一些不可预料的事情。此时可能更加希望跳到嵌套的所有循环语句之外。通过添加一些额外的条件判断实现各层循环的检测很不方便。

   这里有一个示例说明了break语句的工作状态。请注意,标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号

Scanner in = new Scanner(System.in);
int n;
read_data:
    while(...) // this loop statement is tagged with the label
    {
        ...
        for (...) // this inner loop is not labeled
        {
         System.out.print()           
        }    
    }
复制代码

    如果输入有误,通过执行带标签的break跳转到带标签的语句块末尾。对于任何使用break语句代码都需要检测循环是正常结束,还是由break跳出。

注释:事实上,可以将标签应用到任何语句中,甚至可以应用到if语句或者块语句中,如下所示:

label:

{

...

if (conditon) break label; //exits block

...

}

//jumps here when the break statment executes

因此,如果希望使用一条goto语句,并将一个标签放在想要跳到的语句块之前,就可以使用break语句!当然,并不提倡使用这种式。另外需要注意,只能跳出语句块,而不能跳入语句块。

    最后,还有一个contiue语句。与break语句一样,它将中断正常的控制流程。contiue语句将控制转移到最内层循环的首部。例如:

Scanner in = new Scanner(System.in);
while (sum < goal) 
{
    System.out.print("Enter a number: ");
    n = in.nextInt();
    if (n < 0) continue;
    sum += n;// not executed if n < 0
}
复制代码

如果n<0,则continue语句越过当前循环体的剩余部分,立刻跳到循环首部。

    如果将contiue语句用于for循环中,就可以跳到for循环的"更新"部分。例如,下面这个循环:

for (count = 1; count <= 100; count++)
{
 System.out.print("Enter a number, -1 to quit:");
 n = in.nextInt();
 if (n < 0) continue;
 sum += n;// not executed if n < 0
}
复制代码

如果n<0,则contiue语句跳到count++语句。

    还要一种带标签的contiue语句,将跳到与标签匹配的循环首部。

提示:许多程序员容易混淆break和contiue语句。这些语句完全是可选,即不使用它们也可以表达同样逻辑含义。在本书中,将不使用break和contiue。

猜你喜欢

转载自juejin.im/post/7110463481958105101