第三章 流程控制语句结构

96 阅读18分钟

流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。

程序设计中规定的三种流程结构:

顺序结构

  • 程序从上到下逐行地执行,中间没有任何判断和跳转。

分支结构

  • 根据条件,选择性地执行某段代码。
  • if…elseswitch-case 两种分支语句。

循环结构

  • 根据循环条件,重复性的执行某段代码。
  • forwhiledo-while 三种循环语句。
  • 补充:JDK5.0 提供了 foreach 循环,方便的遍历集合、数组元素。

一、顺序结构

顺序结构是所有流程控制语句结构中最基础的结构,顺序结构的程序从整体来看都是顺序执行的。

特点:上一行对某个变量的修改对下一行会产生影响。

示例代码:

public class Demo{
 public static void main (String[] args){
  int x = 1;
  int y = 2;
  System.out.println("x = "+ x);
  System.out.println("y = "+ y);

  //对x,y的值进行修改
  x += 1;
  y += x;
  System.out.println("x = "+ x);
  System.out.println("y = "+ y);
 }
}
// 代码运行结果:
x = 1
y = 2
x = 2
y = 4

【注意】: 输出语句

  • System.out.println("内容"); 语句在输出括号中的内容后会换行,即光标移动到下一行行首。
  • System.out.print("内容"); 语句在输出括号中的内容后不换行,即光标还停留在内容后。

无论是哪一种输出语句,括号中都只能有一个值,要么是一个常量值,要么是一个变量值,要么是一个表达式的计算结果。如果有多个值,那么应使用运算符 +​ 将它们连接起来。

二、分支结构

分支结构,是指程序中出现了多种选择,即某些语句可能执行,可能不执行,是否执行要看条件是否满足。

Java 主要提供两种分支结构:if 条件语句和 switch 选择语句。

1、if 条件语句

if ​语句通过对条件表达式的求值结果(true ​或 false​)来决定执行哪个代码块。

根据程序路径的复杂性,if 语句可分为单分支、双分支和多分支三种形式。

注意:

条件表达式的结果只能是布尔类型,支持如下几种形式:

  • 布尔型的变量。
  • 布尔型的常量值。
  • 布尔型的表达式,如关系表达式、逻辑表达式。

(1)单分支:if

当程序仅需在某个条件成立时执行特定操作时,使用单分支结构。

  • 执行逻辑:如果条件表达式为 true​,则执行其后的语句块;否则,跳过该语句块,继续执行后续代码,如图所示。

  • 语法格式
if(条件表达式){
 语句块; ///如果条件表达式为true将执行的语句

(2)双分支:if...else

当程序需要在条件成立与不成立时,分别执行不同的操作(二选一)时,使用双分支结构。

  • 执行逻辑:如果条件表达式为 true​,执行 if ​后的语句块;否则(为 false​),执行 else ​后的语句块。

  • 语法格式
if(条件表达式) { 
 语句块1; //如果布尔表达式的值为true,执行语句块1
}else {
 语句块2; //如果布尔表达式的值为false,执行语句块2
}

(3)多分支:if...else if...else

当程序需要在多个互斥的条件中选择一个来执行(多选一)时,使用多分支结构。

  • 执行逻辑:程序从上至下依次判断每个 if ​或 else if ​的条件表达式。一旦遇到第一个为 true ​的条件,就执行其对应的语句块,然后跳出整个多分支结构。如果所有条件都不为 true​,则执行可选的 else ​语句块。

  • 语法格式
if (条件表达式1) {
 语句块1; //如果布尔表达式 1的值为true执行代码
} else if (条件表达式2) {
 语句块2; //如果布尔表达式 2的值为true执行代码
}
...
}else if (条件表达式n) {
 语句块n; //如果布尔表达式 n的值为true执行代码
} else {
 语句块n+1; //如果以上布尔表达式都不为true执行代码
}
  • 注意事项

    • 可以有多个 else if​ 语句块。
    • 单独的 else​ 语句块只能放在最后,不可以提到前面,根据实际情况,单独的 else 语句块是可选的。
    • else if​ 语句块中,else​ 关键字不可以省略,否则就不再是多分支。
  • 特别注意

    • 当多个条件是互斥(没有交集)关系时,多个条件的顺序可以随意执行,即执行它们的先后顺序不会影响结果;
    • 当多个条件是包含(满足一个条件的情况完全包含在满足另一个条件的情况中,如 ≥90 分的成绩一定满足 ≥70 分)关系时,则按照"小上大下"或"子上父下"的条件编写,否则结果不同。

(4)嵌套

在 if 的语句块中,或者是在 else 语句块中,又包含了另外一个条件判断(可以是单分支、双分支、多分支),就构成了 嵌套结构

  1. 执行特点:

    • 如果是嵌套在 if 语句块中的,只有当外部的 if 条件满足,才会去判断内部的条件
    • 如果是嵌套在 else 语句块中的,只有当外部的 if 条件不满足,进入 else 后,才会去判断内部的条件
  2. 注意 :

    • 所有的流程控制语句都可以互相嵌套,互不影响
    • 语句块只有一条执行语句时,一对 {}可以省略,但建议保留
    • 当 if-else 结构是"多选一"时,最后的 else是可选的,根据需要可以省略
    • 从开发经验上讲,没有超过三层的嵌套

(5)案例

需求:

求 ax2+bx+c=0 方程的解,其中 a、b、c 分别为方程的参数。

提示:

可以使用系统函数 Math.sqrt(x)求 x 的平方根。

案例分析:

  1. 如果 a≠0,那么该方程是一个一元二次方程,利用一元二次方程的判别式 Δ=b24acb^2-4ac,可以知道以下几点。

    • b24acb^2-4ac>0 时,一元二次方程有两个实数解:x=b±b24ac2ax = \frac{-b\pm{\sqrt{b^2-4ac}}}{2a}
    • b24acb^2-4ac=0 时,一元二次方程有两个相同的实数解:b2a-\frac{b}{2a}
    • b24acb^2-4ac<0 时,一元二次方程在实数范围内无解。
  2. 如果 a=0 且 b≠0,那么该方程是一个一元一次方程,方程的解为:x=cbx=-\frac{c}{b}

  3. 如果 a=0 且 b=0,那么提示参数输入有误,无法构成方程。

代码演示:

2、switch 选择语句

switch-case​ 是提供多分支程序结构语句。当多分支中的条件表达式是对同一个变量或表达式进行等值判断时,往往使用它代替实现。

(1)switch-case 基本语法与执行流程

  • 执行逻辑switch ​首先计算括号内表达式的值,然后从上到下逐个与 case ​后的常量值进行匹配。

    • 入口:一旦找到匹配的 case​,程序就从该 case ​处进入并执行其后的语句。如果所有 case ​都不匹配,则从可选的 default ​处进入。
    • 出口:执行过程中遇到 break ​语句或整个 switch ​结构的右大括号 } ​时,执行结束。

  • 语法格式
switch(变量或表达式){
    case 常量值1:
        语句块1;
        //break;可选
    case 常量值2:
        语句块2;
        //break;可选

        ...  //可以有任意数量的case语句

    default:
        语句块n+1;
        //break;可选
}

(2)注意事项

  1. 支持的类型switch ​表达式的类型支持 byte​, short​, char​, int​。从 JDK 5.0 开始支持 enum​(枚举),从 JDK 7.0 开始支持 String​。
  2. case的要求case ​后面必须跟一个常量值,不能是变量或表达式。且同一个 switch ​块内,case ​的常量值必须唯一。
  3. break的作用break ​用于强制跳出 switch ​结构。
  4. case穿透:如果没有 break​,一旦匹配成功(表达式的值=常量值),则将贯穿执行下面的 case 或 default 中的语句,不再判断是否相等。直到遇到 break 或者整个 switch 语句结束,执行终止。 这就是所谓的“穿透”。
  5. default子句default ​是可选的,用于处理所有 case ​都不匹配的情况。它的位置灵活,但无论放在哪里,都会在所有 case ​匹配失败后才执行。习惯上将其放在末尾。

示例代码:

public class SwitchExample {

    public static void main(String[] args) {
        // 设置今天是星期四
        int day = 4;
        String dayString;

        System.out.println("今天是星期 " + day + ",从今天开始到周末还有哪些活动:");

        switch (day) {
            case 1:
                System.out.println("星期一: 开始新的工作周");
            case 2:
                System.out.println("星期二: 继续努力工作");
            case 3:
                System.out.println("星期三: 周中会议");
            case 4:
                System.out.println("星期四: 项目截止日期");
                // 这里没有 break 语句
            case 5:
                System.out.println("星期五: 轻松一下,准备过周末");
                // 这里没有 break 语句
            case 6:
                System.out.println("星期六: 外出游玩");
                // 这里没有 break 语句
            case 7:
                System.out.println("星期日: 好好休息");
                break; // 这个 break 会结束 switch 语句
            default:
                System.out.println("无效的日期");
                break;
        }
    }
}

输出:

今天是星期 4,从今天开始到周末还有哪些活动:
星期四: 项目截止日期
星期五: 轻松一下,准备过周末
星期六: 外出游玩
星期日: 好好休息

(3)if-else 语句与 switch-case 语句比较

  1. 结论:

    凡是使用 switch-case 的结构都可以转换为 if-else 结构。反之不成立。

  2. 开发经验:

    如果既可以使用 switch-case,又可以使用 if-else,建议使用 switch-case。因为效率稍高。

  3. 细节对比:

特性if-else​ 语句switch​ 语句
判断条件布尔表达式(true​/false​)同一个变量或表达式的等值判断
应用范围更广。可用于范围判断(>​, <​, !=​)和等值判断。较窄。仅限于对 byte​, short​, char​, int​, enum​, String ​类型的等值判断。
性能逐一判断,直到条件满足。对于多分支且等值判断的场景,效率通常稍高
特殊功能可利用 case 穿透特性,执行多个分支。

三、循环结构

循环语句具有在 某些条件​ 满足的情况下,反复执行​ 特定代码的功能。

Java 提供了三种主要的循环语句,每种都有其最适用的场景。分别是 for​、while​ 和 do-while

任何循环结构,无论是 for​、while​ 还是 do-while​,都由以下四个不可或缺的部分组成:

  1. 初始化表达式 (Initialization) :在循环开始前执行,通常用于声明和初始化循环控制变量。
  2. 循环条件 (Condition) :一个布尔表达式。每次循环迭代开始前都会进行求值,结果为 true​ 则执行循环体,为 false​ 则终止循环。
  3. 循环体 (Body) :需要重复执行的代码块。
  4. 迭代表达式 (Iteration/Update) :在每次循环体执行完毕后执行,通常用于更新循环控制变量,使其向终止条件趋近。

1、while​ 循环:先判断,后执行

  • 执行流程

    1. 执行初始化表达式。
    2. 判断循环条件。
    3. 若条件为 true​,执行循环体和迭代表达式。
    4. 返回步骤 2,重复此过程。
    5. 若条件为 false​,跳出循环。

  • 语法结构
// ① 初始化部分
while (② 循环条件部分) {
    // ③ 循环体部分;
    // ④ 迭代部分;
}
// 执行顺序: ① -> ② (true) -> ③ -> ④ -> ② (true) -> ... -> ② (false) -> 结束
  • 适用场景:适用于循环次数不确定,需要依赖某个条件来决定是否继续循环的场景。

  • 案例:纸张折叠

    • 需求:一张厚度为 0.1 毫米的纸,对折多少次后其厚度能超过珠穆朗玛峰的高度(8848.86 米)?
    • 分析:这是一个典型的循环次数未知的场景,循环的终止条件是“纸张厚度 > 山峰高度”,因此使用 while​ 循环非常合适。
    • 代码实现:
    • public class PaperFolding {
          public static void main(String[] args) {
              // 定义珠穆朗玛峰高度(单位:毫米)
              final double MOUNTAIN_HEIGHT_MM = 8848.86 * 1000;
              // 定义纸张初始厚度(单位:毫米)
              double paperThickness = 0.1;
      
              // 定义计数器,用于记录折叠次数
              int foldCount = 0;
      
              // 循环条件:当前纸张厚度小于山峰高度
              while (paperThickness < MOUNTAIN_HEIGHT_MM) {
                  // 循环体:每次对折,厚度加倍
                  paperThickness *= 2;
                  // 迭代:折叠次数加1
                  foldCount++;
              }
      
              System.out.println("需要对折 " + foldCount + " 次,才能超过珠穆朗玛峰的高度。");
          }
      }
      
      

2、do-while​ 循环:先执行,后判断

do-while​ 循环是一种 “出口控制” 的循环结构。它保证循环体至少会被执行一次,然后再判断循环条件。

  • 执行流程

    1. 执行初始化表达式。
    2. 无条件地先执行一次循环体和迭代表达式。
    3. 判断循环条件。
    4. 若条件为 true​,返回步骤 2,重复此过程。
    5. 若条件为 false​,跳出循环。

  • 语法结构
// ① 初始化部分;
do {
    // ③ 循环体部分;
    // ④ 迭代部分;
} while (② 循环条件部分); // 注意:这里的分号(;)不能省略

// 执行顺序: ① -> ③ -> ④ -> ② (true) -> ③ -> ④ -> ... -> ② (false) -> 结束

  • 适用场景:适用于那些无论条件如何,都至少需要执行一次循环体的业务逻辑,例如“先尝试,后判断”的场景。实际开发中较少使用。

  • 案例:分期款款

    • 需求:有一笔 100 元 的初始贷款。每年固定偿还 30 元。需要多少年才能还清全部贷款,并打印出每年的还款情况。
    • 分析:无论贷款金额多少,都必须至少偿还一次。
    • 代码实现:
    • public class LoanRepayment {
          public static void main(String[] args) {
              // 定义初始贷款金额
              double loanAmount = 100.0;   
              // 定义每年固定还款金额
              final double REPAYMENT_PER_YEAR = 30.0;       
              // 定义计数器,用于记录年份
              int years = 0;
      
              System.out.println("贷款总额:" + loanAmount + "元,每年固定还款:" + REPAYMENT_PER_YEAR + "元。");
              System.out.println("开始还款...");
      
              // 使用do-while循环来模拟还款过程
              do {
                  // 循环体:至少会执行一次
      
                  // 1. 进行一次还款
                  loanAmount -= REPAYMENT_PER_YEAR;
      
                  // 2. 年份计数器加1
                  years++;
      
                  // 打印当年的还款状态
                  if (loanAmount > 0) {
                      System.out.println("第 " + years + " 年后,剩余贷款:" + loanAmount + "元。");
                  } else {
                      // 如果还款后余额小于等于0,说明已还清
                      System.out.println("第 " + years + " 年,贷款已还清!");
                  }
      
              } while (loanAmount > 0); // 循环条件:只要贷款余额大于0,就继续还款
      
              System.out.println("\n最终,总共需要 " + years + " 年才能还清全部贷款。");
          }
      }
      // 预期输出:
      贷款总额:100.0元,每年固定还款:30.0元。
      开始还款...
      第 1 年后,剩余贷款:70.0元。
      第 2 年后,剩余贷款:40.0元。
      第 3 年后,剩余贷款:10.0元。
      第 4 年,贷款已还清!
      最终,总共需要 4 年才能还清全部贷款。
      

3、for 循环

循环结构最开始设计的是 while​ 结构,即强调循环条件成立就执行语句,直到循环条件不成立。

而引入 do...while​ 结构是为了满足至少执行一次循环体语句块的需求。

但在使用过程中,有一种情况很常见,那就是循环条件是一个区间值,从几循环到几,每次修改循环变量的迭代语句也很有规律,为了满足这种需求,从而设计了 for​ 循环结构。

for​ 循环是使用最广泛的循环结构。它将循环的四大核心要素(初始化、条件、迭代)集中写在了一起,结构清晰,特别适用于循环次数已知有明确范围的遍历。

  • 执行流程

    1. 执行初始化表达式(仅一次)。
    2. 判断循环条件。
    3. 若条件为 true​,执行循环体。
    4. 执行迭代表达式。
    5. 返回步骤 2,重复此过程。
    6. 若条件为 false​,跳出循环。

  • 语法结构
for (① 初始化部分; ② 循环条件部分; ④ 迭代部分) {
    // ③ 循环体部分;
}
// 执行顺序: ① -> ② (true) -> ③ -> ④ -> ② (true) -> ... -> ② (false) -> 结束zhu
  • 注意:

    • 两个分号必不可少,如果 for 括号中的三个表达式都省略,则相当于条件恒成立的 死循环​。
    • 循环变量初始化可以由多条变量初始化语句组成,中间用逗号隔开。循环变量更新也可以由多条更新语句组成,中间用逗号隔开。
  • 适用场景:数组遍历、固定次数的重复操作、按规律递增/递减的场景。

  • 案例: 寻找水仙花数

    • 需求:找出所有的三位数“水仙花数”(即该数等于其每位数字的立方和,如 153 = 1³ + 5³ + 3³​)。
    • 分析:遍历的范围是明确的(所有三位数,从 100 到 999),这是 for​ 循环的典型应用场景。
    • 代码实现:
    • public class NarcissisticNumber {
          public static void main(String[] args) {
              System.out.println("所有的三位数水仙花数有:");
              int count = 0; // 用于统计水仙花数的个数
      
              // 循环遍历所有三位数
              for (int num = 100; num < 1000; num++) {
                  // 分解出个位、十位、百位
                  int ge = num % 10;
                  int shi = num / 10 % 10;
                  int bai = num / 100;
      
                  // 判断是否满足水仙花数的条件
                  if ((ge * ge * ge + shi * shi * shi + bai * bai * bai) == num) {
                      System.out.print(num + " ");
                      count++;
                  }
              }
              System.out.println("\n水仙花数共有 " + count + " 个。");
          }
      }
      
      

4、三种循环语句的对比

特性for​ 循环while​ 循环do-while​ 循环
执行顺序先判断,后执行先判断,后执行先执行,后判断
结构四要素集中,结构紧凑四要素分散,逻辑灵活四要素分散,逻辑灵活
保证执行次数可能 0 次可能 0 次至少 1 次
最佳适用场景循环次数已知或有明确范围的遍历循环次数未知,依赖条件判断逻辑上要求循环体至少执行一次

相同点

  1. 都具备循环四要素,只是声明位置不同
  2. 本质上三种循环之间完全可以互相转换,都能实现循环的功能

不同点

  • 语法不同

  • 执行循序不同

    • while​ 和 for​ 语句:先判断后执行。
    • do…while​ 语句:先执行后判断(至少执行一次循环体语句)

选择策略

  • 首选 for循环:当遍历有明确的起止点或次数时(如遍历数组、集合),for​ 循环是代码最清晰、最符合编程习惯的选择。
  • 次选 while循环:当循环的持续依赖于一个运行时才能确定的条件时(如读取文件直到末尾、等待用户输入特定指令),while​ 更为合适。
  • 慎用 do-while循环:仅在业务逻辑明确要求“先执行一次再判断”的罕见场景下使用。在多数情况下,其逻辑可被 while​ 替代,因此使用频率最低。

5、嵌套循环

一个循环的循环体内包含另一个完整的循环结构,称为嵌套循环。

  • 执行特点:外层循环每执行一次,内层循环都要完整地执行一轮(从开始到结束)。
  • 执行总次数:若外层循环执行 M 次,内层循环执行 N 次,则内层循环体总共会执行 M * N​ 次。
  • 开发技巧:在处理二维数据或打印二维图形时,通常将外层循环看作控制,内层循环看作控制

案例:打印九九乘法表

public class MultiplicationTable {
    public static void main(String[] args) {
        // 外层循环控制行数 (从1到9)
        for (int i = 1; i <= 9; i++) {
            // 内层循环控制列数 (列数不超过当前行数)
            for (int j = 1; j <= i; j++) {
                System.out.print(j + " * " + i + " = " + (i * j) + "\t");
            }
            // 每打印完一行后,换行
            System.out.println();
        }
    }
}

6、跳转语句

跳转语句就是在中途改变程序原本的执行过程的语句,由 break ​或 continue ​关键字修饰。

break​ 的意思为中断,用于 switch-case 或循环结构中,表示提前结束 switch 或当前循环。

continue​ 的意思为继续,只能用于循环结构中,表示继续下一次循环,本次循环剩下的循环体语句将被跳过。

关键字使用范围作用
breakswitch​ 或循环结构立即终止并跳出当前所在的整层循环或 switch​。
continue循环结构立即结束本次循环迭代,跳过循环体中余下的语句,直接开始下一次迭代。
  • 相同点break ​或 continue ​的后面不能声明执行语句。
  • 核心区别break​ 是“不玩了,直接结束”,而 continue​ 是“这次算了,继续下次”。
  • 注意:在嵌套循环中,break​ 和 continue​ 默认只对最内层的循环起作用。如果想控制外层循环,需要使用“标签(label)”,但应谨慎使用以避免代码逻辑复杂化。
  • 开发中,break 的使用频率要远高于 continue。

案例:

public class JumpStatementsDemo {
    public static void main(String[] args) {
        // 示例1:在循环中使用break
        // 当数字等于5时,中断整个循环
        System.out.println("使用 break 结束整个循环:");
        for (int i = 1; i <= 10; i++) {
            if (i == 5) {
                // 当 i 等于 5 时,使用 break 退出整个循环,
                // 后续循环中的代码不会执行
                break;
            }
            System.out.println("数字:" + i);
        }
        /*
        结果:
        使用 break 结束整个循环:
        数字:1
        数字:2
        数字:3
        数字:4
        */

        // 示例2:在循环中使用continue
        // 当数字等于5时,跳过当前循环剩下的部分,直接进入下一次循环
        System.out.println("使用 continue 跳过当前循环的剩余部分:");
        for (int i = 1; i <= 10; i++) {
            if (i == 5) {
                // 当 i 等于 5 时,使用 continue 跳过本次循环中后续代码
                // 但循环会继续进行下一次迭代
                continue;
            }
            System.out.println("数字:" + i);
        }
        /*
        结果:
        使用 continue 跳过当前循环的剩余部分:
        数字:1
        数字:2
        数字:3
        数字:4
        数字:6
        数字:7
        数字:8
        数字:9
        数字:10
        */
    }
}

四、本章小结

本章学习了流程控制结构:顺序结构、分支结构、循环结构。

实际上,一个完整的程序中这几种结构往往是密不可分的。

顺序结构区别于其他两种结构,顺序结构是伴随所有的 Java 程序存在的,在 Java 的编程体系中,默认的结构就是顺序结构,是自上而下的设计。

分支结构是有选择的语言控制,Java 中的分支结构包含两组关键字:ifswitch。其中 if 包含三种结构:单分支结构、双分支结构和多分支结构,单分支结构和双分支结构的应用场景少一些。一旦涉及太多的选择,就需要使用多分支结构,但是多分支结构操作起来太过烦琐,不如 switch 结构简单明了,当然前提是 switch 结构能解决多分支的需求问题。

循环结构主要有三组关键字:do…whileforwhile

do...while​ 实现的是先执行循环体和迭代表达式后判断的循环。

while​ 在执行前先判断条件是否成立再执行循环体语句。

for​ 和 while​ 一样是先判断条件是否成立再执行循环体语句,但是 for 的使用更加简洁。

除以上三种循环结构,还有多重循环嵌套,也就是三种循环结构结合在一起使用。由于多重嵌套循环直接影响程序运行效率,所以在程序开发中不到万不得已的情况下,尽量避免使用嵌套循环。