C控制语句:循环
掌握 for/while/do-while 三种循环结构、嵌套循环与哨兵值循环模式,能编写计数循环和条件驱动循环程序
学习目标
- 能区分
for、while、do-while三种循环的语法与执行顺序,并选出适合计数循环、哨兵输入、至少执行一次菜单的场景 - 能改出嵌套循环打印乘法表或遍历 3×3 矩阵的效果,并解释外循环与内循环各自控制什么
- 能用哨兵值模式编写「读入数据直到遇到结束标记」的程序,并回答:如果把
while换成do-while且哨兵是第一个读入的值,循环体会多执行几次?
机制总览
C控制语句:循环:机制路径
- 1
为什么程序需要「反复执行」?
想象一条巧克力工厂的流水线:同一道工序要对传送带上的每一块巧克力重复操作——称重、包膜、贴标签。工人不会只做一块就下班,而是「只要还有货,就继续干」。程序里的很多任务也一样:打印 1 到 100、累加用户输入的分数、遍历矩阵每个格子——都需要把同一段代码跑很多遍。
- 2
while 回顾:先判后做的入口条件循环
上一章你已经见过 while 循环 。它的规则很简单: 先问「还要不要继续?」,答「要」才干活。
- 3
for 循环:把三要素写在一行
猜一猜:下面 for 循环结束后, i 的值是多少?通过 Stepper 一步步揭晓。
章级决策实验
C控制语句:循环:机制与证据
切换《C控制语句:循环》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么程序需要「反复执行」?
想象一条巧克力工厂的流水线:同一道工序要对传送带上的每一块巧克力重复操作——称重、包膜、贴标签。工人不会只做一块就下班,而是「只要还有货,就继续干」。程序里的很多任务也一样:打印 1 到 100、累加用户输入的分数、遍历矩阵每个格子——都需要把同一段代码跑很多遍。
可核验证据
用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「为什么程序需要「反复执行」?」的实际行为。
学完《C控制语句:循环》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
C控制语句:循环:失效与核验
为什么程序需要「反复执行」?
典型失效
若只记语法而忽略「为什么程序需要「反复执行」?」的类型范围、求值顺序或资源边界,输入变化后可能出现未定义行为或错误结果。
核验证据
用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「为什么程序需要「反复执行」?」的实际行为。
while 回顾:先判后做的入口条件循环
典型失效
若只记语法而忽略「while 回顾:先判后做的入口条件循环」的类型范围、求值顺序或资源边界,输入变化后可能出现未定义行为或错误结果。
核验证据
用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「while 回顾:先判后做的入口条件循环」的实际行为。
for 循环:把三要素写在一行
典型失效
若只记语法而忽略「for 循环:把三要素写在一行」的类型范围、求值顺序或资源边界,输入变化后可能出现未定义行为或错误结果。
核验证据
用 -Wall -Wextra -pedantic 构建本节示例,再以正常值、边界值和失败输入核对「for 循环:把三要素写在一行」的实际行为。
为什么程序需要「反复执行」?
想象一条巧克力工厂的流水线:同一道工序要对传送带上的每一块巧克力重复操作——称重、包膜、贴标签。工人不会只做一块就下班,而是「只要还有货,就继续干」。程序里的很多任务也一样:打印 1 到 100、累加用户输入的分数、遍历矩阵每个格子——都需要把同一段代码跑很多遍。
如果没有循环呢? 你得把 printf 复制粘贴一百遍,改一百次数字。用户输入个数不确定时,你甚至不知道要粘贴多少遍。循环就是 C 语言给你的「流水线开关」:写一次逻辑,让机器按条件反复执行,直到该停的时候停。
这一章教你三种循环开关——for、while、do-while——以及两种经典用法:数着跑(计数循环)和看着停(哨兵值循环)。
while 回顾:先判后做的入口条件循环
上一章你已经见过 ↡先检查条件、条件为真才执行循环体、条件为假则跳过的入口条件循环。循环体可能一次都不执行。。它的规则很简单:先问「还要不要继续?」,答「要」才干活。
当输入数据的个数事先不知道时,常用 ↡用一个特殊值(哨兵)标记数据结束;循环每次先读入数据,再判断是否为哨兵。不是哨兵就处理并继续读入,是哨兵则退出。典型哨兵如 -1、0、空行。 驱动循环——你不需要提前知道有多少个数字,只要约定「读到 -1 就表示结束」:
#include <stdio.h>
int main(void) {
int item;
int sum = 0;
printf("输入整数(-1 结束):\n");
if (scanf("%d", &item) != 1) return 1;
while (item != -1) { // -1 是哨兵
sum += item;
if (scanf("%d", &item) != 1) return 1;
}
printf("总和 = %d\n", sum);
return 0;
}这种模式叫「先读入、再判断、再处理、再读入」——第一个数在循环外读,循环内每次处理完再读下一个。也有人把读入放在循环体开头,逻辑等价,只是写法不同。
for 循环:把三要素写在一行
↡C 语言的计数循环结构:for(初始化; 条件; 更新) { 循环体 }。初始化只执行一次,然后重复 条件判断→循环体→更新→回到条件,直到条件为假。 把循环最需要关心的三件事——从哪开始、什么时候停、每轮怎么推进——压缩进括号里:
for (初始化; 条件; 更新) {
// 循环体
}三要素各司其职:
- 初始化(init):设置 ↡在循环中用来计数或推动循环向终止条件靠近的变量,如 i、n、row。必须在循环体内被更新,否则可能死循环。,只执行一次
- 条件(cond):每轮开始前检查,为真才进入循环体
- 更新(update):每轮循环体结束后执行,通常用
i++推动控制变量
猜一猜:下面
for循环结束后,i的值是多少?通过 Stepper 一步步揭晓。
第一步:初始化(只执行一次)
int i = 0 在循环开始前执行一次——把计数器归零。这一步不属于「重复」的部分,for 的语法把它写在括号最左边,强调「循环从什么状态出发」。
典型计数循环——打印 1 到 10 的平方:
#include <stdio.h>
int main(void) {
int i;
for (i = 1; i <= 10; i++) {
printf("%d^2 = %d\n", i, i * i);
}
return 0;
}i = 1 起步,i <= 10 控制最后一轮是 10,i++ 每轮加 1。三个要素写在同一行,一眼看清循环的「起止步长」——这就是 for 比 while 更适合次数已知场景的原因。
逗号运算符在 for 里很常见——同时初始化或更新两个变量:
for (i = 0, j = 10; i < j; i++, j--) {
printf("i=%d j=%d\n", i, j);
}do-while:至少执行一次的出口条件循环
↡先执行循环体、再检查条件的出口条件循环:do { 循环体 } while (条件); 分号不能少。保证循环体至少运行一次,适合菜单、输入校验等必须先做一次的场景。 和 while 的最大区别:先干活,再问要不要继续。
语法注意末尾的分号——while (条件); 这里的分号是 do-while 语法的一部分,不能省:
do {
// 循环体——至少执行一次
} while (条件); // 分号必须有!菜单程序是经典用例——总得先显示选项,再让用户选:
#include <stdio.h>
int main(void) {
int choice;
do {
printf("1. 新建 2. 打开 0. 退出\n");
printf("请选择:");
scanf("%d", &choice);
/* 根据 choice 处理 ... */
} while (choice != 0);
return 0;
}即使用户第一次就输入 0,菜单也会显示一次——这就是 do-while 和 while 的分水岭。
嵌套循环:外圈换行、内圈扫列
↡在一个循环体内包含另一个循环。若每轮内层次数固定,总次数是外层与内层次数乘积;若内层边界依赖外层,则应对各轮次数求和。常用于二维数据和组合枚举。 就是“循环里套循环”。矩形遍历常由外层控制行、内层控制列,三角形边界则让每行迭代次数不同:
打印九九乘法表——外循环控制被乘数、内循环控制乘数:
#include <stdio.h>
int main(void) {
int i, j;
for (i = 1; i <= 9; i++) {
for (j = 1; j <= i; j++) {
printf("%d×%d=%2d ", j, i, i * j);
}
printf("\n"); // 每行结束换行——外循环的职责
}
return 0;
}内循环的上界写成 j <= i 而不是固定的 9,所以每行只打印到对角线——三角形乘法表。如果把内循环改成 j <= 9,就是完整的 9×9 方阵。
代码详解:四种循环场景逐段拆解
场景一:for 计数循环——累加 1 到 N
#include <stdio.h>
int main(void) {
int n, sum = 0;
printf("N = ");
if (scanf("%d", &n) != 1 || n < 0 || n > 65535) return 1;
for (int i = 1; i <= n; i++) {
sum += i;
}
printf("1+...+%d = %d\n", n, sum);
return 0;
}C99 起可以在 for 的初始化里声明变量(int i)——i 的作用域只在 for 循环内,出了循环就不能再用 i。sum += i 每轮把当前 i 累加进去,循环结束后 sum 就是等差数列求和。
场景二:while 哨兵值——统计正数个数
#include <stdio.h>
int main(void) {
int x, count = 0;
printf("输入整数(0 结束):\n");
if (scanf("%d", &x) != 1) return 1;
while (x != 0) {
if (x > 0)
count++;
if (scanf("%d", &x) != 1) return 1;
}
printf("正数个数:%d\n", count);
return 0;
}哨兵这里是 0。循环条件 x != 0 和「0 表示结束」的约定必须一致——换哨兵值时,判断条件也要一起改。
场景三:do-while 输入校验——至少问一次
#include <stdio.h>
int main(void) {
int age;
do {
printf("请输入年龄(1-120):");
if (scanf("%d", &age) != 1) return 1;
} while (age < 1 || age > 120);
printf("年龄 %d 已记录\n", age);
return 0;
}如果用户第一次就输入合法年龄,循环也只跑一轮就退出。如果输入非法,会反复提示——用 while 也能写,但得把提示和读入拆到循环外,结构不如 do-while 直观。
场景四:嵌套循环——遍历 3×3 矩阵求和
#include <stdio.h>
int main(void) {
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int row, col, total = 0;
for (row = 0; row < 3; row++) {
for (col = 0; col < 3; col++) {
total += matrix[row][col];
}
}
printf("矩阵元素总和 = %d\n", total); // 45
return 0;
}外循环 row 从 0 到 2,内循环 col 从 0 到 2——每个 (row, col) 恰好访问一次,共 9 次,与 NestedLoopDiagram 里的访问顺序一致。
容易踩的坑
小结
- 三种循环:while(先判后做,可能零次)、for(三要素计数,次数已知时最清晰)、do-while(先做后判,至少一次)
- 哨兵值模式适合输入个数未知的场景:约定结束标记,先读入再判断
- 嵌套循环的总次数按每轮内层次数求和;只有固定矩形边界才能直接相乘
- for 的 init/cond/update 分工明确;退出时控制变量往往比最后一轮循环体的值大 1(当条件是
<时) - 铁律:循环关键字后不加多余分号;循环体永远写花括号;控制变量必须有明确的更新路径
练习
问题 1(改代码型) 下面程序想打印 1 到 5,但有两处错误。找出并修正。
#include <stdio.h>
int main(void) {
int i;
for (i = 1; i < 5; i++);
printf("%d ", i);
return 0;
}问题 2(问答型) 读程序,写出输出。解释 do-while 为什么至少执行一次。
#include <stdio.h>
int main(void) {
int n = 10;
do {
printf("%d ", n);
n++;
} while (n < 10);
return 0;
}问题 3(综合实现型) 用嵌套 for 循环打印如下 4 行图案(每行字符数递增):
*
**
***
****
要求:外循环控制行数(1 到 4),内循环控制每行星号个数。不能使用字符串常量数组。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- while 循环
先检查条件、条件为真才执行循环体的入口条件循环。条件一开始就不成立时,循环体一次都不执行。适合「可能零次」的重复任务。详见本章「while 回顾」一节和 WhileLoopFlowDiagram。
- 哨兵值(sentinel value)
标记数据结束的特殊值(如 -1、0)。循环每次先读入数据,再判断是否为哨兵——不是就处理并继续读入,是就退出。适合事先不知道输入个数的场景。详见 SentinelLoopDiagram 和「while 回顾」一节。
- for 循环
把初始化、条件、更新写进括号的计数循环:
for(init; cond; update) { ... }。初始化只执行一次,然后重复「判断→循环体→更新」。次数已知时比 while 更紧凑。详见 ForLoopFlowDiagram 和 Stepper 分步图示。- 循环控制变量(loop control variable)
推动循环向终止条件靠近的变量(如 i、n)。必须在每轮循环中被更新(通常在 for 的 update 段或循环体末尾),否则条件永远不变就会死循环。忘了更新是最常见的死循环原因。
- do-while 循环
先执行循环体、再检查条件的出口条件循环。语法末尾的分号不能少。保证循环体至少运行一次——适合菜单显示、输入校验等必须先做一轮的场景。详见 DoWhileVsWhileDiagram。
- 嵌套循环(nested loop)
循环体内再包含循环。固定矩形边界的总次数可相乘;内层边界依赖外层时,应把每轮内层次数相加。
原版目录概念补充核对
以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。
关系运算符:机制、边界与证据
在《C控制语句:循环》的官方单元 cpr-06 中,关系运算符连接本章第 2 组知识约束。学习时要同时说明它接受什么输入、改变什么状态、在何种边界失效;再以本章示例的编译诊断、固定输入输出或失败用例复核结论,不能只记术语名称。
函数返回值:机制、边界与证据
在《C控制语句:循环》的官方单元 cpr-06 中,函数返回值连接本章第 6 组知识约束。学习时要同时说明它接受什么输入、改变什么状态、在何种边界失效;再以本章示例的编译诊断、固定输入输出或失败用例复核结论,不能只记术语名称。