循环结构
你好,欢迎回来,我是BoBo!HAKUNA MATATA!!!
紧接着上次课的选择结构,今天学习流程控制的另外一种情况:循环结构。“循环”就是事物周而复始的运动或变化的现象,生活中这样的现象数不胜数。比如海陆空物流系统、自然界的水循环系统,还有废弃物回收利用的环保系统等等:
Java 语言与现实生活是紧密联系的,因此,在 Java 语言中也有让代码重复执行的循环结构。
代码不会无缘无故地重复执行,想要让代码重复执行,必须要满足一定的条件;此外,代码重复执行多少次呢?如果无限制的重复执行下去,其它的代码将永远也得不到执行的机会,这肯定不是我们想要看到的,还得想办法控制代码执行的次数。因此,想要弄明白循环结构,我们必须得想清楚两件事:
- 代码重复执行的条件
- 如何控制代码重复执行的次数
Java 语言为我们提供了三种基本的循环结构:for
、while
和 do...while
,绝大部分时候三种循环都能实现相同的效果,也就是说,它们是可以相互替换的,但因为彼此格式上细微的差异,决定了不同的循环更适合于不同的场景。本次课程将为你介绍这三种循环的格式和用法,希望你重点关注它们的差异,以便把它们用在最合适的场景。
通过本次课程的学习,你将会有以下收获:
使用 Java 语言中三种基本循环结构(
for
、while
和do...while
)实现代码的重复执行- 在循环结构中根据一定条件控制循环是否继续或终止
- 在复杂的循环结构中进行跳转
本次课程的内容如下:
循环:爱的魔力转圈圈
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
循环是如何执行的呢?来看看它的执行流程:
对照上图和代码。
for循环开始,会首先执行初始化语句,完成所需数据的定义和初始化;
紧接着执行判断条件,此时,判断条件有可能成立,也有可能不成立:
如果条件不成立(判断条件返回 false):循环立即结束;
反之,如果条件成立(判断条件返回 true):执行循环体,这时,会把循环体中所有代码执行一遍,
然后,执行控制条件,
到此为止,第一次循环执行结束,打印了一行信息:爱的魔力转圈圈。
很明显,for 循环并没有终止执行,接下来,它继续执行判断条件,检查循环继续执行的条件是否成立,同样的:
如果条件不成立(判断条件返回 false):循环立即结束;
反之,如果条件成立(判断条件返回 true):执行循环体,这时,会把循环体中所有代码再执行一遍,
然后,再执行控制条件,
到此为止,第二次循环执行结束,再一次打印了一行信息:爱的魔力转圈圈。
就这样一直重复下去,直到判断条件不成立,循环结束。
从上面的执行流程可以看出,判断条件的成立与否,是决定循环体是否执行的关键,初始化语句给判断条件提供了初始数据,而控制条件通过修改判断条件使用的数据,从而控制判断条件是否成立,进而影响循环体的重复执行。
那么请问,假设for
循环最大可执行N次,for
循环中的初始化语句、判断条件、控制条件和循环体在整个循环执行的过程中,分别会执行多少次?
- 初始化语句:有且仅有 1 次。
- 判断条件:至少 1 次,即1-N次。当第一次判断不成立的时候,循环结束,所以只执行一次;如果成立,则在下次循环开始还会进行判断。
- 控制条件:0-N次。0次,是指第一次判断条件就不成立。
- 循环体:0-N次。0次,同上。
做个练习试试手。
需求:使用 for
循环在控制台输出1-5
分析:for
循环对你来说还是个新知识,要使用它,首先搞明白几件事:
我们要做的事是什么,即循环体是什么:打印从1到5的数字。
- 我们要做的事有什么规律,即循环体要执行几次,每次执行之间的联系:执行5次,打印数字每次递增1。
- 如何决定循环体是否执行,即判断条件成立的根据是什么:判断执行的次数,最大不能超过5次。
- 如何控制判断条件成立与否,即控制条件如何修改判断条件的数据:控制次数,逐次递增1.
好了,根据上面的分析和 for
循环的基本格式可以得出实现这个需求的步骤:
定义整型变量
number
,表示要打印的数字,初始值是1,最大值是5,每打印一次之后都需要加1:int number= 1;
定义整型变量
time
,表示循环体执行的次数,作为for
循环的初始化语句,初始值是1:int time = 1;
循环最多执行5次,所以变量
time
的最大值是5,即for
循环的判断条件:time <= 5;
每打印一次数字,次数都需要加1,所以,for 循环的控制条件:
time++
在循环体中打印数字,然后让数字加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
数字已经正确输出,完全符合需求。
聪明的你可能已经发现,变量 time
和 number
的初始化、变化规律完全一样!没错。那能不能优化一下代码,只使用一个变量呢?答案是肯定的:
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之和
分析:
- 定义一个变量
sum
,代表1到5的和,初始化值是0 - 用
for
循环获取1-5的数据 把每一次获取到的数据累加到变量
sum
sum = sum + x;
或者sum += x;
- 循环结束,输出变量
sum
的值
答案:
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之间偶数和
分析:
- 定义一个求和变量
sum
,初始化值是0 - 获取1-100之间的数,用
for
循环实现 判断每一个数是否为偶数,是就累加,否则不做操作
对2取余等于0,则为偶数:
x % 2 == 0
for
循环结束,输出求和变量sum
的值
答案:
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
步骤:
- 获取所有的三位数,即100-1000之间的数
- 获取每一个三位数的个位,十位,百位
个位:153 % 10 = 3
十位:153/10%10 = 5
百位:153/10/10%10 = 1 - 拿个位,十位,百位的立方和与该数本身进行比较,如果相等,则在控制台打印该数
答案:
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.需求:统计所有的”水仙花数”的个数
分析:
- 定义计数器变量
count
,初始化值为0 - 使用
for
循环获取所有的三位数,即100-1000之间的数 - 判断每一个三位数是否为水仙花数,是则
count
自增1count ++;
- 循环结束,输出计数器
count
的值
答案:
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
循环完全一致,贴个图了事,这里就不赘述了:
你可能会说,既然两种循环结构任何时候都可以互换,而且执行流程也一样,为啥还弄俩,一个不就行了吗?虽然看起来是这样,但是你有没有注意到,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
循环相同的地方是,初始化语句在整个循环结构的上面,控制条件依然在循环体的最后;不同的地方在于,循环体和判断条件的位置发生了调换,这也就意味着,先执行循环体和控制条件,再执行判断条件。
它的执行流程是这样的:
- 先执行初始化语句。这一步与 while 循环完全一样。
- 执行循环体。
- 执行控制条件。
执行判断条件:
条件成立,则再一次执行循环体、控制条件等内容
条件不成立,循环终止。
由于 do...while
循环的判断条件放在最后,所以它的循环体部分必然能执行,或者说,do...while
循环的循环体至少执行一次,这是它与前两个循环最大的不同。
回到前面提到的需求:如何用 do…while
循环实现“学完一个知识,至少练习1次”?
分析:假设练习三次之后一定能学会,那么,do...while
循环的各个部分如下:
1. 初始化条件:定义 int
型变量 count
,代表练习的次数,初始化值为 1
2. 判断条件:定义 boolean
型变量 isOK
,作为一个标记,代表是否学会,默认值为 false
3. 循环体:
判断当练习次数小于等于3时,打印正在练习的次数
每练习一次,次数加1:count ++
- 控制条件:当次数大于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.打印其它数。打印效果:
示例代码:
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;
语句进行终止。
“死循环”的意思,是指循环本身不会自动终止,而是需要依靠其它的条件才能结束。前面介绍的循环,最终都会因为判断条件不成立而终止,可死循环的判断条件永远都是成立的。三种简单的死循环格式如下:
通过上图可以看出,for
循环的死循环格式,要求判断条件为空,或者恒成立,初始化语句和控制条件并没有作任何要求;while
循环和 do...while
循环的判断条件都是 boolean
类型的常量:true
,这样也能保证判断条件的恒成立。这就是三种循环的简单死循环格式。
我们并不会容忍死循环永远的执行下去,只不过由于不知道这种循环具体的执行次数,才不得不使用这种格式。所以,一般情况下,我们都会在循环体内部设置某些条件,当条件满足就让循环终止,而不是任由它浪费CPU的资源。
举个栗子。
需求:假设纸张厚度为 0.001米,喜马拉雅山高度为8848米。请问:需要将纸张对折多少次,才能达到喜马拉雅山的高度?
分析:由于不知道循环多少次,用死循环。就使用格式简洁的 while
循环,你也可以改成另两种格式。每次循环让厚度加一倍,即厚度乘以2,这里的厚度是在上一次折叠之后的基础上,所以是累乘的效果。
- 定义
int
变量count
作为折叠计数器,代表需要折叠的次数,初始值为0,每折叠一次加1 - 定义
double
变量thickness
和height
,分别代表纸张厚度和喜马拉雅山高度,初始值分别是0.001和8848 循环体:
判断当前纸张厚度是否达到山的高度:
thickness >= height
如果已达到,即判断条件返回
true
,终止循环:break;
否则,
进行纸张的折叠:
thickness *= 2;
每折叠一次,让计数器加1:
count++;
- 循环结束,打印折叠计数器的值
示例代码:
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名同学。如果要找到所有的同学,可以一个班一个班地找,每找一个班,再逐个把该班级所有同学找出来就行了。看起来逻辑并不复杂,该怎么实现?
分析:通过观察可知,班级和同学这两组数据之间有一定的关系:班级包含同学。遍历每个班级需要用到循环,同样,遍历每个同学也需要用到循环,莫非循环之间也可以有包含关系吗?是的,我们可以在一个循环体语句中包含另一个循环语句,这种情况称为循环嵌套。班级循环称为外层循环,被包含的同学循环称为内层循环。我们先来尝试把外层循环实现出来:
初始化条件:定义
int
型变量classNumber
,初始值为1,最大值为3;- 判断条件:
classNumber <= 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件商品为次品。
分析:
先使用
for
循环遍历每一个批次,定义标号:label_batch: for () { }
- 在批次循环体中,再使用
for
循环遍历每个商品 - 判断如果批次编号为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
标号
参考代码:
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) + "个橙子");
}
}
课程总结
三种循环的基本格式和执行流程(参考上文中图):
for (初始化语句; 判断条件; 控制条件) { 循环体 }
初始化语句; while (判断条件) { 循环体; 控制条件; }
初始化语句; do { 循环体; 控制条件; } while (判断条件);
循环的终止:
break
:结束当前循环continue
:结束本次循环,继续下次循环循环嵌套:多组数据之间有包含关系时,使用循环嵌套格式:每层循环所代表的含义;变量名重复问题;
标号:循环的名字,配合
break
和continue
语句使用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。