第3章 流程控制语句

流程就是指程序执行的顺序,流程控制就是指通过控制程序执行的顺序实现要求的功能。流程控制部分是程序中语法和逻辑的结合,也是程序中最灵活的部分,是判断一个程序员的能力的主要方面。众所周知,算法是程序逻辑的核心,而算法的绝大部分代码都是流程控制实现的。本章介绍Java中控制程序流程的语句,包括条件语句、循环语句、转移(分支)语句等。

实例9 打印任一年日历

在程序中,需要根据某个条件重复地执行某项任务若干次,直到满足或不满足某条件为止,这就是程序流程的控制。流程控制可以使程序员用很少的语句,让计算机重复完成大量的计算,而且使程序的结构在逻辑上更清晰易读。经常用到的控制流程的语句包括for循环、while循环、do-while循环、多路分支switch-case、if-else等。下面将综合运用这些流程语句编写任一年的日历。

技术要点

本实例主要说明如何使用控制流程语句及它们的使用规范。技术要点如下:

• for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,也可以用于循环次数不确定而只给出循环条件结束的情况,可以完全替代while语句。一般形式为:for(循环变量赋初值;循环条件;循环变量改变值)。

• while的一般形式为:

while(condition){语句}

while的执行流程:求出表达式condition的值,若值为真,则执行循环体,直到表达式condition的值为假,就退出循环。while是先判断后执行。

• do-while的一般形式为:

do{语句}while(condition);

无论表达式的值是真是假,其循环体总先被执行一次,然后再判断循环结束条件。

• switch-case语句用来专门处理“多中择其一”的情况语句,故又称为多路选择语句或开关语句。在这种情况下,使用switch语句写出的程序往往比使用if-else语句写的程序更简洁、清晰,且不易出错。

• if语句用来判定所给定的条件是否满足,根据判定的结果(真或假)决定执行哪块语句。

实现步骤

(1)新建一个类名为TextControl.java。

(2)代码如下所示:

package com.zf.s3                                    //创建一个包
import java.io.*;
public class TextControl{                            //操作打印任一年日历的类
    static int year, monthDay, weekDay;              //定义静态变量,以便其他类调用
    public static boolean isLeapYear(int y) {        //判断是否是闰年
          return ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0));
    }
    public static int firstDay(int y) {              //计算该年第一天是星期几
          long n = y * 365;
          for (int i = 1; i < y; i++)
              if (isLeapYear(i))                     //判断是否是闰年
                    n += 1;
          return (int) n % 7;
    }
    public static void printWeek(){                  //打印表头
          System.out.println("===========================");
          System.out.println("日 一 二 三 四 五 六");
    }
    public static int getMonthDay(int m){            //获取每个月的天数
          switch (m) {
          case 1:
          case 3:
          case 5:
          case 7:
          case 8:
          case 10:
          case 12:
              return 31;
          case 4:
          case 6:
          case 9:
          case 11:
              return 30;
          case 2:
              if (isLeapYear(year))                 //判断是否是闰年
                    return 29;
              else
                    return 28;
          default:
              return 0;
          }
    }
    public static void printMonth(){                //分别按不同条件逐月打印
          for (int m = 1; m <= 12; m++)             //循环月份
          {
              System.out.println(m + "月");
              printWeek();
              for (int j = 1; j <= weekDay; j++){//按每个月第一天是星期几打印相应的空格
                    System.out.print("    ");
              }
              int monthDay = getMonthDay(m);        //获取每个月的天数
              for (int d = 1; d <= monthDay; d++) {
                  if (d < 10)                       //以下4行对输出格式化
                        System.out.print(d + "   ");
                  else
                        System.out.print(d + " ");
                  weekDay = (weekDay + 1) % 7;      //每打印一天后,反映第二天是星期几
                  if (weekDay == 0)                 //如果第二天是星期天,便换行
                        System.out.println();
              }
              System.out.println('\n');
          }
    }
    public static void main(String[] args)throws IOException{//Java程序的主入口处
          System.out.print("请输入一个年份:");
          InputStreamReader ir;                     //以下接收从控制台的输入
          BufferedReader in;
          ir = new InputStreamReader(System.in);
          in = new BufferedReader(ir);
          String s = in.readLine();
          year = Integer.parseInt(s);
          weekDay = firstDay(year);                 //计算该年第一天是星期几
          System.out.println("\n           " + year + "年           ");
          printMonth();
    }
}

(3)运行结果如下所示:

请输入一个年份:2003
            2003年
1月
===========================
日 一 二  三 四 五 六
         1  2  3  4
5  6  7  8  9  10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
2月 =========================== 日 一 二 三 四 五 六 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
3月 =========================== 日 一 二 三 四 五 六 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ⋯

源程序解读

(1)isLeapYear()方法根据传入的年份被4整除但不能被100整除或能被400整除来判断是否是闰年。

(2)firstDay()方法判断传入的年份是否是闰年,如果是闰年则天数要加1,再取得除7的余数,余数即为星期几的表示。

(3)getMonthDay()方法根据大月小月以及2月份的月份天数的不同,运用switch-case分路分支的流程判断。注意:switch中的字符是int、short、byte、char,不能用long、String。如果case的值为空,没有break语句,则循环一直往下寻找直到遇到return;在没有符合匹配的字符串时,则进入default语句。

(4)在程序的main()方法中,运用控制台输入数值,具有很好的灵活性和可操作性。将所输入的文本从流中InputStream(字节流)传给InputStreamReader(字符流)再放到BufferStream(缓冲流)。这样有助于读完数据释放已分配的内存。

实例10 控制台输出几何图形

几何图形基本上包括长方形、三角形、菱形、梯形等,但如何通过程序输出指定的图形?本节实例将讲解如何输出直角三角形、菱形以及由规律数字组成的直角三角形。

技术要点

本实例主要运用了for的双重循环实现图形的显示。技术要点如下:

• 运用for双重循环:外循环可以控制行数,内循环在行的基础上控制每列的数目。

• 输出图形的多样性在于运用数学逻辑控制每行每列的输出,这也是本实例锻炼开发人员的逻辑思维的目的所在。

实现步骤

(1)新建一个类名为TextPrintPicture.java。

(2)代码如下所示:

package com.zf.s3;                                 //创建一个包
public class TextPrintPicture {                    //操作控制台输出图形的类
    public static void printRightangle(int row){   //输出直角图形
          for(int i=1;i<row;i++){                  //外层循环row次,输出row行
              for(int j=1;j<=i;j++){       //控制本次输出的"*"数目,这个数目由i决定
                    System.out.print("*");
              }
              System.out.println();                //每输完一行就要换行
          }
      }
      public static void printLozenge(int row){    //输出菱形图形
          if(row>=1){                              //判断传入的行数
              int i,j;
                for(i=1; i<=row; i++){        //先输出上面的正三角形
                    for(j=1; j<=row-i; j++)   //控制本次输出的空格数,注意循环控制表达式
                      System.out.print(" ");
                    for(j=1; j<=2*i-1; j++)   //控制本次输出的"*"数目,注意循环控制表达式
                      System.out.print("*");
                    System.out.println();     //每输完一行就要换行
                }
                for(i=1;i<=row;i++){          //输出下面的正三角形
                for(j=1;j<=i;j++)
                      System.out.print(" ");  //打印左边的空格
                for(j=1;j<=2*(row-i)-1;j++)   //控制本次输出的"*"数目,注意循环控制表达式
                      System.out.print("*");
                System.out.println();         //每输完一行就要换行
                }
          }
      }
      public static void printNumberRightangle(int row){   //输出数字直角图形
          for(int i=1;i<=row;i++){                         //外层循环row次,输出row行
              for(int x=1;x<i;x++)                         //数字由小到大排列显示
                    System.out.print(x);
              for(int j=i;j!=0;j--)                        //数字由大到小排列显示
                    System.out.print(j);
              System.out.println();                        //每输完一行就要换行
          }
      }
      public static void main(String []args){              //Java程序主入口方法
          System.out.println("1.输出直角图形");
          printRightangle(5);                              //输出行数为5的直角图形
          System.out.println("2.输出菱形图形");
          printLozenge(5);                                 //输出行数为2*5-1的菱形
          System.out.println("3.输出数字直角图形");
          printNumberRightangle(8);                        //输出行数为8的数字直角图形
      }
}

(3)运行结果如下所示:

1.输出直角图形
*
**
***
****
2.输出菱形图形
        *
      * * *
    * * * * *
  * * * * * * *
* * * * * * * * *
  * * * * * * *
    * * * * *
      * * *
        *
3.输出数字直角图形
1
121
12321
1234321
123454321

源程序解读

(1)输出的第一个直角三角形,每行输出的“*”都在依次增加,很容易想到用双层循环来实现:外层循环控制输出的行数,即为传入的参数值;内层循环控制本行输出的“*”数目,这个数目恰好是本行的行号;每一行输出完毕后,需要输出一个换行符。

(2)输出的第二个菱形,可以把它分成两个正三角形:上面一个的行数是与传入参数值相等的正三角形,下面一个的行数是(传入参数值-1)的倒三角形。对于上面的正三角形,若以i代表行号,那么每行“*”的数目等于2i-1。对于下面的倒三角形,行数是上三角形行数-1,这样“*”的数目等于2(row-i)-1,其中row就是参数的值。对于空格:上三角形输出的空格越来越少,下倒三角形输出的空格越来越多,也是运用双重循环进行逻辑控制。

(3)输出的数字直角三角形,也是把它分为两部分完成。具体编写与输出菱形类似。

实例11 杨辉三角

杨辉三角是一个由数字排列的三角形数表。本节介绍如何实现控制台输出杨辉三角形。

技术要点

实现杨辉三角技术要点如下:

杨辉三角最本质的特征是:除两侧元素均为1以外,其余每个位置上的元素值为其正上方元素与左上角元素之和,用数组来描述则为a[i][j]=a[i-1][j-1]+a[i-1][j]。

实现步骤

(1)新建一个类名为TextTriangle.java。

(2)代码如下所示:

package com.zf.s3;                                          //创建一个包
public class TextTriangle {                                 //操作杨辉三角的类
    public static void yanghui(int a[][], int ROW) {        //输出杨辉三角
          for (int i = 0; i <= ROW; i++){                   //循环行数
              for (int j = 0; j <= a[i].length - 1; j++) {  //在行基础上循环列数
                    if (i == 0 || j == 0 || j == a[i].length - 1)
                        a[i][j] = 1;                        //将两侧元素设为1
                    else                             //元素值为其正上方元素与左上角元素之和
                        a[i][j] = a[i - 1][j - 1] + a[i - 1][j];
                }
          }
          for (int i = 0; i <= ROW; i++) {                     //循环行数
                for (int j = 0; j <= a[i].length - 1; j++)     //在行基础上循环列数
                    System.out.print(a[i][j] + " ");           //输出
                System.out.println();                          //换行
          }
      }
      public static void main(String args[]) {                 //Java程序主入口处
          final int ROW = 5;                                   //设置行数
          int a[][] = new int[ROW + 1][];                      //声明二维数组,行数为6
          for (int i = 0; i <= ROW; i++) {                     //循环初始化数组
                a[i] = new int[i + 1];
          }
          yanghui(a, ROW);                                     //调用方法显示杨辉三角
      }
}

(3)运行结果如下所示:

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 1 0 1 0 5 1

源程序解读

在main()方法中声明二维数组并运用循环对数组赋值。调用yanghui()方法,传入数组和行数作为参数。在yanghui()方法中,运用循环将要输出的三角的两侧元素值设为1,再运用杨辉三角的性质:每个位置上的元素值为其正上方元素与左上角元素之和。将元素进行运算赋值。最后将这个二维数组元素循环显示出来,即为杨辉三角。

实例12 拜访过程(break和continue)

本实例通过模拟拜访的过程,讲解break语句与continue语句,并运用for循环与continue循环。通过两种语句的跳出循环进行比较联系和区别。

技术要点

运用break和continue跳出循环的技术要点如下:

• break语句主要用于跳出循环语句。continue通常与if语句联用,在满足条件时结束当次循环。continue可以方便地编写除把不满足要求的元素去除的逻辑,可以称为“反逻辑”。

• break与continue的区别在于:break是直接跳出循环,而continue只是中断当次循环,如果后面的条件满足要求,还会继续执行。

实现步骤

(1)新建一个类名为TextBreakAndCon.java。

(2)代码如下所示:

package com.zf.s3;                                //创建一个包
public class TextBreakAndCon {                    //操作break与continue语句
    private static void showBreak(){              //测试break语句
          label:                                  //声明标签,后面要加":"
          for(int i=1,j=0;i<5;i++){               //定义访问次数计数器i和访问页面计数器j
              System.out.println("第"+i+"次拜访我");
              while(j<5){
                    j++;
                    System.out.println("Hello,打开了第"+j+"扇门");
                    if(j>2)
                        break label;
              }
          }
    }
    private static void showContinue(){           //测试continue语句
          for(int i=1;i<5;i++){                   //循环测试continue
              if(i==2)
                    continue;
              System.out.println("欢迎第"+i+"次拜访我");
          }
    }
    public static void main(String []args){       //Java程序主入口处
          System.out.println("1.测试break语句");
          showBreak();                            //调用方法
          System.out.println("2.测试continue语句");
          showContinue();                         //调用方法
    }
}

(3)运行结果如下所示:

1.测试break语句
第1次拜访我
Hello,打开了第1扇门
Hello,打开了第2扇门
Hello,打开了第3扇门
2.测试continue语句
欢迎第1次拜访我
欢迎第3次拜访我
欢迎第4次拜访我

源程序解读

(1)showBreak()方法中定义标签,运用嵌套的for循环和while循环。当打开的门数大于2时,执行break标签语句,跳转到标签语句块的末尾,结束此循环,所以只输出一次拜访和打开3扇门。

(2)showContinue()方法中运用for循环,当拜访次数为2时,执行continue语句,跳出当前循环。再判断后面的条件是否合适,以决定是否继续进行循环。

常见问题 for循环初始化问题

将声明的数组或集合进行初始化,通常有两种方法:用关键字new,在声明数组的时候进行初始化;二是在循环中对数组或集合进行初始化。

技术要点

• 初始化格式如int[]a=new int{1,2,3,4,5};或用for循环依次进行初始化。

• 对基本数据类型可以进行匿名数组初始化。

实现步骤

(1)新建一个类名为QuestionOne.java。

(2)代码如下所示:

package com.zf.s3;                                      //创建一个包
import java.awt.Panel;                                  //引入包
public class QuestionOne {                              //操作for循环问题的类
    public static void main(String []args){             //Java程序的主入口处
          Panel[] panels = new Panel[]{ p1,p2,p3,p4};   //声明一个数组
          for (int i = 0 ; i < panels.length ; i++) {   //循环对数组进行初始化
              panels[i] = new Panel();
          }
    }
}

(3)运行结果如下所示:

编译不通过

源程序解读

语句Panel[]panels=new Panel[]{p1,p2,p3,p4};是想用匿名数组进行初始化,这样是不对的。如果是用匿名数组进行初始化可以这样:

Int []array=new int[]{1,2,3,4};
Panel []p=new Panel[]{new Pannel(),new Panel()};

基本数据类型那样初始化可以。