循环结构
上一篇我们学会了让程序“做选择”。但现实生活中,很多任务需要反复执行:计算 1 到 100 的和、打印九九乘法表、遍历数组里的每个元素……这些靠分支结构还不够,我们需要循环结构。
学习目标
读完这篇文章,你会掌握:
while、do-while、for三种循环的语法- 三种循环各自适合什么场景
- 循环变量、死循环、嵌套循环的概念
- 如何用循环遍历数组
为什么需要循环
假设你要打印 1 到 5。不用循环的话,代码可能是这样的:
printf("1\n");
printf("2\n");
printf("3\n");
printf("4\n");
printf("5\n");
那如果要打印 1 到 1000 呢?复制粘贴 1000 行显然不现实。循环的作用就是:把一段代码重复执行多次,而只需要写一次。
while 循环
while 循环的语法是:
while (条件) {
// 循环体
}
只要条件为真,循环体就会一直执行。
#include <stdio.h>
int main() {
int i = 1;
while (i <= 5) {
printf("%d\n", i);
i++; // 别忘了更新循环变量,否则会变成死循环
}
return 0;
}
输出:
1
2
3
4
5
while 循环适合那种“不确定要循环多少次,但知道什么时候该停”的场景。比如用户输入一个数,直到输入正确为止。
do-while 循环
do-while 和 while 很像,但它会先执行一次循环体,然后再判断条件。
do {
// 循环体
} while (条件);
#include <stdio.h>
int main() {
int i = 1;
do {
printf("%d\n", i);
i++;
} while (i <= 5);
return 0;
}
do-while 的关键特点是:至少执行一次。当你需要先让用户输入,再判断输入是否合法时,它非常有用。
#include <stdio.h>
int main() {
int n;
do {
printf("请输入一个正数:");
scanf("%d", &n);
} while (n <= 0);
printf("你输入的是:%d\n", n);
return 0;
}
提示
do-while 的 while 后面有一个分号,不要忘了写。这是很多初学者会漏掉的地方。
for 循环
for 循环通常用于“知道要循环多少次”的场景。它的语法是:
for (初始化; 条件; 更新) {
// 循环体
}
#include <stdio.h>
int main() {
for (int i = 1; i <= 5; i++) {
printf("%d\n", i);
}
return 0;
}
执行顺序是:
- 先执行初始化:
int i = 1 - 判断条件:
i <= 5,如果为真就执行循环体 - 执行完循环体后,执行更新:
i++ - 回到第 2 步,直到条件为假
for 循环把“初始化、条件、更新”三件事都放在了一行,逻辑非常紧凑,适合计数型循环。
for 循环的变量声明
上面 for (int i = 1; i <= 5; i++) 这种写法是 C99 标准才开始支持的。如果你使用较老的编译器或嵌入式开发环境,可能需要在 for 外面声明循环变量:
int i;
for (i = 1; i <= 5; i++) { ... }
不过现在绝大多数编译器都支持 C99 及更高版本,直接写在 for 里更简洁、更能限制变量的作用域。
三种循环怎么选
| 循环类型 | 适合场景 |
|---|---|
while | 不确定次数,先判断后执行 |
do-while | 至少执行一次,先执行后判断 |
for | 确定次数,计数型循环 |
这只是经验法则,实际写代码时三种循环很多时候可以互换。选择让你代码最清晰的那种即可。
循环变量
在循环里控制次数的变量叫循环变量,通常用 i、j、k 来命名。
for (int i = 0; i < 10; i++) {
...
}
提示
关于 i 从 0 还是从 1 开始,没有绝对的对错。遍历数组时通常从 0 开始,因为数组下标从 0 起。如果只是为了打印 1 到 10,从 1 开始更直观。
死循环
如果循环条件永远为真,循环就永远不会结束,这叫死循环。
while (1) {
printf("停不下来了\n");
}
有时候我们会故意写死循环,比如服务器的主循环、等待用户输入的循环。但初学阶段,绝大多数死循环都是 bug。写循环时一定要确保条件最终会变为假。
嵌套循环
一个循环里面还可以再写循环,这就是嵌套循环。经典的例子是打印九九乘法表:
#include <stdio.h>
int main() {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%d*%d=%-2d ", j, i, i * j);
}
printf("\n");
}
return 0;
}
外层循环控制行,内层循环控制列。嵌套循环的执行次数是外层次数乘以内层次数,所以层数不宜过多,否则性能会下降。
循环与数组遍历
循环最常做的事情之一就是遍历数组。
#include <stdio.h>
int main() {
int scores[] = {85, 90, 78, 92, 88};
int n = sizeof(scores) / sizeof(scores[0]);
for (int i = 0; i < n; i++) {
printf("第 %d 个成绩是:%d\n", i + 1, scores[i]);
}
return 0;
}
sizeof(scores) / sizeof(scores[0]) 是计算数组元素个数的常用写法。整个数组的大小除以单个元素的大小,就是元素个数。
提示
关于数组,我们会在后面的文章里详细讲。这里你只需要知道:数组是一堆相同类型数据排在一起,用下标 scores[i] 可以访问第 i 个元素。
常见错误与注意事项
- 循环条件写错,导致死循环
for (int i = 1; i <= 5; i--) { // i 越来越小,永远 <= 5
...
}
- 数组越界
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; i++) { // i = 5 时越界
printf("%d\n", arr[i]);
}
for循环三个部分用逗号还是分号
for (int i = 0, i < 10, i++) { // 错误!应该用分号
先写清循环不变量
循环不变量是在每次迭代开始时都成立的事实。例如求前 n 项之和:
long long sum = 0;
for (size_t i = 0; i < n; i++) {
// 循环开始时,sum 等于 data[0..i) 的和
sum += data[i];
}
用半开区间 [0, i) 描述已经处理的部分,可以同时解释初始化、继续条件和结束后的结果。
边界错误的三个问题
写循环时逐项回答:
- 第一个合法下标是什么?
- 第一个不合法下标是什么?
- 循环结束时变量处于什么值?
长度为 n 的数组通常写 i < n,而不是 i <= n。
无符号倒序循环要小心
for (size_t i = n - 1; i >= 0; i--) { // 错误:i 永远不会小于 0
}
size_t 是无符号类型。可以写:
for (size_t i = n; i-- > 0; ) {
use(i);
}
或在逻辑允许时使用正向循环。任何 n - 1 都要先考虑 n == 0。
循环体里避免悄悄改变控制变量
除 for 的更新表达式外又修改 i,会让边界难以推理。确实需要跳步时,把步长明确写在循环结构或单独注释中。
小结
这篇文章我们学习了 C 语言里的三种循环:
while:先判断,后执行do-while:先执行,后判断,至少执行一次for:适合已知次数的计数循环
我们还讲了循环变量、死循环、嵌套循环,以及如何用循环遍历数组。
循环让程序能重复做事,但有时候我们也需要“提前结束”或者“跳过某一次”。下一篇我们就来讲讲怎么控制循环的流程:break、continue,以及为什么不推荐用 goto。