为什么需要循环: 三种循环语句
while 语句与复合循环体
do-while 与输入校验
案例: 平方表、数列求和、整数的位数
break、continue 与空语句陷阱
课堂小测: 概念 + 编码
有些工作就是"同一件事做很多遍":
打印 1 到 100——手写 100 行 printf?
输入 100 个成绩求平均——声明 100 个变量?
之前讲过的"找最高分"——求用户输入数据的最大值, 当时只能处理固定几个数
计算机最擅长不厌其烦地重复——循环就是把这个能力交给你的语法
C 有三种循环语句 (控制表达式的玩法下面细讲):
while: 先判断, 再执行
do-while: 先执行, 再判断
for: 最适合计数循环
while 是最简单的循环语句, 形式为 while (表达式) 语句
表达式是控制表达式(条件表达式); 语句是循环体, 可为单条语句或 {} 复合语句
例: 找 ≥ n 的最小 2 的幂:
#include <stdio.h>
int main(void) {
int n, i = 1;
scanf("%d", &n);
while (i < n) /* 控制表达式 */
i = i * 2; /* 循环体 */
printf("%d\n", i);
return 0;
}
n = 10 时, while 的执行过程:
i = 1; i 现在是 1
i < n? 是, 继续
i = i * 2; i 现在是 2
i < n? 是, 继续
i = i * 2; i 现在是 4
i < n? 是, 继续
i = i * 2; i 现在是 8
i < n? 是, 继续
i = i * 2; i 现在是 16
i < n? 否, 退出循环
执行过程: 先计算控制表达式; 非零 (true) 就执行循环体并再次测试, 持续到控制表达式变为零才停止
while (i > 0) { /* 循环体: 两条语句 */
printf("%d\n", i);
i--;
}
while (i < n) { /* 循环体只有一条语句 */
i = i * 2;
}
i = 10;
while (i > 0) {
printf("%d\n", i);
i--;
}
printf("发射!\n");
循环体必须是一条语句
要执行多条语句, 就用大括号包成一条复合语句:
即使只有一条语句, 建议也用大括号——以后加语句不用补:
看一个火箭发射倒计时的例子: 打印 10、9、…、1, 最后喊出"发射!"
最后一行输出是 发射!
循环结束时, 控制表达式一定为假——倒计时循环结束, 说明 i ≤ 0 了
循环体可能一次都不执行——条件在循环体之前先判定
倒计时还有更简洁的写法 (i-- 直接在 printf 里用掉):
while (i > 0) {
printf("%d\n", i--);
}
控制表达式恒为非零就是无限循环: while (1) { … } 会永远执行, 除非循环体里有 break、goto、return, 或调用终止程序的函数
现场演示: 打印平方表——用户指定行数, 每行输出"数字 + 它的平方"
⚡ 打开 skeleton.c样例 (输入 5):
请输入行数: 5
1 1
2 4
3 9
4 16
5 25
提示: 两列右对齐——之前的 %10d 字段宽度还记得吗?
/* 打印平方表 */
#include <stdio.h>
int main(void)
{
int i, n;
printf("请输入行数: ");
scanf("%d", &n);
i = 1;
while (i <= n) {
printf("%10d%10d\n", i, i * i);
i++;
}
return 0;
}
i 从 1 到 n, 循环条件 i <= n——计数循环用 while 的最常见写法
循环体两条语句: 打印一行 + 计数递增——所以用大括号
%10d: 占 10 格右对齐, 两列对齐成表
现场演示: 对用户输入的整数数列求和——用户不断输入整数, 直到输入 0 为止, 输出总和
样例:
输入整数 (0 结束): 8 23 71 5 0
总和是: 107
提示: 0 是哨兵值——本身不参与求和, 只用来表示"输完了"
/* 对数列求和 */
#include <stdio.h>
int main(void)
{
int n, sum = 0;
printf("输入整数 (0 结束): ");
scanf("%d", &n);
while (n != 0) {
sum += n;
scanf("%d", &n);
}
printf("总和是: %d\n", sum);
return 0;
}
先读一个数, 不是 0 就累加、再读下一个——读一个, 判一个, 加一个
sum 初值必须为 0 (累加的起点)
哨兵值模式: 用特殊值标记"输入结束", 循环条件就是 n != 0
任务: 程序心里想好一个 1~100 的整数 (骨架里已设为 42), 你反复输入猜测, 程序提示"太大"或"太小", 直到猜中; 最后输出猜了几次
样例:
猜一个 1~100 的整数: 50
太大了
猜一个 1~100 的整数: 25
太小了
猜一个 1~100 的整数: 42
猜对了! 你猜了 3 次
提示:
先读一次, 再进入 while——循环条件就是"没猜中"
每轮: 比较并提示 → 计数 → 再读下一个; 猜中那次不在循环体里, 计数别漏
/* guess.c: 猜数字循环版 */
#include <stdio.h>
int main(void) {
int secret = 42, guess, count = 0;
printf("猜一个 1~100 的整数: ");
scanf("%d", &guess);
while (guess != secret) {
if (guess > secret) printf("太大了\n");
else printf("太小了\n");
count++;
printf("猜一个 1~100 的整数: ");
scanf("%d", &guess);
}
count++;
printf("猜对了! 你猜了 %d 次\n", count);
return 0;
}
循环条件 guess != secret: 猜中就退出——沿用"先读一次, 再判断"的哨兵模式
循环体末尾再读一次, 下一轮判断的才是新输入; 忘了这句就是死循环
计数: 循环体里的 count++ 只数"猜错"的次数, 猜中那次在循环外, 所以结束后再 count++
也可以用 while (1) + 猜中时 break——下一节的 break 会让这种写法更直接
一般形式: do 语句 while (表达式);
最典型的用途: 输入校验——必须先读一次, 才知道合不合法:
#include <stdio.h>
int main(void) {
int n;
do {
printf("请输入一个正整数: ");
scanf("%d", &n);
} while (n <= 0);
printf("你输入了 %d\n", n);
return 0;
}
执行顺序: 先执行循环体 → 再算控制表达式 → 非零再来一轮
与 while 的唯一区别: 循环体至少执行一次——0 也是一位数, 至少拆一次位
"先读后判"的场景 while 写起来别扭 (得先预读一次), do-while 刚刚好——对比数列求和的预读写法
建议给所有 do 语句都加大括号——没有大括号的 do 容易被误读成 while 的开头:
do
printf("%d\n", i--);
while (i > 0); /* 这个 while 不是新循环 */
任务: 输入一个非负整数, 输出它各位数字之和; 0 也要能正确处理
样例:
输入一个非负整数: 12345
各位数字之和 = 15
输入一个非负整数: 0
各位数字之和 = 0
提示:
拆位老办法: % 10 取末位, / 10 去掉末位
至少要做一次——do-while 正合适; 想想输入 0 时 while 版会怎样
/* digitsum.c: 各位数字之和 */
#include <stdio.h>
int main(void) {
int n, sum = 0;
printf("输入一个非负整数: ");
scanf("%d", &n);
do {
sum += n % 10;
n /= 10;
} while (n > 0);
printf("各位数字之和 = %d\n", sum);
return 0;
}
n % 10 取末位、n /= 10 缩位, 循环到 n 变 0——和"计算整数的位数"是同一套拆位
用 do-while: 0 也是一位数, 循环体至少执行一次; 换成 while (n > 0), 输入 0 时一次都不进 (本题 sum 初值 0 碰巧对, 换成统计位数就错了)
循环结束后 n 已经被改成 0; 还要用原数的话, 先复制一份 (下一节"位数"的例子就是这么做的)
sum 是累加器, 初值必须为 0——和数列求和的 sum 一样
现场演示: 输入一个非负整数, 输出它是几位数
⚡ 打开 skeleton.c样例:
输入一个非负整数: 60
它有 2 位数字
提示: 每除以 10 一次, 位数就多 1——直到除成 0。想想为什么这里用 do-while 比 while 更合适?
/* 计算整数的位数 */
#include <stdio.h>
int main(void)
{
int digits = 0, n;
printf("输入一个非负整数: ");
scanf("%d", &n);
do {
n /= 10;
digits++;
} while (n > 0);
printf("它有 %d 位数字\n", digits);
return 0;
}
思路: 反复除以 10 直到变 0, 除的次数就是位数 (60 → 6 → 0, 两次 → 2 位)
为什么用 do-while: 每个整数至少有一位数字——输入 0 时 while 版会输出 0 位 (错的!), do-while 先除一次正好
digits 初值 0, 每轮 +1——计数循环的惯用写法
break 把控制从 switch 中转出, 也可跳出循环。
检查 n 是否为素数的循环, 找到因子后立即 break:
#include <stdio.h>
int main(void) {
int n = 13, d = 2;
while (d < n) {
if (n % d == 0) {
break;
}
d++;
}
if (d < n) {
printf("%d 能被 %d 整除\n", n, d);
} else {
printf("%d 是素数\n", n);
}
return 0;
}
循环终止后, 用 if 判断: 提前终止 → n 不是素数; 正常终止 → n 是素数。
break 适合退出点位于循环体中间的场景 (读取输入并在特定值时终止):
#include <stdio.h>
int main(void) {
int n;
while (1) {
printf("输入一个数 (0 结束): ");
scanf("%d", &n);
if (n == 0) {
break;
}
printf("%d 的立方是 %d\n", n, n * n * n);
}
return 0;
}
break 只跳出最内层的一层 while/do/for/switch
switch 里的 break 只结束 switch 本身; 要跳出外层 while, 需在外层另写 break
任务: 读入 10 个整数, 计算 100/i 的和——i 为 0 时除零会崩溃, 必须跳过
用 continue 的版本:
n = 0;
sum = 0;
while (n < 10) {
scanf("%d", &i);
n++; /* 先计数: continue 不能跳过它 */
if (i == 0) {
continue; /* 跳过除零 */
}
sum += 100 / i;
}
不用 continue 的版本:
n = 0;
sum = 0;
while (n < 10) {
scanf("%d", &i);
n++;
if (i != 0) {
sum += 100 / i;
}
}
break 跳出循环; continue 跳到本轮循环体末尾, 直接进入下一轮 (循环照常继续)
效果: 读到 0 必须跳过——否则 100 / i 除零崩溃; continue 的必要性不依赖题目规定, 而是程序的生死问题
另一个区别: break 可用于 switch 和循环; continue 只能用于循环
空语句: 只有一个分号 ;, 什么都不做
意外写出的空语句是经典 bug——多打的一个分号会让循环体/if 体变成"空":
while (i < n); /* BUG: 循环体是空语句 → 死循环! */
sum += i++; /* 这行其实只执行一次 */
if (d == 0); /* BUG: if 的体是空语句 → 条件成立时什么都不做 */
printf("zero");
编译器通常会警告 (empty body in a while), 但不要依赖警告
好习惯: 循环体和 if 后面永远加大括号, 哪怕只有一条语句
0 为止, 输出其中最大值