13、循环结构(上):while循环、do-while循环、循环的适用场景、循环的嵌套
说到循环,我脑子里第一个蹦出来的画面,是当年调试一个串口接收程序时的场景。那个程序需要不停地轮询接收标志位,直到数据到达。那时候我刚入行,写了个死循环把自己卡死了——嗯,从那以后我就对循环结构有了刻骨铭心的认识。
循环说白了,就是让一段代码反复执行。你想想看,如果让你写一个程序,打印1到100,没有循环的话你得写100行printf。有了循环,三行代码搞定。这就是循环的价值。
13.1 while循环:先判断,后执行
while循环的结构很简单,我直接上代码:
while (条件表达式) {
// 循环体
// 当条件为真时,反复执行这里的代码
}
它的执行流程是这样的:先判断条件,条件成立就执行循环体,执行完再回来判断。条件不成立,直接跳过。所以while循环有可能一次都不执行——如果一开始条件就是假的。
我举个例子,咱们写一个倒计时程序:
#include <stdio.h>
int main() {
int count = 5;
while (count > 0) {
printf("倒计时:%d\n", count);
count--; // 别忘了改变条件,否则死循环
}
printf("发射!\n");
return 0;
}
运行结果:
倒计时:5
倒计时:4
倒计时:3
倒计时:2
倒计时:1
发射!
13.2 do-while循环:先执行,后判断
do-while和while的区别,说白了就是一个「先斩后奏」。先执行一次循环体,然后再判断条件。所以do-while至少会执行一次。
do {
// 循环体
// 先执行一次,再判断条件
} while (条件表达式); // 注意这里有个分号!
我拿刚才的倒计时改一下:
#include <stdio.h>
int main() {
int count = 0; // 注意,这次count初始就是0
do {
printf("count = %d\n", count);
count++;
} while (count < 5);
return 0;
}
运行结果:
count = 0
count = 1
count = 2
count = 3
count = 4
你想想看,如果换成while,count初始为0,条件count < 5一开始就不成立,循环体一次都不会执行。但do-while至少会打印一次。这个特性在某些场景下特别有用。
13.3 循环的适用场景
我做了这么多年嵌入式,总结下来,循环主要用在以下几个场景:
| 场景 | 说明 | 推荐循环 |
|---|---|---|
| 已知循环次数 | 比如遍历数组、打印固定次数 | for循环(下节讲) |
| 未知循环次数,但知道结束条件 | 比如等待某个标志位、读取传感器直到数据稳定 | while循环 |
| 至少需要执行一次 | 比如菜单选择、用户输入验证 | do-while循环 |
| 无限循环 | 比如嵌入式主循环、操作系统调度 | while(1) 或 for(;;) |
我在项目中遇到过这样一个场景:读取一个温度传感器,传感器需要100ms才能稳定输出。这时候我就用while循环,不断读取状态寄存器,直到状态位变成「数据就绪」。循环次数不确定,但结束条件很明确——这就是while的典型应用。
13.4 循环的嵌套
循环嵌套,就是一个循环里面再套一个循环。说白了就是「外层循环每执行一次,内层循环完整跑一遍」。
我拿打印九九乘法表来举例,这个例子很经典:
#include <stdio.h>
int main() {
int i = 1, j;
while (i <= 9) {
j = 1;
while (j <= i) {
printf("%d*%d=%-2d ", j, i, i * j);
j++;
}
printf("\n");
i++;
}
return 0;
}
运行结果:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
...(后面省略)
这里外层循环控制行数(i从1到9),内层循环控制每行的列数(j从1到i)。每次外层循环i加1,内层循环就重新从1开始跑。
- 内层循环的初始化要放在外层循环里面。你看上面代码,j = 1 是放在外层while里面的,每次外层循环开始,j都重新赋值为1。
- 嵌套层数不要太深。我见过有人嵌套了五六层,代码读起来跟迷宫一样。一般两层就够用了,三层已经是极限。
- 注意性能。内层循环的执行次数 = 外层次数 × 内层次数。如果外层1000次,内层1000次,那就是100万次。在嵌入式系统里,这可能会影响实时性。
下面这张图,是我画的一个循环嵌套的执行流程,你看一眼就明白了:
- 内层循环的变量和外层循环的变量搞混了。我见过有人内外层都用i,结果内层把i改了,外层循环乱套了。
- 忘记重置内层循环的计数器。比如上面乘法表的例子,如果j = 1写在while外面,那内层循环只跑一次就完了。
- 嵌套太深导致代码可读性差。我个人建议,如果超过三层,就考虑拆成函数吧。
13.5 实际项目中的循环选择
说了这么多,到底什么时候用while,什么时候用do-while?我分享几个实际经验:
- 等待硬件就绪:用while。比如等待ADC转换完成、等待SPI发送完毕。因为硬件可能已经就绪了,不需要先执行一次。
- 用户输入验证:用do-while。用户至少得输入一次,然后我们判断输入是否合法,不合法就继续问。
- 主循环:用while(1)。嵌入式程序的主循环就是无限循环,不需要结束条件。
- 遍历链表:用while。你不知道链表有多长,但知道结束条件是p != NULL。
我记得有一次,一个同事用do-while去等待一个串口标志位。结果标志位在进入循环前就已经置位了,do-while先执行了一次发送操作,导致多发送了一帧数据。从那以后,他再也不敢乱用do-while了。
循环结构就讲到这里。你想想看,while和do-while的区别其实就一句话:while是先看再干,do-while是先干再看。记住这个,就不会搞混了。下一节我们讲for循环,那又是另一种风格了。
公众号:蓝海资料掘金营,微信deep3321