C foundations / Lesson 05

让程序重复工作,
也知道何时停下。

采集四次数据,不必把相同代码复制四遍。本课从一个能运行的累计程序开始,学习循环的初始化、判断、更新与边界,逐步认识 for、while、do-while。

05 / C 语言基础建议先学条件与分支先预测,再运行
01 / 目标

重复执行,也要能解释结束条件

条件语句决定一次分支;循环在条件允许时反复执行一段代码。学完后,你应当能够:

  • 读懂循环的初始状态、继续条件、循环体和更新步骤。
  • 预测 for、while 与 do-while 在边界情况下执行几次。
  • 区分 break 和 continue,检查循环变量是否能走向结束。
  • 编译运行一个累计示例,并通过修改上限验证输出。
本课产出

一个对 1、2、3、4 做累计的小程序和一张手写追踪表。示例数字是模拟数据,尚未读取真实传感器。

02 / 实验

创建并运行 lesson05.c

先预测程序会打印几行、最后的总和是多少,再输入并运行:

lesson05.cC11 · 本课主例程
#include <stdio.h>

int main(void)
{
    int sum = 0;

    for (int sample = 1; sample <= 4; sample++) {
        sum = sum + sample;
        printf("sample=%d total=%d\n", sample, sum);
    }

    printf("sum=%d\n", sum);
    return 0;
}
WINDOWS · MSYS2 UCRT64

编译与运行

gcc -std=c11 -Wall -Wextra lesson05.c -o lesson05.exe
./lesson05.exe
UBUNTU

编译与运行

gcc -std=c11 -Wall -Wextra lesson05.c -o lesson05
./lesson05

请在源码所在目录运行。预期输出:

sample=1 total=1
sample=2 total=3
sample=3 total=6
sample=4 total=10
sum=10

这里 sum 是累计和,不是循环次数。循环结束后,函数体内的最后一条 printf 只执行一次。

03 / 追踪

把每一次变化写进表格

sum 开始为 0。每次进入循环体,先执行加法与赋值,再打印当前结果;随后执行更新 sample++,然后重新判断条件。

轮次进入循环时 sample计算前 sum计算后 sum
1101
2213
3336
44610

第四轮结束后,更新使 sample 变成 5。判断 5 <= 4 为假,因此循环结束,不会执行第五轮。

手算时的顺序:初始化一次 → 判断 → 执行循环体 → 更新 → 再判断。把每一步产生的值写下来,可以发现漏掉更新和多执行一轮的问题。
04 / FOR

拆开 for 的三个部分

for (int sample = 1; sample <= 4; sample++) {
    /* 循环体 */
}
初始化:int sample = 1
进入循环前执行一次,创建并初始化循环变量。
继续条件:sample <= 4
每轮开始前检查;非零为真,零为假。第一次就为假时,循环体一次也不执行。
更新:sample++
每次正常执行完循环体后加 1,再检查条件。这里没有使用表达式的结果;先理解它改变了变量。

for (int i = 0; i < 4; i++) 的循环体也执行四次,i 依次为 0、1、2、3。以后遍历数组时,常见的合法下标从 0 到元素数减 1。

三个部分之间用分号分隔。三个表达式都可以省略,但省略条件时不能误以为会自动结束;本课先明确写出条件和更新。

05 / WHILE

条件成立就继续:while

while 在每次执行循环体之前检查条件。下面片段可放进已有 main 中运行:

int attempt = 0;
while (attempt < 3) {
    attempt++;
    printf("attempt=%d\n", attempt);
}

依次输出 attempt=1、attempt=2、attempt=3,随后因为 3 < 3 为假而结束。如果初值就是 3,循环体不会执行。与 for 相比,初始化和更新写在不同位置,更需要主动检查更新是否发生。

结束条件要可达

如果忘记 attempt++,且没有其他代码改变 attempt,条件会持续为真,程序不会正常走出这个循环。可先画出变量变化,再运行测试。

06 / DO-WHILE

先执行,再判断:do-while

int count = 3;
do {
    printf("count=%d\n", count);
    count++;
} while (count < 3);

这段代码先打印一次 count=3,再将 count 加到 4,最后判断为假并退出。即使开始时条件为假,循环体仍会执行一次。注意末尾 while (...); 的分号是语法的一部分。

对比:把同样的条件放在普通 while 中,初始 count = 3 时循环体执行零次。选哪种写法取决于任务是否必须先执行一次。

07 / 控制

提前结束还是跳过本轮?

break 退出包含它的最内层循环或 switch;continue 跳过当前循环体剩余部分,进入下一轮所需的处理。下面的片段打印 0 1 3 4,忽略整数 2:

for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue;
    }
    printf("%d ", i);
}
printf("\n");

在 for 中,continue 后仍会执行更新表达式 i++,再判断条件。在 while 中,continue 直接进入下一次条件判断;如果更新写在它之后,就会跳过更新,因此应重新检查结束条件。

在嵌套循环中,break 退出包含该语句的最内层循环;若循环内部的 switch 包含这个 break,它只退出该 switch。需要结束外层循环时,应明确设计外层条件或状态。

08 / 边界

从循环次数到变量作用范围

for (int i = 0; i < 4; i++) 中声明的 i 可用于该循环的条件、更新和循环体;循环结束后不能在循环外直接使用它。如果结束后还需要这个值,可在循环前声明:

int i;
for (i = 0; i < 4; i++) {
    printf("%d\n", i);
}
printf("after=%d\n", i); /* 4 */

边界是循环中最值得测试的地方:起点、最后一个满足条件的值、第一个不满足条件的值,以及第一次条件就为假的情况。若变量数值可能达到类型范围边界,还要检查更新是否会产生超出范围的结果;不能依赖有符号整数溢出。

联系 STM32:重复轮询状态是一类常见任务,但空循环的实际运行时间会受到编译优化、时钟和执行环境影响。计时应根据具体芯片使用定时器等可靠机制,不能把本课循环次数当作精确延时。
09 / 排错

四个常见问题

现象检查点处理
多执行一次< 和 <= 的最后一个值不同写出第一轮和最后一轮的变量值。
一直不退出更新遗漏、方向错误或被 continue 跳过追踪条件与变量变化;先在小范围验证。
循环体没有执行第一次判断是否已经为假核对初值和比较方向。
循环外提示变量未声明变量是否声明在 for 内若确需循环外使用,在合适的外层作用域声明。

另外,while (condition); 末尾的分号构成空语句;紧随其后的花括号不会成为这个 while 的循环体。应仔细检查多余分号和缩进。

10 / 练习

写下预测,再展开答案

LEVEL 01 / 次数

少一次还是多一次?

将主例程条件改为 sample < 4,累计输出几轮,最后 sum 是多少?

查看参考答案

循环体执行三轮,sample 取 1、2、3,最后 sum=6。

LEVEL 02 / WHILE

预测边界

int n = 3; while (n < 3) { n++; } 的循环体执行几次?结束后 n 是多少?

查看参考答案

第一次判断就是假,执行零次,n 保持 3。

LEVEL 03 / CONTINUE

避开遗漏更新

若在 while 中因为某个值执行 continue,而变量更新位于它后面,会发生什么?

查看参考答案

该轮更新会被跳过;如果条件继续为真且状态没有其他变化,可能一直重复。应调整更新位置或设计其他可证明的退出条件。

11 / 验收

什么时候进入下一课?

  1. 能不看答案写出四次累计并核对最终结果。
  2. 能根据初值与条件判断循环执行零次还是多次。
  3. 能说明 for 中 continue 之后的更新步骤。
  4. 能指出一个循环的结束条件,以及变量怎样向它靠近。

接下来进入函数,把重复使用的计算封装为有名字的程序片段。

资料

对照官方资料继续查