19.1 while循环中的初始化、判断和调整
我们在学 while 循环的时候,我们要感受到一个点,什么点呢?举个例子,要在屏幕上去打印1-10,我们写的是:int i = 1,while(i <= 10),这个时候我们进来,进来之后打印 i,然后 i++如图所示:
而这个代码里边,我们要注意3个点,这个 i 初始化成多少,会直接影响到我这个代码的结果,比如说 i 写成10,那只打印一个10,结果变了
为什么呢?因为 i 是10的时候,在 while 循环后边,圆括号里边的表达式≤10只能进去一次,i++变成11就进不去了,如图所示:
所以 i 是多少,非常重要,这叫初始化,就是我们给这个 i 赋一个初始值是多少,这是非常重要的,如图所示:
其次,影响到这个循环,还有另外一个地方就是 while(i <= 10).比如 while(i <= 10)写成while(i <= 10)和写成 while(i <= 100)就完全是两码事。所以这个地方叫判断部分,如图所示:
然后对于这个循环来说,这个 i++也很重要,i 写成++,那这个时候每次+1,那循环10次。那如果这个地方写成 i+=2,这个地方次数就明显发生变化了,所以这个地方叫调整部分,如图所示:
3个部分,初始化、判断和调整,3个部分任何一个部分发生变化,都会影响到循环,随着我们的代码写的越来越多,这3个部分会越来越分散,比如前面初始化又加了很多代码,判断里边又加了很多代码,这3个部分就离的越来越远,越来越远。
而如果未来我们要改动我们的循环的时候,就会涉及到3个东西的一个改变,比如说我们的初始化变量从5开始,而一旦改,这3个部分离的比较远,这个时候就比较麻烦了,我得找到对应的部分才可以做,所以 while 循环从形式上讲,3个部分离的太远,就不太好
而这个时候,出来一个 for 循环就解决了这个问题,for 循环把初始化、判断和调整3个部分集中在了一起
19.2 for 循环基本的书写形式
for 循环的基本语法形式是这样的:for 后边跟一个圆括号,圆括号里边放的是表达式1分号,表达式2分号,表达式3。这三个表达式中,第一个表达式就是初始化;第二个表达式就是判断;第三个表达式就是调整。如图所示:
如果我们现在,想用 for 循环来实现我们刚刚前面屏幕上打印的1-10,我们该如何去做呢?int i = 0,我们现在用 for 循环,for 循环中间有2个分号,3个部分
第一个部分,我们要初始化,i 从多少开始呢?你既然要打印1-10,i 肯定从1开始,即 i = 1;第二个部分,i 判断的时候最大是10,所以我们写 i<= 10;第三个部分,也就是调整部分,i 每次是怎么变化的呢?i++就可以了
下面一对大括号,这是 for 循环的循环体,里边进去之后就做一件事情,打印就可以了,如图所示:
那这个代码,跟我们刚刚的这个循环是不是匹配的呢?i = 1就是我们的表达式1,就是我们的初始化部分,我让 i 从1开始;i <= 10这就是我们的判断部分,i++是我们的调整部分
而且你会发现,我们把初始化、判断和调整三个部分放在了一起,这样从形式上讲,更优于 while 循环,这就是 for 循环的优势,所以使得我们在循环里边使用的时候,for 循环用的最多,次之是 while 循环,再次就是 do...while 循环。do...while 循环应用的是最少的。如图所示:
for 循环的执行流程图,它的执行流程是首先不管三七二十一,上来执行表达式1,表达式1就是我们的初始化部分,初始化部分一执行之后,看我们的判断部分,判断部分如果为非0,就为真,我就进入循环,执行语句,语句执行完之后,再上来看表达式3调整,调整完了之后,再去判断,依此类推
这个执行的过程中,你会发现这个第一步初始化部分是不是只被执行了一次,这就是 for 循环的特点,for 循环的特点是:初始化部分,只会被执行1次,不管循环多少次,我的初始化只有1次,如图所示:
19.3 for循环中的break和continue
当然我们有细心也看到了:在这个循环执行的过程中,在循环语句里边,完全有可能出现 break 和 continue,那出现 break 和 continue 又是怎么执行的呢?我们继续往下学习
我们还是按照这个代码来讲,如果 i 呢如果等于等于5,if(i == 5),那就 break,那这个代码的结果又是什么?根据我们对于 while 循环的了解,这个 break 的作用是跳出循环
而现在如果我真的就跳出循环,等于5的时候终止,那5打印了么?没有。那这个代码的结果,应该还是我们曾经学过的1-4,这就是 for 循环里的 break,它也是用于终止整个循环,只要 break 执行了,循环就终止了,如图所示:
那如果我们能够理解 break 的这种写法,那我们换成 continue 呢?现在的结果是什么?不会有死循环的可能性,为什么呢?continue 它是跳过本次循环 continue 后边的代码,它只是把 continue 后边的代码跳过去了
也就是说,一旦跳过第71行代码之后,它紧接着上来要执行的是 i++,因为如果要执行了这个 printf("%d\n", i) 下来,就是执行 i++,所以当我们跳过去 continue 后边的代码的时候,它是不会把整个循环终止的
而这一次循环 continue 后边的代码可能不执行了,上来还要执行 i++的,i++执行完之后 i 就变了,就变成6了,6就不会使得 continue 跳过了,所以6,7,8,9,10照样打印出来,如图所示:
while 循环和 for 循环中 continue 的对比:while 循环里的 continue 和 for 循环里的 continue 是有所去别的,for 循环里的 continue 跳到调整部分进行 i++,而 while 循环里的 continue 完全有可能把调整部分跳过去,导致程序死循环
19.4 for语句的循环控制变量
for 循环的循环变量控制,我们有一些小小的建议:
19.4.1. 不可在 for 循环体内修改循环变量,防止 for 循环失去控制
举个例子,i 是我们的循环变量,i 是1,i <=10,i++。调整是在圆括号内调整的,而如果有人在第84行代码写了个 i = 5,这个代码输出的结果,还是不是我们的1-10了?就不再是了,而且会导致这个代码死循环
为什么这个代码死循环了呢?就是因为循环内部改变循环变量,本来循环变量是放在放在圆括号内去调整的,每次 i++一下,非要在第84行代码把循环变量改一下,所以代码就出问题了,如图所示:
调整部分能不能不放在圆括号内,我把这个调整放在第84行代码行不行呢?也是可以的,如果你明确的知道,这个时候放在这可以,又不想在圆括号内部放,这是没问题的,如图所示:
但是如果有一个人说,圆括号内好像漏东西了,i++再加上,这个时候我们会发现,在循环的调整部分调整了,而且在循环的里边 i++也把循环变量改了一次,那这个时候再看结果,结果就不再是我们想的那样1-10了,如图所示:
因此,在循环体内,去改变循环变量,会让循环发生意想不到一些结果,所以尽量不要在 for 循环的循环体内修改修改循环变量,防止 for 失去控制。就是说,这个本来是这样的,后来发现跟我们现象的不一样,其实就是因为循环体内不小心把它改掉了
有时候我们在 for 循环嵌套的时候,我们用个 j 变量来写,结果我们写着写着 for(j = 0; j <10; i++),这个时候的结果,可能也不会是我们想要的,比如我们打印一个东西 printf("hehe")
我们这个代码是怎么执行的呢?i = 1的时候进来一次,打印1,打印完之后下面的循环就要走了,而这个循环走的过程中,我们每次判断的是 j < 10,j 肯定不小于10,因为这个过程我们没让 j 加加,而每次上来之后 i++,因为我们让 i++了,j 没有++,所以 j 永远都是小于10的
所以这个代码我们会发现:这个循环内部,去改变循环变量,这种方式是不可取的,如图所示:
19.4.2. 建议 for 语句的循环控制变量的取值采用"前闭后开区间"写法
就是说我们在写代码的时候,尤其是写这个 for 循环的时候,如果有机会的话,我们可以写前闭后开区间的写法
举个例子,假设 int arr = {0},这是10个元素的一个数组,数组的下标是从0开始的,数组是通过下标来访问的,所以我就写 int i = 0,for(i = 0; i<10; i++),然后打印 arr[i],我们想通过下标 i 访问到这个数组的每个元素
现在我在写这个循环变量的时候,左边 i 初始化的时候,从0开始的,i < 10最大是9,这叫开区间,i = 0叫闭区间,所以这叫左闭右开区间的写法,如图所示:
有人就说我不这样写行不行?写成 i<= 9,这叫左右都是闭区间的写法,行不行呢?能不能达到效果呢?也是可以的,打印10个0这是妥妥的,如图所示:
但是,不建议这样去写,我们建议左边闭区间,右边开区间的这种写法,这种写法更好一些,为什么更好呢,此时此刻,当我们这样去写的时候,我们会发现这个10好像具有某种意义,它循环10次,并不是说写成 <= 9就错了,而是说风格、写法、理解程度和可读性会更好一些。当然,这都是建议,不是绝对要这样写
19.5 for循环的变种
19.5.1 变种1
for 循环在写的时候,会有一些变种,举个例子,写个 for分号分号,然后打印 hehe,这好像在死循环的打印,为什么呢?因为关于这个 for 循环的初始化、判断和调整都可以省略,想省略哪一个,我们就省略哪一个,从语法上,都是支持的
但是,判断部分的省略,判断部分恒为真,那这个代码最终死循环了,如图所示:
初始化可以省略;判断也可以省略;调整也可以省略。或者三个同时都省略都行,但是一旦让判断部分省略,这个循环的判断部分就恒为真了,就可能会导致我们的死循环,所以我们要慎重省略,我们也不建议随便省略,为什么呢?当我们理解的不够透的时候,或者说我们把握不够足的时候,不要随便省略,因为随便省略会导致问题
举个例子,假设 int i = 0,int j = 0,for(i = 0; i < 3; i++),for(j = 0; j < 3; j++),然后打印,结果会打印9个 hehe,如图所示:
又有人好奇:前面的 i = 0,j 也等于0,不是赋值0了么?那第122行代码 i = 0不就多此一举么?第124行代码 j = 0不就多此一举么?我们把它去掉,去掉之后,我们再来看,这个时候我们代码打印3个 hehe
为什么是3个 hehe 呢?这就是省略带来的原因,当 i = 0的时候,i < 3可以进来。进来之后,嵌套的 for 循环整体都要执行了,这个执行的过程中,j++ j 总会变成3,它不满足了,j 变成3的时候,已经打了3个 hehe 了,当我3个 hehe 打印完之后再上去回到第122行代码
然后 i要加加一次,i++变成1,1 < 3进来,进来之后此时此刻的 j,没有被初始化成0了,j 还是我们上一次留的3,因为 j 在外边创建的,用完之后 j 已经变成3了,3没有改。没有改的时候 j < 3就进不去了,<3 进不去的时候,那里边的 for 循环就截止了
截止的时候再上来,上来 i++变成2,2<3又进来了,你会发现这一次进来的时候,j 还是3,所以又没有打印,依此类推,当 i++变成3的时候,小于3就不满足条件了,它跳出去了,那整个就打印第一次 i = 0 的时候进来的3次 hehe,如图所示:
而如果我们能在第124行代码加上一句话,叫 j = 0,每一次下来之后,j 不管变成多少,都先回归成0,这样的话就又能循环3次。我们省略给我们带来的一个不变,所以当我们还不会跑的时候,我们就慢慢地走,不要随便省略,这些省略给我们会带来很多的一些问题
19.5.2 变种2
for 循环的循环变量控制,可能不是一个变量,int x = 0,int y = 0,我们这个循环是由2个循环变量控制的,xy同时控制,同时初始化。判断的时候 x < 2 && y < 5,++x,y++,当然这个前置后置都无所谓的
这种循环也是支持的,就是说为了一个循环想要两个循环变量来控制的时候,也是可以的,如图所示:
19.6 for循环面试题
请问循环要循环多少次,答案:循环0次!因为最开始 k 是0,i 是0,i 初始化成0,k 初始化成0,然后 k 被赋值成0, 这个地方是判断部分,判断部分把0赋给 k,这个表达式的结果就是0,0为假,循环一次都没进去(k = 0中是一个等号,它叫赋值,而不是判断)
这个表达式的结果,它是为假的,为假就一次都不进去,所以这个循环循环了0次!如果写成 k == 0,最起码能够循环一次,如图所示:
这个题不是在判断 k 是不是等于0,而是把0赋给 k,0为假,为假这个循环一次都不进去,所以这个循环0次
补充:
1. 为什么判断部分省略是恒为真?这个是规定,语法就是这么规定的
2. 如果 for 循环里边嵌套 for 循环,是两个 for 循环都跳过吗?不是,各自跳各自的,一个 break 只能跳出一层循环,只能跳出自己的循环。不可能跳出两层循环,那不就乱套了吗,那三层循环就跳出三层,四层循环就跳出四层,那怎么从第一层跳到第二层呢?我只跳出一层,那怎么做呢?是不是做不到了,所以它其实只能跳出一层,一个 for 循环只能跳出一层,如果多层嵌套,那我们要每一层都加上 break 才能跳出来。