循环结构,了解Java爱的魔力转圈圈

循环结构 你好,欢迎回来,我是BoBo!HAKUNA MATATA!!!
紧接着上次课的选择结构,今天学习流程控制的另外一种情况:循环结构。“循环”就是事物周而复始的运动或变化的现象,生活中这样的现象数不胜数。比如海陆空物流系统、自然界的水循环系统,还有废弃物回收利用的环保系统等等:
循环结构,了解Java爱的魔力转圈圈
文章图片

Java 语言与现实生活是紧密联系的,因此,在 Java 语言中也有让代码重复执行的循环结构。
代码不会无缘无故地重复执行,想要让代码重复执行,必须要满足一定的条件;此外,代码重复执行多少次呢?如果无限制的重复执行下去,其它的代码将永远也得不到执行的机会,这肯定不是我们想要看到的,还得想办法控制代码执行的次数。因此,想要弄明白循环结构,我们必须得想清楚两件事:

  • 代码重复执行的条件
  • 如何控制代码重复执行的次数
Java 语言为我们提供了三种基本的循环结构:forwhiledo...while,绝大部分时候三种循环都能实现相同的效果,也就是说,它们是可以相互替换的,但因为彼此格式上细微的差异,决定了不同的循环更适合于不同的场景。本次课程将为你介绍这三种循环的格式和用法,希望你重点关注它们的差异,以便把它们用在最合适的场景。
通过本次课程的学习,你将会有以下收获:
  1. 使用 Java 语言中三种基本循环结构(forwhiledo...while)实现代码的重复执行
    1. 在循环结构中根据一定条件控制循环是否继续或终止
    2. 在复杂的循环结构中进行跳转
本次课程的内容如下:
  • ? 循环:爱的魔力转圈圈
    ? for循环
    ? while循环
    ? do…while循环
  • ? 循环终止:不带走一片云彩
  • ? 标号:循环跳转的骚走位
第一关 循环:爱的魔力转圈圈 1.1 for循环 开发中用的最多的是 for 循环,并非它有多特殊,习惯而已。看一个需求:把“爱的魔力转圈圈”输出5遍。你当然可以写5次输出语句,但是太low,你好意思写,我都不好意思看。使用 for 循环该怎么做呢?上代码:
public class Test{ public static void main(String[] args) { for (int i = 1; i <= 5; i++) { System.out.println("爱的魔力转圈圈"); } } }

输出:
爱的魔力转圈圈 爱的魔力转圈圈 爱的魔力转圈圈 爱的魔力转圈圈 爱的魔力转圈圈

这段代码看起来简单又复杂:简单指的是输出语句只有一句,复杂指的是,for 循环的声明看起来内容很多,而且跟以前每句代码独占一行的写法不同。没错,我们把 for 循环的声明提取出基本格式:
public static void main(String[] args) { for (初始化语句; 判断条件; 控制条件) { // 循环体 } }

从整体来看,for 循环的声明与 if 语句的声明,结构上类似:关键字 + 小括号 + 大括号,只不过 if 语句中的小括号里只有关系表达式,而 for 循环小括号里用两个分号(; )将三条语句隔开:初始化语句、判断条件、控制条件,这里的“判断条件”就是一个关系表达式,返回结果也是 boolean 类型,与 if 语句里的一致;后面紧跟着的一对大括号,里面是要重复执行的代码,叫做“循环体”,这就是 for 循环声明的固定格式。
初始化语句:for 循环执行需要的初始数据,一般都是变量的定义。
判断条件:代码重复执行的条件,意思是,条件成立就会重复执行,不成立就终止循环。
控制条件: 控制整个循环能否继续执行下去的条件,一般是对“判断条件”的数据的修改。
循环体:要重复执行的代码。
那这个 for 循环是如何执行的呢?来看看它的执行流程:
循环结构,了解Java爱的魔力转圈圈
文章图片

对照上图和代码。
for循环开始,会首先执行初始化语句,完成所需数据的定义和初始化; 紧接着执行判断条件,此时,判断条件有可能成立,也有可能不成立: 如果条件不成立(判断条件返回 false):循环立即结束; 反之,如果条件成立(判断条件返回 true):执行循环体,这时,会把循环体中所有代码执行一遍, 然后,执行控制条件, 到此为止,第一次循环执行结束,打印了一行信息:爱的魔力转圈圈。很明显,for 循环并没有终止执行,接下来,它继续执行判断条件,检查循环继续执行的条件是否成立,同样的: 如果条件不成立(判断条件返回 false):循环立即结束; 反之,如果条件成立(判断条件返回 true):执行循环体,这时,会把循环体中所有代码再执行一遍, 然后,再执行控制条件, 到此为止,第二次循环执行结束,再一次打印了一行信息:爱的魔力转圈圈。就这样一直重复下去,直到判断条件不成立,循环结束。

从上面的执行流程可以看出,判断条件的成立与否,是决定循环体是否执行的关键,初始化语句给判断条件提供了初始数据,而控制条件通过修改判断条件使用的数据,从而控制判断条件是否成立,进而影响循环体的重复执行。
那么请问,假设for 循环最大可执行N次,for 循环中的初始化语句、判断条件、控制条件和循环体在整个循环执行的过程中,分别会执行多少次?
  • 初始化语句:有且仅有 1 次。
  • 判断条件:至少 1 次,即1-N次。当第一次判断不成立的时候,循环结束,所以只执行一次;如果成立,则在下次循环开始还会进行判断。
  • 控制条件:0-N次。0次,是指第一次判断条件就不成立。
  • 循环体:0-N次。0次,同上。
做个练习试试手。
需求:使用 for 循环在控制台输出1-5
分析:for 循环对你来说还是个新知识,要使用它,首先搞明白几件事:
  1. 我们要做的事是什么,即循环体是什么:打印从1到5的数字。
    1. 我们要做的事有什么规律,即循环体要执行几次每次执行之间的联系:执行5次,打印数字每次递增1。
    2. 如何决定循环体是否执行,即判断条件成立的根据是什么:判断执行的次数,最大不能超过5次。
    3. 如何控制判断条件成立与否,即控制条件如何修改判断条件的数据:控制次数,逐次递增1.
好了,根据上面的分析和 for 循环的基本格式可以得出实现这个需求的步骤:
  1. 【循环结构,了解Java爱的魔力转圈圈】定义整型变量 number,表示要打印的数字,初始值是1,最大值是5,每打印一次之后都需要加1:
    int number= 1;
  2. 定义整型变量 time,表示循环体执行的次数,作为 for 循环的初始化语句,初始值是1:
    int time = 1;
  3. 循环最多执行5次,所以变量 time 的最大值是5,即 for 循环的判断条件:
    time <= 5;
  4. 每打印一次数字,次数都需要加1,所以,for 循环的控制条件:
    time++
  5. 在循环体中打印数字,然后让数字加1:
    System.out.println(number);
    number++;
完整代码如下:
public class Test{ public static void main(String[] args) { // 1.要打印的数字,初始值是1,最大值是5,每打印一次之后都需要加1 int number = 1; /* 2.定义整型变量 time,表示循环体执行的次数,作为 for 循环的初始化语句,初始值是1 3.循环最多执行5次,所以变量 time 的最大值是5,即 for 循环的判断条件:time <= 5 4.每打印一次数字,次数都需要加1,所以,for 循环的控制条件:time++ */ for (int time = 1; time <= 5; time++) { // 5.在循环体中打印数字, System.out.println(number); number++; // 然后让数字加1 } } }

输出结果:
1 2 3 4 5

数字已经正确输出,完全符合需求。
聪明的你可能已经发现,变量 timenumber 的初始化、变化规律完全一样!没错。那能不能优化一下代码,只使用一个变量呢?答案是肯定的:
public class Test{ public static void main(String[] args) { for (int number = 1; number <= 5; number++) { System.out.println(number); } } }

这个时候,number 变量既代表打印的次数,又代表每次打印的数字,一举两得。学习的过程不仅要弄明白知识的来龙去脉,还要有自己的思考。代码看起来很简单,每个组成部分的来源和意义才是关键。当你能够用自己的方式把知识之间的关系联结起来,你才算是学会了。
考一考:for 循环的使用 1.需求:使用 for 循环输出1-5之和
分析:
  1. 定义一个变量 sum,代表1到5的和,初始化值是0
  2. for 循环获取1-5的数据
  3. 把每一次获取到的数据累加到变量 sum
    sum = sum + x; 或者 sum += x;
  4. 循环结束,输出变量 sum 的值
循环结构,了解Java爱的魔力转圈圈
文章图片

答案:
public class Test{ public static void main(String[] args) { // 1. 定义求和变量sum. int sum = 0; // 2. 通过for循环获取1~5之间的数据. for (int i = 1; i <=5; i++) { // i记录的就是: 1~5之间的数字 // 3. 把获取到的数据依次累加给变量sum sum += i; // sum = sum + i; } // 4. 打印结果 System.out.println("sum = " + sum); } }

2.需求:求出1-100之间偶数和
分析:
  1. 定义一个求和变量 sum,初始化值是0
  2. 获取1-100之间的数,用 for 循环实现
  3. 判断每一个数是否为偶数,是就累加,否则不做操作
    对2取余等于0,则为偶数: x % 2 == 0
  4. for 循环结束,输出求和变量 sum 的值
循环结构,了解Java爱的魔力转圈圈
文章图片

答案:
public class Test{ public static void main(String[] args) { // 1. 定义一个求和变量sum int sum = 0; // 2. 获取1~100之间所有的数据 for (int i = 1; i <= 100; i++) { // i的值其实就是1~100之间的数字, 只要判断i是否是偶数即可 // 3. 判断当前获取到的数据是否是偶数, 是就累加 if(i % 2 == 0) { // 能走到这里, 说明i是偶数, 累加即可 sum += i; } } // 4. 打印结果 System.out.println("sum: " + sum); } }

3.需求:在控制台输出所有的”水仙花数”
分析:
? 水仙花数:所谓的水仙花数是指一个三位数,其各位数字的立方和等于该数本身
? 举例:153是一个水仙花数:1 1 1 + 5 5 5 + 3 3 3 = 1 + 125 + 27 = 153
步骤:
  1. 获取所有的三位数,即100-1000之间的数
  2. 获取每一个三位数的个位,十位,百位
    ? 个位:153 % 10 = 3
    ? 十位:153/10%10 = 5
    ? 百位:153/10/10%10 = 1
  3. 拿个位,十位,百位的立方和与该数本身进行比较,如果相等,则在控制台打印该数
循环结构,了解Java爱的魔力转圈圈
文章图片

答案:
public class Test{ public static void main(String[] args) { // 1. 通过for循环, 获取所有的三位数. for (int i = 100; i < 1000; i++) { // i表示的就是所有的三位数 // 2. 获取该数据的个位, 十位, 百位数字. int ge = i % 10; int shi = i / 10 % 10; int bai = i / 10 / 10 % 10; // 3. 判断该数字是否是水仙花数, 如果是, 直接打印即可 if (ge * ge * ge + shi * shi * shi + bai * bai * bai == i) { // 能走到这里, 说明i是水仙花数 System.out.println(i); } // 不是水仙花数,不做任何操作 } } }

public class Test{ public static void main(String[] args) { // 1. 通过for循环, 获取所有的三位数.// 2. 获取该数据的个位, 十位, 百位数字// 3. 判断该数字是否是水仙花数, 如果是, 直接打印即可} }

4.需求:统计所有的”水仙花数”的个数
分析:
  1. 定义计数器变量 count,初始化值为0
  2. 使用 for 循环获取所有的三位数,即100-1000之间的数
  3. 判断每一个三位数是否为水仙花数,是则 count 自增1
    count ++; ?
  4. 循环结束,输出计数器 count 的值
循环结构,了解Java爱的魔力转圈圈
文章图片

答案:
public class Test{ public static void main(String[] args) { // 1. 定义一个计数器, 用来记录水仙花数的个数 int count = 0; // 2. 获取到所有的三位数 for (int i = 100; i < 1000; i++) { // i记录的就是所有的三位数 // 3. 获取到该数字的个位, 十位, 百位数字. int ge = i % 10; int shi = i / 10 % 10; int bai = i / 10 / 10 % 10; // 4. 判断该数字是否是水仙花数, 如果是, 计数器自增1. if (ge * ge * ge + shi * shi * shi + bai * bai * bai == i) { // 能走到这里, 说明i是一个水仙花数 ++count; } } // 5. 打印计数器的结果即可 System.out.println("水仙花数的个数是: " + count); } }

public class Test{ public static void main(String[] args) { // 1. 定义一个计数器, 用来记录水仙花数的个数// 2. 获取到所有的三位数// 3. 获取到该数字的个位, 十位, 百位数字.// 4. 判断该数字是否是水仙花数, 如果是, 计数器自增1.// 5. 打印计数器的结果即可} }

1.2 while循环 从语法的角度上讲,所有的 for 循环都可以用 while 循环改写。只不过两种循环的格式不同,适用场景有所差别。来看下 while 循环的格式:
初始化语句; while (判断条件) { 循环体; 控制条件; }

while 循环的初始化语句控制条件不像 for 循环那样在小括号里面,这是二者最大的不同。按照这种格式,可以很容易的把 for 循环的代码改写成 while格式(打印1-5的数字):
public class Test{ public static void main(String[] args) { int number = 1; // 初始化语句 while (number <= 5) { // 判断条件 System.out.println(number); // 循环体 number++; // 控制条件 } } }

输出结果没什么不同:
1 2 3 4 5

while 循环的执行流程和 for 循环几乎完全一样,只不过由于初始化语句的位置不同,while 循环的初始化语句优先执行之后,才真正进入 while 循环的地界,后续的执行流程与 for 循环完全一致,贴个图了事,这里就不赘述了:
循环结构,了解Java爱的魔力转圈圈
文章图片

你可能会说,既然两种循环结构任何时候都可以互换,而且执行流程也一样,为啥还弄俩,一个不就行了吗?虽然看起来是这样,但是你有没有注意到,while 循环的初始化语句在整个循环结构的外面,由此看来,它并非是 while 循环必不可少的一部分;同样的,控制条件在循环体的内部,换句话说,它也可以认为是循环体的一部分啊!照这么说,while 循环的初始化语句和控制条件语句都是可以省略的。换句话说,while 循环相当于 for 循环的简洁形式。
“哦,原来如此。但是彭彭,有个Bug。”
“说来听听。”
“你不是说 for 循环和 while 循环任何时候都可以互换吗?”
“是的。”
“省略了初始化语句和控制条件的 while 循环,怎么转成 for 循环?”
“聪明。小伙子反应还挺快。”
其实在 for 循环声明格式里,小括号里面的三条语句都可以省略——但是两个分号(; )不能省。如果省略了三条语句,整个 for 循环看起来光秃秃的,像没穿衣服的小屁孩(是不是想到了小时候的自己):
for ( ; ; ) { // 死循环 // 循环体 }

这种格式的 for 循环是一种最简单的"死循环"——本节最后介绍。省略了初始化语句和控制条件的 while 循环,只需要把判断条件放到 for 循环对应的位置,就改写成了 for 循环:
for ( ; 判断条件; ) { // 循环体 }

对比 for 循环,while 循环相当于简写形式,也就是说,当我们不需要“初始化条件”和/或“控制条件”的时候,就可以使用 while 循环,因为它的格式更加简洁。
好了,现在交给你个任务,把前面介绍的所有 for 循环的代码改成 while 循环的格式。
1.3 do…while循环 老夫子教导我们:学而时习之。学完一个知识,要多练习。刻意练习才能够熟能生巧,如果练一遍不行,那就练两遍、三遍,但是,至少要练一遍,直到学会为止。否则,知识仅仅在脑海中浮光掠影般过了一遍,没有留下深刻的印象,就不算完成了学习。多次练习其实就是重复执行,我们可以用循环来实现,但是如何确保“至少练一次”呢?
Java 语言提供了一种特殊的循环结构:do...while 循环,这个结构的特殊之处在于,它会让循环体先执行(do)一遍,然后去判断条件是否满足,再根据实际需要决定是否继续循环。这种格式的循环完美适用上面的需求。
来看看神奇的 do…while 循环语句的格式:
初始化语句; do { 循环体; 控制条件; } while (判断条件);

注意 while 小括号最后的分号不可省略。与 while 循环相同的地方是,初始化语句在整个循环结构的上面,控制条件依然在循环体的最后;不同的地方在于,循环体和判断条件的位置发生了调换,这也就意味着,先执行循环体和控制条件,再执行判断条件。
它的执行流程是这样的:
循环结构,了解Java爱的魔力转圈圈
文章图片

  1. 先执行初始化语句。这一步与 while 循环完全一样。
  2. 执行循环体。
  3. 执行控制条件。
  4. 执行判断条件:
    条件成立,则再一次执行循环体、控制条件等内容
    条件不成立,循环终止。
由于 do...while 循环的判断条件放在最后,所以它的循环体部分必然能执行,或者说,do...while 循环的循环体至少执行一次,这是它与前两个循环最大的不同。
回到前面提到的需求:如何用 do…while 循环实现“学完一个知识,至少练习1次”?
分析:假设练习三次之后一定能学会,那么,do...while 循环的各个部分如下:
? 1. 初始化条件:定义 int 型变量 count,代表练习的次数,初始化值为 1
? 2. 判断条件:定义 boolean 型变量 isOK,作为一个标记,代表是否学会,默认值为 false
? 3. 循环体:
? 判断当练习次数小于等于3时,打印正在练习的次数
? 每练习一次,次数加1:count ++
  1. 控制条件:当次数大于3,表示已学会:给标记重新赋值:isOK = true
示例代码:
public class Test{ public static void main(String[] args) { // 1. 定义一个变量, 记录练习次数 int count = 1; // 2. 定义一个变量, 用来标记是否学会这个知识点. true: 学会了, false: 没学会 boolean isOK = false; do { if (count <= 3) { // 3. 判断当练习次数小于等于3时, System.out.println("正在进行第" + count + "次练习"); //打印正在练习的次数 count++; // 每练习一次, 次数要+1 } else { // 4. 当次数大于3,表示已学会 isOK = true; // 将boolean类型变量的值改为: true } } while (!isOK); // 判断标记不为 false,即没学会的时候,循环执行 } }

输出结果:
正在进行第1次练习 正在进行第2次练习 正在进行第3次练习

这就是 do...while 循环的基本使用。
对比这三种循环:从格式上看,while 循环相当于 for 循环的简化格式,要知道,这两种循环任何时候都可以相互替换。之所以会出现两个不同格式,是因为有些场景的循环操作并不需要初始化条件,对控制条件的需要也没那么强烈,简化版循环 while 就显得结构更清晰,可读性更强。对于 do...while 循环来说,它在格式上类似 while 循环,只不过二者循环体和判断条件的位置正好相反,也是这个原因,使得 do...while 循环的循环体部分先于判断条件执行,即至少执行一次。
综上所述,一般情况下,我们尽可能使用 for 循环,如果不关注循环的初始化条件,可以使用简化版的 while 循环,当你的需求必须要执行一次循环体的时候,使用 do...while 循环。
到此为止,这三种循环的基本使用介绍完了,希望你多练习案例代码,尝试相互转换三种循环的代码。
第二关 循环终止:不带走一片云彩 2.1 break 如果对一组数据的每一条都要进行处理,那么遍历所有的数据是必要的;但如果是为了查找一组数据中的某一个,就不总是循环所有的次数,因为可能在任何一次循环的中间找到需要的数据。这个时候,对后续数据的遍历就不再必要了。为了提高性能,同时也为了节省时间,找到需要的数据之后就终止循环才是最合适的做法。
那么,如何在循环执行的过程中去终止它呢?
答案是:使用 break; 语句。
你应该对 break; 语句不陌生了吧,因为在《选择结构》课程中介绍的 switch 结构中,就是通过 break; 语句来结束的,同样的,在循环结构中,也可以使用它来结束。用法非常简单,在循环体中的任何一个位置,只要你认为可以结束循环,那么就可以把这个语句放到这里。
举个栗子演示一下。
比如现在有这样一个需求:班级中有15位同学,请查找编号为3的同学。
分析:查找的过程是一个重复操作,使用 for 循环来实现。我们假设班级中同学们的编号是1-15,那么遍历每个同学,查看他们的编号,如果为3,就终止循环。如果不是,就继续查找,直到找到3号同学为止。由此,循环的各个组成部分:
? 1.初始化语句:定义同学的编号,从1开始,最大15
? int number = 1;
? 2.判断条件:遍历每个同学的编号,最大到15结束,所以编号的范围是1-15
? number <= 15;
? 3.控制条件:每遍历一位同学,编号就加1
? number++
? 4.循环体:
? 判断同学编号是否为3
? 若该同学编号为3,则打印该同学编号,结束循环
? 若该同学编号不为3,不做任何操作
示例代码:
public class Test{ public static void main(String[] args) { // 遍历编号为1-15的所有同学 for (int number = 1; number <= 15; number++) { if (number == 3) { // 如果某位同学的编号为3 System.out.println("找到了编号为3的同学"); // 打印信息 break; // 结束循环 } // 否则,不作任何操作,让循环继续,进行下一次查找 } } }

代码非常简单。我们在循环体里加入一个 if 判断语句:当编号为3时,打印同学信息,然后使用 break; 语句结束循环,否则,不做任何操作,让循环继续,进行下一次查找,直到找到为止。变量 number 的值从1开始,那么肯定是到第三次循环的时候找到了对应的同学,然后整个循环被 break; 语句终止了。
是不是很简单!
2.2 continue 另一种情况,在遍历一组数据的时候,并不是要取出那些特殊的数据,而是跳过它们,因为它们可能是非法的、或者是因为测试而填入的数据。那么,怎么在循环执行的过程中跳过这个数据呢?
答案是:使用 continue; 语句。
再举个栗子。
需求:一起来玩逢7必过小游戏。游戏规则:多人围坐在一起,依次快速说出从1-100的数字,所有含7、7的倍数的数不能说,否则就失败受到惩罚。
分析:
1.同样,使用for循环遍历1-100的数,

? 2.然后在循环体中,判断当前遍历的数中是否含7、或是否为7的倍数
? 是否含7,包括个位是7和十位是7两种情况:
? 个位是7:对10取模,余数为7:number % 10 == 7
? 十位是7:除以10,商为7:number / 10 == 7
? 是否为7的倍数:对7取模,余数为0:number % 7 == 0
? 3.跳过所有符合上述要求的数:continue;
4.打印其它数。打印效果:

循环结构,了解Java爱的魔力转圈圈
文章图片

示例代码:
public class Test{ public static void main(String[] args) { // 1. 通过for循环获取到1~100之间所有的数据 for (int number = 1; number <= 100; number++) { // 2. 包含7或者是7的倍数, 这些数据都要跳过 if (number % 10 == 7 || number / 10 == 7 || number % 7 == 0) { // 3. 符合要求,直接跳过当前数(跳过本次循环) continue; } // 4. 如果数据合法, 直接打印即可 System.out.println(number); } } }

程序运行过程中,如果通过了 if 语句的判断,就会执行 continue; 语句,它的作用是:跳过本次循环,进行下次循环。
break; 语句和 continue; 语句都有“结束”循环的意思,但它们有明显的区别:
break:结束所在循环的遍历操作。它终止了整个循环,不再进行循环遍历操作。
continue:跳过本次循环,继续下次循环。它终止了整个循环操作的某一次,然后继续下一次循环操作。
2.3 死循环 探索人生的道路上,我们不知道失败了多少次,而且,在不远的未来,我们可能还会再一次遭遇失败。但是——前面都是废话,无论失败多少次,我们依然坚持尝试,不管需要坚持多久,我们都不会放弃,直到找到成功出路的那天。
说的我自己都快信了。
虽然我们的人生方向并不迷茫,但我们并不明确的知道获得成功的具体日期,所以,我们不得不一次又一次的尝试,不知道还需多少次,何时才能破局而出,升职加薪、走上人生癫疯。其实,我是想说,有些时候,重复的次数是无法预知的,而前面介绍的循环,都明确知道循环的最大次数。在不知道应该重复执行多少次的时候,如何用循环来实现、又如何终止循环呢?
答案:用死循环来实现,然后在满足某种特殊条件时,使用 break; 语句进行终止。
“死循环”的意思,是指循环本身不会自动终止,而是需要依靠其它的条件才能结束。前面介绍的循环,最终都会因为判断条件不成立而终止,可死循环的判断条件永远都是成立的。三种简单的死循环格式如下:
循环结构,了解Java爱的魔力转圈圈
文章图片

通过上图可以看出,for 循环的死循环格式,要求判断条件为空,或者恒成立,初始化语句和控制条件并没有作任何要求;while 循环和 do...while 循环的判断条件都是 boolean 类型的常量:true,这样也能保证判断条件的恒成立。这就是三种循环的简单死循环格式。
我们并不会容忍死循环永远的执行下去,只不过由于不知道这种循环具体的执行次数,才不得不使用这种格式。所以,一般情况下,我们都会在循环体内部设置某些条件,当条件满足就让循环终止,而不是任由它浪费CPU的资源。
举个栗子。
需求:假设纸张厚度为 0.001米,喜马拉雅山高度为8848米。请问:需要将纸张对折多少次,才能达到喜马拉雅山的高度?
分析:由于不知道循环多少次,用死循环。就使用格式简洁的 while 循环,你也可以改成另两种格式。每次循环让厚度加一倍,即厚度乘以2,这里的厚度是在上一次折叠之后的基础上,所以是累乘的效果。
  1. 定义 int 变量 count 作为折叠计数器,代表需要折叠的次数,初始值为0,每折叠一次加1
  2. 定义 double 变量 thicknessheight,分别代表纸张厚度和喜马拉雅山高度,初始值分别是0.001和8848
  3. 循环体:
    判断当前纸张厚度是否达到山的高度:thickness >= height
    如果已达到,即判断条件返回 true,终止循环:break;
    否则,
    ? 进行纸张的折叠:thickness *= 2;
    ? 每折叠一次,让计数器加1:count++;
  4. 循环结束,打印折叠计数器的值
示例代码:
public class Test{ public static void main(String[] args) { // 1. 定义折叠次数,每次加1 int count = 0; // 2. 定义纸张厚度和喜马拉雅山高度 double thickness = 0.001; // 纸张厚度 double height = 8848; // 喜马拉雅山高度while (true) { // 不知道循环多少次,用死循环 // 3. 判断条件,当前纸张厚度是否达到山的高度 if (thickness >= height) { break; // 如果已达到,则终止循环 } // 否则。如果 if 条件成立,必然跳出循环,if 语句体里的代码不会与这里的代码同时执行。 thickness *= 2; // 进行纸张的折叠 count++; // 计数器加1 } // 4. 循环结束,打印折叠计数器的值 System.out.println(count); } }

输出结果:
24

需要折叠24次,不信你可以试试。
如果死循环使用不当,比如你设置的结束条件始终无法达成,那么这个循环体代码可能会一直执行下去,从而导致CPU资源耗尽,所以,使用死循环一定要慎重,确保使它跳出的条件一定能够执行到,否则,等待你的将是......,你自己品。
期待你破局而出的日子。
第三关 标号:循环跳转的骚走位 3.1 循环嵌套 遍历一组数据对你来说没有什么挑战性,现在,增加一点难度:遍历多组数据。
需求:假设现在有3个班级,每个班级15名同学,如何获取所有班级中的同学呢?
这个需求里有两组数据:一、3个班级;二、每个班级15名同学。如果要找到所有的同学,可以一个班一个班地找,每找一个班,再逐个把该班级所有同学找出来就行了。看起来逻辑并不复杂,该怎么实现?
分析:通过观察可知,班级和同学这两组数据之间有一定的关系:班级包含同学。遍历每个班级需要用到循环,同样,遍历每个同学也需要用到循环,莫非循环之间也可以有包含关系吗?是的,我们可以在一个循环体语句中包含另一个循环语句,这种情况称为循环嵌套。班级循环称为外层循环,被包含的同学循环称为内层循环。我们先来尝试把外层循环实现出来:
  1. 初始化条件:定义 int 型变量 classNumber,初始值为1,最大值为3;
    1. 判断条件:classNumber <= 3;
    2. 循环体:打印班级编号
    3. 控制条件:classNumber++;
示例代码:
public class Test{ public static void main(String[] args) { for (int classNumber = 1; classNumber <= 3; classNumber++) { // 外层循环,遍历每个班级 System.out.println(classNumber); // 打印班级编号 } } }

输出:
1 2 3

现在,我们再把内存循环实现出来,也就是把一个班级的同学编号打印出来:
1. 初始化条件:定义 `int` 型变量 `studentNumber`,初始值为1,最大值为15; 2. 判断条件:`studentNumber <= 3; ` 3. 循环体:打印同学编号 4. 控制条件:`studentNumber++; `

示例代码:
public class Test{ public static void main(String[] args) { for (int studentNumber = 1; studentNumber <= 15; studentNumber++) { // 内层循环,遍历每个同学 System.out.println(studentNumber); // 打印同学编号 } } }

运行输出:
1 2 3 ... 14 15

好了,两层循环都完成了,怎么把它们包含起来呢?
其实非常简单,我们的要求是:在遍历每个班级的时候去查找该班级的所有同学,所以,内存循环——即遍历同学的循环,其实是外层循环的一部分,即循环体:
public class Test{ public static void main(String[] args) { for (int classNumber = 1; classNumber <= 3; classNumber++) { // 外层循环,遍历每个班级 for (int studentNumber = 1; studentNumber <= 15; studentNumber++) { // 内层循环,遍历每个同学 System.out.println("正在获取的第" + classNumber + "个班级的第" + studentNumber + "位同学"); // 打印同学编号 } } } }

输出:
正在获取的第1个班级的第1位同学 正在获取的第1个班级的第2位同学 正在获取的第1个班级的第3位同学 ... 正在获取的第2个班级的第1位同学 正在获取的第2个班级的第2位同学 ... 正在获取的第2个班级的第14位同学 正在获取的第2个班级的第15位同学 正在获取的第3个班级的第1位同学 正在获取的第3个班级的第2位同学 正在获取的第3个班级的第3位同学 ... 正在获取的第3个班级的第13位同学 正在获取的第3个班级的第14位同学 正在获取的第3个班级的第15位同学

就这样,我们通过循环嵌套完成了3个班级共45名同学的查找。
循环嵌套并没有想像中的那么复杂,在设计双层循环结构的时候,首先要想清楚每层循环所代表的含义,然后分别专注于每层循环代码的实现就可以了。你只需要把内层循环作为外层循环的循环体,在编写内层循环代码的时候,就把它当作普通的循环,与外层循环无关,剥离外层循环的干扰,专注于内层循环的实现。还要注意一个细节,两层循环使用的变量名不要重复,要不然很容易混乱,各自独立是最好的。
3.2 标号 稍微改一下需求:A公司邀请程旭元同学加入,旭元同学还在班级里埋头狂敲代码,现按班级查找程旭元同学。有3个班级,每班15个同学,假设第3个班级的第10位同学名叫程旭元,找到该同学后则停止查找。怎么实现?
肯定还是要使用双层嵌套循环,外层循环和内层循环的含义没变,问题是,查找同学的操作在内层循环,找到程旭元同学后怎么让两层循环同时终止?我们知道结束单层循环的方式:break; 语句,却没有办法结束双层循环,况且是外层循环。
这的确是个难题。
但是难不倒聪明的彭彭。 break; 语句默认情况下的作用是结束当前循环,如果给它一种能力,让它可以结束外层循环不就行了嘛!
怎么做呢?可以给外层循环加一个名字,同时,在执行 break; 语句的时候把这个名字带上,意思是“结束指定名字的循环”。这种名字叫“标号”,即循环的名称。我们给循环定义一个标号,就可以根据需要结束或跳转到指定循环,它的定义格式是这样的:
标号: for () {}// 标号的定义,直接在循环前写标识符。while 和 do…while 举例略

或者这样,让标号独占一行:
标号: for () {}// 标号的定义,直接在循环前写标识符。while 和 do…while 举例略

使用的时候,只需要在 break 关键字后面跟上标号就可以了,就像这样:
break 标号; // 结束名称为指定标号的循环

或者这样,使用 continue 关键字加标号,意思是跳转到指定标号的循环继续执行:
continue 标号; // 跳转到名称为指定标号的循环继续执行

标号是一种标识符,定义的时候必须符合标识符的命名规则。但它不是变量,不需要前面加数据类型。标号只能用于多层嵌套循环中,不能在同级别的循环之间使用哦,那样的话,会让代码的执行顺序乱套的!
现在,我们就可以开始查找程旭元同学了。
分析:
? 1. 先使用 for 循环遍历每一个班级,定义标号:
? label_class: for () { }
? 2. 在班级循环体中,再使用 for 循环遍历每个同学
? 3. 判断:如果班级编号为3,同学编号为10,则停止查找:break label_class;
示例代码:
public class Test{ public static void main(String[] args) { label_class: // 外层循环标号 //1. 先使用 for 循环遍历每一个班级 for (int classNumber = 1; classNumber <= 3; classNumber++) { // 2. 在班级循环体中,再使用 for 循环遍历每个同学 for (int studentNumber = 1; studentNumber <= 15; studentNumber++) { // 打印正在查找的同学编号 System.out.println("正在查找的第" + classNumber + "个班级的第" + studentNumber + "位同学"); // 3. 判断:如果班级编号为3,同学编号为10,则停止查找 if (classNumber == 3 && studentNumber == 10) { System.out.println("哈哈, 找到程旭元同学了, 整个循环结束"); break label_class; // 停止查找:结束指定标号的循环 } } } } }

输出:
正在查找的第1个班级的第1位同学 正在查找的第1个班级的第2位同学 正在查找的第1个班级的第3位同学 ... 正在查找的第1个班级的第15位同学 正在查找的第2个班级的第1位同学 正在查找的第2个班级的第2位同学 ... 正在查找的第2个班级的第14位同学 正在查找的第2个班级的第15位同学 正在查找的第3个班级的第1位同学 ... 正在查找的第3个班级的第9位同学 正在查找的第3个班级的第10位同学 哈哈, 找到程旭元同学了, 整个循环结束

使用 break 标号; 语句结束指定名称的循环非常方便。此时,标号为 label_class 的外层循环被强制结束,那内层循环呢,还执行吗?很明显,内层循环也结束了,因为内层循环的代码不可能脱离外层循环独立执行啊。
再看另一种情况,什么样的场景下会使用 continue 标号; 语句。
需求:按批次检测商品的次品量。现有3个批次,每个批次有10件商品,如果某批次商品中包含任意一个次品,则该批次商品不合格,跳过该批次剩余商品的检测并记录,继续下个批次。假设查找到第2个批次的第5件商品为次品。
分析:
  1. 先使用 for 循环遍历每一个批次,定义标号:
    label_batch: for () { }
    1. 在批次循环体中,再使用 for 循环遍历每个商品
    2. 判断如果批次编号为2,商品编号为5,则结束当前批次的检测,继续下个批次:
      continue label_batch;
示例代码:
public class Test{ public static void main(String[] args) { label_batch: // 外层循环标号 // 1. 先使用 for 循环遍历每一个批次 for (int batchNumber = 1; batchNumber <= 3; batchNumber++) { // 2. 在批次循环体中,再使用 for 循环遍历每个商品 for (int goodsNumber = 1; goodsNumber <= 10; goodsNumber++) { // 打印正在检测的商品编号 System.out.println("正在检测第" + batchNumber + "个批次的第" + goodsNumber + "件商品"); // 3. 判断:如果批次编号为2,商品编号为5,则停止查找 if (batchNumber == 2 && goodsNumber == 5) { System.err.println("记录:第" + batchNumber + "个批次的第" + goodsNumber + "件商品是次品"); continue label_batch; // 停止检测当前批次,继续下个批次:继续指定标号的循环 } } } } }

输出结果:
正在检测第1个批次的第1件商品 正在检测第1个批次的第2件商品 ... 正在检测第2个批次的第4件商品 正在检测第2个批次的第5件商品 记录:第2个批次的第5件商品是次品 正在检测第3个批次的第1件商品 正在检测第3个批次的第2件商品 ... 正在检测第3个批次的第9件商品 正在检测第3个批次的第10件商品

一定要注意的是,break 标号; continue 标号; 两个语句含义的不同:break 代表“结束全部”,而 continue 代表“结束部分,然后继续”。仔细区分它们的含义,才能正确地使用它们。
3.3 循环案例:1024程序员节,小黑带你发橙子 3.3.1 需求:
*1024程序员节*,是传智播客发起的中国程序员共同的节日。每到10月24日,小黑都会按班级给每位同学发橙子。假设有3个班级,每个班级有35个同学,现在要将100个橙子分别发放给每位同学,每人只能拿一个。条件:如果该同学已经有了橙子,则不再发给该同学;如果橙子发完了,则发放活动终止。

3.3.2 分析:
A:模拟发橙子的过程:循环每一个班级,然后遍历班级的每个同学,所以需要双层循环

? B:假设编号为5的倍数的同学都已经有了橙子,则发放到该同学时,使用 continue 语句结束该次循环
? C:橙子的数量为0时,使用 break + 标号; 语句结束外层循环,发放活动终止。
3.3.3 步骤: ? A:实现给每位同学发橙子的功能
? B:添加判断条件:跳过编号为5的倍数的同学
? C:添加判断条件:橙子数目为0,则终止发放
3.3.4 技术点: ? 循环嵌套
? break
? continue
? 标号
循环结构,了解Java爱的魔力转圈圈
文章图片

参考代码:
public class Test{ public static void main(String[] args) { int orangeCount = 100; // 橙子总数100个 label_class: // 外层循环标号:班级循环 //1. 使用 for 循环遍历每一个班级 for (int classNumber = 1; classNumber <= 3; classNumber++) { // 2. 在班级循环体中,再使用 for 循环遍历每个同学 for (int studentNumber = 1; studentNumber <= 35; studentNumber++) { if (orangeCount <= 0) { // 检查橙子数量是否足够,如果橙子数量不够了 break label_class; // 结束发橙子的活动:结束外层循环 } if (studentNumber % 5 == 0) { // 检查当前同学编号:编号为5的倍数,该同学已经有了橙子 continue; // 跳过当前同学 } // 打印得到橙子的同学编号 System.out.println("正在给第" + classNumber + "个班级的第" + studentNumber + "位同学发橙子"); orangeCount--; // 橙子数量减1 } } // 活动结束,打印发出的橙子数量 System.out.println("总共发出了" + (100 - orangeCount) + "个橙子"); } }

课程总结
  1. 三种循环的基本格式和执行流程(参考上文中图):
    for (初始化语句; 判断条件; 控制条件) { 循环体 }

    初始化语句; while (判断条件) { 循环体; 控制条件; }

    初始化语句; do { 循环体; 控制条件; } while (判断条件);

  2. 循环的终止:
    break:结束当前循环
    continue:结束本次循环,继续下次循环
    循环嵌套:多组数据之间有包含关系时,使用循环嵌套格式:每层循环所代表的含义;变量名重复问题;
    标号:循环的名字,配合 breakcontinue 语句使用。

    推荐阅读