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。但既然语言提供了不同的工具,咱们就按场景选最合适的,代码读起来也舒服。

注意:不管用哪种循环,一定要确保循环条件最终会变成假。否则就是死循环。嵌入式系统里死循环的后果可能是看门狗复位、系统卡死。我见过同事调试时忘了给循环变量自增,结果程序跑飞了,整块板子死机。

知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以把它当作一个快速参考:

C语言循环控制结构总览 while 循环 do-while 循环 for 循环 先判断,后执行 可能一次都不执行 适合:等待事件、轮询 语法:while(条件) { } 先执行,后判断 至少执行一次 适合:菜单、初始化命令 语法:do { } while(条件); 计数型循环 初始化、条件、更新合一 适合:遍历数组、固定次数 语法:for(; ;) { } 控制语句:break 与 continue break → 跳出整个循环 continue → 跳过本次,进入下次

这张图把三种循环的关系和各自特点都标清楚了。你写代码时拿不准用哪个,回来瞄一眼就行。


好了,循环控制这部分就讲到这里。记住:循环是让计算机替你干苦力活的工具,但前提是你得把条件写对、把边界想清楚。我见过太多因为循环条件写反了导致死循环的案例,包括我自己也犯过。所以,写循环时多问自己一句:「这个循环能结束吗?」

最后送你一句话:写循环就像开车——知道什么时候加速(for)、什么时候等待(while)、什么时候必须走一趟(do-while),以及什么时候刹车(break)和变道(continue)。开稳了,代码自然就优雅了。

公众号:蓝海资料掘金营,微信deep3321