5. 流程控制(下):while循环、do-while循环、for循环、break与continue、循环嵌套
循环,说白了就是让计算机反复干同一件事。你想想看,如果让你手动打印1到100,你肯定疯了。但计算机不会,它只会老老实实按你的指令跑。这就是循环的价值。
我个人习惯把循环分成三类:先判断再执行的while、先执行一次再判断的do-while、以及计数型的for。它们各有各的脾气,用错了地方,代码就会变得别扭。
5.1 while循环:先看门再进门
while循环的语法很简单:
while (条件表达式) {
// 循环体
}
它的执行逻辑是:先判断条件,条件为真才执行循环体。如果一开始条件就是假,循环体一次都不执行。
我在项目中遇到过一个问题:用while循环读取传感器数据,结果传感器初始化失败,条件永远为假,循环体一次都没跑。程序静悄悄地跳过去了,调试了半天才发现。嗯,这里要注意——while循环可能一次都不执行,如果你必须保证循环体至少跑一次,那就得换do-while。
核心要点:while循环适合「先检查,后执行」的场景。比如等待某个标志位被置位,或者轮询硬件状态。
5.2 do-while循环:先进门再看门
do-while和while的区别就一句话:先干再说。
do {
// 循环体
} while (条件表达式);
注意末尾有个分号,别漏了。我曾经因为这个分号被坑过——编译报错找了半天,结果就是少了个分号。
do-while保证循环体至少执行一次。这在嵌入式里很有用。比如你要发送一个启动命令给外设,然后等待它回应。不管外设有没有准备好,你总得先把命令发出去吧?
小技巧:我写菜单程序时特别喜欢用do-while。用户至少能看到一次菜单,然后根据输入决定是否继续。这样用户体验好,代码也干净。
5.3 for循环:计数器的首选
for循环是C语言里最常用的循环。它的结构很紧凑:
for (初始化; 条件; 更新) {
// 循环体
}
三个部分都可以省略,但分号不能省。比如 for (;;) 就是一个死循环,等价于 while(1)。
我个人习惯用for循环处理已知次数的迭代。比如遍历数组:
int arr[10] = {0};
for (int i = 0; i < 10; i++) {
arr[i] = i * 2;
}
你看,初始化、条件判断、步进更新,全写在一行里,一目了然。但要注意,C99之前不支持在for里声明变量,如果你用老编译器,得把 int i 提到外面。
避坑指南:我曾经在for循环里修改了循环变量,导致死循环。比如 for (i=0; i<10; i++) { i = 0; } —— 这种低级错误,调试时真想抽自己。记住:不要在循环体里随意修改循环控制变量,除非你很清楚自己在做什么。
5.4 break与continue:循环的刹车和跳过
这两个关键字是循环的「控制杆」。用好了代码优雅,用不好逻辑混乱。
| 关键字 | 作用 | 适用场景 |
|---|---|---|
| break | 立即跳出当前循环 | 找到目标后提前结束 |
| continue | 跳过本次循环剩余代码,进入下一次 | 过滤掉不需要处理的数据 |
举个例子。你要在一个数组里找第一个负数:
int data[] = {3, 5, -1, 7, -2, 9};
for (int i = 0; i < 6; i++) {
if (data[i] < 0) {
printf("找到负数: %d\n", data[i]);
break; // 找到了,不用再找了
}
}
而continue常用于跳过非法数据:
for (int i = 0; i < 6; i++) {
if (data[i] < 0) {
continue; // 负数不处理
}
printf("处理正数: %d\n", data[i]);
}
我的经验:break和continue在嵌套循环里只作用于最内层的循环。如果你想跳出多层循环,要么用goto(不推荐),要么加一个标志变量。我一般用标志变量,代码可读性更好。
5.5 循环嵌套:循环里面套循环
循环嵌套就是循环体里再放一个循环。最常见的例子是二维数组遍历:
int matrix[3][4] = {0};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
matrix[i][j] = i * 4 + j;
}
}
外层循环控制行,内层循环控制列。这个模式在嵌入式里太常见了——处理图像数据、矩阵运算、或者扫描键盘矩阵。
但要注意性能问题。我曾经优化过一个程序,把内外层循环的顺序调换了一下,执行时间直接减半。为什么?因为内存访问的局部性原理。内层循环访问连续内存地址时,缓存命中率更高。
优化建议:写嵌套循环时,把循环次数多的放在内层,次数少的放在外层。这样能减少外层循环的跳转开销,对性能有好处。
5.6 三种循环的选择指南
说了这么多,到底该用哪个?我总结了一个简单的判断方法:
- 知道循环次数 → 用for。比如遍历数组、固定次数的计算。
- 不知道次数,但需要先判断条件 → 用while。比如等待某个事件发生。
- 至少需要执行一次 → 用do-while。比如菜单显示、发送初始化命令。
其实这三种循环在底层是可以互相转换的。你完全可以用while模拟for,也可以用for模拟do-while。但既然语言提供了不同的工具,咱们就按场景选最合适的,代码读起来也舒服。
注意:不管用哪种循环,一定要确保循环条件最终会变成假。否则就是死循环。嵌入式系统里死循环的后果可能是看门狗复位、系统卡死。我见过同事调试时忘了给循环变量自增,结果程序跑飞了,整块板子死机。
知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以把它当作一个快速参考:
这张图把三种循环的关系和各自特点都标清楚了。你写代码时拿不准用哪个,回来瞄一眼就行。
好了,循环控制这部分就讲到这里。记住:循环是让计算机替你干苦力活的工具,但前提是你得把条件写对、把边界想清楚。我见过太多因为循环条件写反了导致死循环的案例,包括我自己也犯过。所以,写循环时多问自己一句:「这个循环能结束吗?」
最后送你一句话:写循环就像开车——知道什么时候加速(for)、什么时候等待(while)、什么时候必须走一趟(do-while),以及什么时候刹车(break)和变道(continue)。开稳了,代码自然就优雅了。