导学案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 (...) 的括号里 |
编译报错 |
| ③ 更新 | 循环体内改变量 | 死循环,永远出不来 |
写完一个循环,请自己对照这张表检查一遍。这是本节最实用的一条建议。
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;
}
输出:
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;
}
输出:
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编译器比较老,可能不支持,会报:解决办法:工具 → 编译选项 → "编译器"选项卡 → 勾选"编译时加入以下命令" → 填
-std=c99→ 确定。(也可以在"代码生成/优化"选项卡里,把"语言标准"改成 ISO C99。)如果你不想改设置,把变量声明在 for 外面也完全一样:
int i; for (i = 0; ...)。VS 用户不需要做这件事。
4.2.1 骨架:把 while 的三要素搬到一行¶
同一段逻辑,两种写法完全等价,请并排对照着看:
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;
}
输出:
4.2.4 for 还是 while?¶
一句话结论:次数已知用 for,条件驱动用 while。
- "做 100 次" →
for - "直到用户输入正确为止" →
while
4.3 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;
}
输出:
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;
}
输出:
没有 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;
}
输出:
说实话,这个例子写成
if (i % 2 == 1) { printf("%d ", i); }更简单。continue真正的价值在于循环体很长、遇到某种情况就想提前放弃这一圈的时候,比如"这一批数据里有个坏值,跳过它别参与统计"。
4.4.3 两点边界¶
- 嵌套循环里,
break只跳出最内层那一层,外层会继续。 - 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 一个最朴素也最有效的调试技巧¶
嵌套循环绕不明白?把执行过程打印出来看:
你会看到 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;
}
输出(一共四个):
练习 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
更新说明:无