跳转至

导学案4 · 循环:让程序学会重复

前置要求:导学案3(会写 if / else if / switch,理解"真假就是 1 和 0") 参考课程:浙江大学 翁恺《C 语言程序设计》第 4~5 章(循环、循环控制) 预计用时:2.5 小时(初学者会感到陌生,按需自取) 本节目标:会用 while / for / do-while,会用 break 和 continue,能读懂嵌套循环


4.0 序

先做个小任务:请打印 1 到 100,每个数一行。

如果一行写一个 printf ,那刚写到前几行你就会想放弃了。面对重复的劳动,循环可以解决的问题:把重复的动作交给计算机。

到这里,程序设计的三大基本结构就集齐了:顺序(导学案2)、选择(导学案3)、循环(本节)。那句很有名的结论现在你可以自己验证了:任何逻辑都能用这三种结构拼出来。

本节路线:while → for → do-while → break / continue → 嵌套循环 → 练习

本节不讲:数组、函数、指针。需要"存一串数"的题目,我们用"滚动变量"绕过(比如斐波那契)。数组在导学案5。


4.1 while 循环

4.1.1 骨架与三要素

while (条件) {
    /* 条件为真,就反复执行这里 */
}

任何一个循环都由三要素组成,缺一不可:

要素 在代码里的位置 少了会怎样
① 初值 循环之前给变量赋值 用到了垃圾值,结果随机
② 条件 while (...) 的括号里 编译报错
③ 更新 循环体内改变量 死循环,永远出不来

写完一个循环,请自己对照这张表检查一遍。这是本节最实用的一条建议。

4.1.2 demo 1:打印 1 ~ 10

#include <stdio.h>

int main(void)
{
    int i = 1;                 /* ① 初值 */

    while (i <= 10) {          /* ② 条件 */
        printf("%d\n", i);
        i = i + 1;             /* ③ 更新,也可以写成 i++ */
    }

    return 0;
}

就会输出1 ~ 10,它执行过程是:判断条件 → 执行循环体 → 更新 → 回到判断条件……直到条件为假,跳出循环往下走。

4.1.3 demo 2:累加求和 1 + 2 + ... + 100

#include <stdio.h>

int main(void)
{
    int i = 1;
    int sum = 0;               /* 累加器必须从 0 开始 */

    while (i <= 100) {
        sum += i;              /* 等价于 sum = sum + i */
        i++;
    }

    printf("1 加到 100 = %d\n", sum);
    return 0;
}

输出:

1 加到 100 = 5050

sum 这种变量叫累加器。请特别注意 int sum = 0; 里的 = 0:如果只写 int sum;,里面就是导学案2讲过的垃圾值,你最后会得到一个莫名其妙的数——而且不报错。

4.1.4 while 是"先判断,后执行"

如果条件一开始就不成立,循环体一次都不会执行:

#include <stdio.h>

int main(void)
{
    int i = 100;

    while (i < 10) {
        printf("这一行永远不会被打印\n");
        i++;
    }

    printf("循环体一次都没执行,i 还是 %d\n", i);
    return 0;
}

输出:

循环体一次都没执行,i 还是 100

4.1.5 死循环:亲手制造一次

/* 反面示例:故意制造死循环,试完请立刻停掉 */
#include <stdio.h>

int main(void)
{
    int i = 1;

    while (i <= 10) {
        printf("%d\n", i);     /* 忘了写 i++,条件永远成立 */
    }

    return 0;
}

屏幕会被数字刷满,程序停不下来。怎么停:

  • 命令行里按 Ctrl + C,或者直接关掉窗口;
  • Dev-C++ 点工具栏的"停止"按钮;
  • VS 点"调试 → 全部终止"(Shift + F5)。

4.2 for 循环

Dev-C++ 用户请先做一次设置(一次设置,永久生效)

本节开始大量出现 for (int i = 0; ...) 这种在 for 里面声明变量的写法,它属于 C99 标准。Dev-C++ 自带的 gcc 4.9.2 编译器比较老,可能不支持,会报:

error: 'for' loop initial declarations are only allowed in C99 mode

解决办法:工具 → 编译选项 → "编译器"选项卡 → 勾选"编译时加入以下命令" → 填 -std=c99 → 确定。(也可以在"代码生成/优化"选项卡里,把"语言标准"改成 ISO C99。)

如果你不想改设置,把变量声明在 for 外面也完全一样:int i; for (i = 0; ...)。VS 用户不需要做这件事。

4.2.1 骨架:把 while 的三要素搬到一行

for (初值; 条件; 更新) {
    /* 循环体 */
}

同一段逻辑,两种写法完全等价,请并排对照着看:

/* while 版 */
int i = 1;
while (i <= 10) {
    printf("%d\n", i);
    i++;
}
/* for 版 */
for (int i = 1; i <= 10; i++) {
    printf("%d\n", i);
}

for 的好处是:三要素全写在一行里,想漏都难。 这就是为什么实际项目里 for 用得比 while 多。

4.2.2 执行顺序

① 初值(整个循环只做一次)
      ↓
② 判断条件 ── 假 ──→ 跳出循环,往下执行
      ↓ 真
③ 执行循环体
      ↓
④ 执行更新
      ↓
   回到 ②

注意:初值只在最开始执行一次,之后每圈都是"判断 → 循环体 → 更新"。

4.2.3 demo:三种常见走法

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 5; i++) {          /* 正着数 */
        printf("%d ", i);
    }
    printf("\n");

    for (int i = 5; i >= 1; i--) {          /* 倒着数 */
        printf("%d ", i);
    }
    printf("\n");

    for (int i = 0; i <= 10; i += 2) {      /* 步长 2 */
        printf("%d ", i);
    }
    printf("\n");

    return 0;
}

输出:

1 2 3 4 5
5 4 3 2 1
0 2 4 6 8 10

4.2.4 for 还是 while?

一句话结论:次数已知用 for,条件驱动用 while。

  • "做 100 次" → for
  • "直到用户输入正确为止" → while

4.3 do-while 循环(用得最少,认识即可)

do {
    /* 先执行一次 */
} while (条件);

和 while 的唯一区别:do-while 先执行后判断,所以循环体至少会执行一次。

它的天然场景就是"至少要来一遍"的事:显示菜单、要求用户输入并校验。

#include <stdio.h>

int main(void)
{
    int n = 0;

    do {
        printf("请输入一个 1~3 的整数:");
        scanf("%d", &n);
    } while (n < 1 || n > 3);

    printf("你输入的是 %d\n", n);
    return 0;
}

输错就再问一次,直到输入合法为止。用 while 也能写,但要先伪造一次输入,别扭得多。

一个细节:while (...); 后面的分号不能少,这是三种循环里唯一需要分号收尾的,漏了就编译报错。

4.3.1 到底差在哪:一个最小对照

#include <stdio.h>

int main(void)
{
    int i = 100;

    while (i < 10) {                 /* 条件一开始就不成立 */
        printf("while 执行了\n");
        i++;
    }

    do {
        printf("do-while 执行了\n");  /* 但它照样执行了一次 */
        i++;
    } while (i < 10);

    return 0;
}

输出:

do-while 执行了

while 执行了 0 次,do-while 执行了 1 次。就这一点区别。


4.4 break 与 continue

两个词长得像,作用完全不同,务必分清:

关键字 作用
break 立刻跳出整个循环,后面的圈数全都不做了(在导学案3的 switch 里已经见过它)
continue 跳过本次剩下的部分,直接进入下一圈

4.4.1 break:找到就收工

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 100; i++) {
        if (i % 7 == 0) {
            printf("找到了:%d\n", i);
            break;
        }
    }

    return 0;
}

输出:

找到了:7

没有 break 的话,它会把 7、14、21……98 全打印出来。"找到第一个就停"是 break 最常见的用途,导学案5在数组里查找元素时会再用到。

4.4.2 continue:跳过这一圈

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 20; i++) {
        if (i % 2 == 0) {
            continue;              /* 偶数:跳过本圈剩下的部分 */
        }
        printf("%d ", i);
    }
    printf("\n");

    return 0;
}

输出:

1 3 5 7 9 11 13 15 17 19

说实话,这个例子写成 if (i % 2 == 1) { printf("%d ", i); } 更简单。continue 真正的价值在于循环体很长、遇到某种情况就想提前放弃这一圈的时候,比如"这一批数据里有个坏值,跳过它别参与统计"。

4.4.3 两点边界

  1. 嵌套循环里,break 只跳出最内层那一层,外层会继续。
  2. C 语言还有个 goto,能跳到任意位置。业界共识是别用,它会让你自己写的代码三天后自己都看不懂。

4.5 嵌套循环

循环里面再套一个循环,叫嵌套循环。语法上没什么新东西,难的是脑子里要有画面。记住一句话:

外层走一步,内层走一整圈。

外圈 i 内圈 j 走过的值
i = 1 j = 1, 2, 3, ..., 9
i = 2 j = 1, 2, 3, ..., 9(重新从 1 开始)
i = 3 j = 1, 2, 3, ..., 9

4.5.1 demo 1:直角三角形

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 5; i++) {          /* i 控制第几行 */
        for (int j = 1; j <= i; j++) {      /* j 控制这一行打几个星号 */
            printf("*");
        }
        printf("\n");                       /* 一行结束,换行 */
    }

    return 0;
}

输出:

*
**
***
****
*****

图形化的输出最容易让人"突然开窍":外层管行,内层管列。

4.5.2 demo 2:九九乘法表

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            printf("%d*%d=%-3d", j, i, i * j);
        }
        printf("\n");
    }

    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
1*5=5  2*5=10 3*5=15 4*5=20 5*5=25
1*6=6  2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7  2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8  2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9  2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

%-3d 表示"占 3 位、左对齐",这样每列的宽度都一样,表格才整齐——正好回收导学案2里"\t 对不齐"的那条备注。

4.5.3 一个最朴素也最有效的调试技巧

嵌套循环绕不明白?把执行过程打印出来看:

/* 片段:把这段临时塞进循环体,看完再删掉 */
printf("i=%d j=%d\n", i, j);

你会看到 i 和 j 一步一步怎么变的,比在脑子里模拟可靠一百倍。这个方法你以后会一直用(专业一点的说法叫"打日志")。等真要上手断点调试,那是另一件事了,现在不用急。


4.6 综合练习

本节练习用于自练与自查,不强制完成、不需要提交;需要提交的是实验室统一布置的作业(作业一 / 作业二)。 参考代码统一放在本节末尾,做完之前不要翻。

练习 1|求和与平均值

读入一个正整数 n,输出 1 到 n 的和,以及它们的平均值(保留两位小数)。

练习 2|阶乘

读入 n,输出 n!(1 × 2 × 3 × ... × n)。

额外任务(重要):用 int 存结果,先算 12!,再算 13!,观察第二个结果有多荒唐,并用导学案2 2.5 的知识解释它。然后换成 long long(打印用 %lld)再试一次,看看能撑到多少。

这个实验想让你记住一件事:计算机的数是有范围的,而且超范围时它不会告诉你。 单片机上的变量比电脑上的还小气得多。

练习 3|猜数字(两个人玩)

第一位同学输入一个"谜底",第二位同学来猜,程序提示"大了"或"小了",最多猜 7 次。

提示:为什么要两个人?因为我们还没讲随机数(那要引入 <stdlib.h> 和函数调用,超出本轮范围)。用"人当随机数发生器"是最干净的办法。 结构上是 while + if / else if + break 的组合,正好把导学案3和本节串起来。7 次不是随便定的:1~100 用二分法猜,最多 7 次一定能猜中。

练习 4|水仙花数

找出 100 ~ 999 之间所有的"水仙花数":一个三位数,各位数字的立方和等于它本身。例如 153 = 1³ + 5³ + 3³。

提示:怎么把 n 的百位、十位、个位拆出来?这是导学案2 / 和 % 的综合运用: 百位 n / 100,十位 n / 10 % 10,个位 n % 10。 立方写 a * a * a,不要写 a ^ 3(^ 不是乘方)。

练习 5|倒三角形

读入行数 n,打印一个倒直角三角形。例如 n = 4:

****
***
**
*

选做:改成打印菱形(上半部分星星递增,下半部分递减)。这题练的是"行号 i 和星星个数 j 之间的关系",想清楚 j <= i 该换成什么。

练习 6|找错题

下面这段代码有 4 处错误,先圈出来再改对。(放心,它能运行完,不会卡死。)

/* 反面示例:有 4 处错误 */
#include <stdio.h>

int main(void)
{
    int n = 0;
    int sum;                        /* 错误 1 */
    int i = 0;

    printf("请输入 n:");
    scanf("%d", &n);

    for (i = 1; i <= n; i++);       /* 错误 2 */
    {
        sum += i;
    }

    if (sum = 0) {                  /* 错误 3 */
        printf("和为 0\n");
    }

    printf("平均值 = %d\n", sum / n);   /* 错误 4 */

    return 0;
}

练习 7(选做)|100 以内的素数

打印 100 以内所有素数。

提示:需要一个新的思路——标志变量(flag):先假设它是素数(isPrime = 1),一旦找到因子就把它改成 0,并且 break。 内层循环只需要试到 i * i <= n 就够了,想想为什么(如果 n = a * b,那 a 和 b 里必有一个不超过 √n)。 这个 flag 的写法,导学案5在数组里查找元素时会原样用到。


4.7 本节小结与自查

学完本节,你应该能做到:

  • 说出循环的三要素,并能在自己写完循环后逐条对照检查
  • 用 while 和 for 写同一个逻辑,说清两者的执行顺序和适用场景
  • 解释 do-while 与 while 的唯一区别,并知道它适合什么场景
  • 说清 break 和 continue 的区别,知道嵌套循环里 break 只跳出一层
  • 读懂两层嵌套循环,能说出"外层走一步,内层走一整圈"
  • 亲手制造过一次死循环,并且知道怎么把它停下来
  • 卡住时知道用最朴素的办法调试:把 i、j 打印出来看

下一节:导学案5 · 数组。终于可以让程序记住一串数据了——这是你在 STM32 上处理传感器数据的第一步。

4.8 练习参考代码(做完再看)

练习 1 参考代码

#include <stdio.h>

int main(void)
{
    int n = 0;
    int sum = 0;

    printf("请输入 n:");
    scanf("%d", &n);

    if (n <= 0) {
        printf("n 必须是正整数\n");
    } else {
        for (int i = 1; i <= n; i++) {
            sum += i;
        }

        printf("1 到 %d 的和 = %d\n", n, sum);
        printf("平均值 = %.2f\n", (double)sum / n);
    }

    return 0;
}

练习 2 参考代码

#include <stdio.h>

int main(void)
{
    int n = 0;
    long long fact = 1;        /* 累乘器,从 1 开始;用 long long 能撑到 20! */

    printf("请输入 n:");
    scanf("%d", &n);

    if (n < 0) {
        printf("负数没有阶乘\n");
    } else {
        for (int i = 1; i <= n; i++) {
            fact *= i;
        }
        printf("%d! = %lld\n", n, fact);
    }

    return 0;
}

练习 3 参考代码

#include <stdio.h>

int main(void)
{
    int answer = 0;
    int guess = -1;
    int count = 0;

    printf("请第一位同学输入谜底(1~100,别人别偷看):");
    scanf("%d", &answer);

    while (count < 7) {
        printf("还剩 %d 次,请猜:", 7 - count);
        scanf("%d", &guess);
        count++;

        if (guess == answer) {
            printf("猜对了!一共猜了 %d 次\n", count);
            break;
        } else if (guess > answer) {
            printf("大了\n");
        } else {
            printf("小了\n");
        }
    }

    if (guess != answer) {
        printf("次数用完了,谜底是 %d\n", answer);
    }

    return 0;
}

练习 4 参考代码

#include <stdio.h>

int main(void)
{
    for (int n = 100; n <= 999; n++) {
        int a = n / 100;           /* 百位 */
        int b = n / 10 % 10;       /* 十位 */
        int c = n % 10;            /* 个位 */

        if (a * a * a + b * b * b + c * c * c == n) {
            printf("%d\n", n);
        }
    }

    return 0;
}

输出(一共四个):

153
370
371
407

练习 5 参考代码

#include <stdio.h>

int main(void)
{
    int n = 0;

    printf("请输入行数:");
    scanf("%d", &n);

    for (int i = n; i >= 1; i--) {
        for (int j = 1; j <= i; j++) {
            printf("*");
        }
        printf("\n");
    }

    return 0;
}

练习 6 参考答案

# 错在哪 怎么改
1 sum 没初始化,里面是垃圾值 int sum = 0;
2 for 后面多了个分号,循环体是空的;花括号里的 sum += i 只执行了一次 删掉那个分号
3 = 是赋值,把 sum 改成了 0,还让条件恒为假 if (sum == 0)
4 整数除法,平均值的小数部分被砍掉了 printf("平均值 = %.2f\n", (double)sum / n);

改完之后请输入 n = 4 自测:和应该是 10,平均值应该是 2.50。

练习 7 参考代码

#include <stdio.h>

int main(void)
{
    for (int n = 2; n <= 100; n++) {
        int isPrime = 1;                   /* 1 = 先假设是素数 */

        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) {
                isPrime = 0;               /* 找到因子,推翻假设 */
                break;
            }
        }

        if (isPrime) {
            printf("%d ", n);
        }
    }
    printf("\n");

    return 0;
}

循环是程序第一次拥有"耐心"。它不会累,不会烦,也不会因为你让它做一万遍就抗议。

但请注意:它同样不会替你思考。 圈数差一个、条件写反一个符号,它就老老实实地错一万遍。所以请把"三要素检查"变成肌肉记忆。

作者:Mike-lin300

最后更新:2026-09-24

更新说明:无