for 语句: 形式、常见用法与陷阱
省略表达式与 C99 的 for
逗号运算符
嵌套循环: 打印星号塔
循环惯用法与案例: 累加累乘、素数、非完全平方数
课堂小测: 概念、编码、纠错与分析
for 特别适合次数已知的循环 ("重复 n 次"型), 一般形式:
for (表达式1; 表达式2; 表达式3) 语句
#include <stdio.h>
int main(void) {
int i;
for (i = 10; i > 0; i--) {
printf("%d\n", i);
}
printf("发射!\n");
return 0;
}
三个表达式各司其职:
表达式1: 初始化——只在循环开始前执行一次
表达式2: 条件——非零就继续, 为零结束
表达式3: 递增/递减——每轮最后执行
三件事排在一行, 起点、终点、步长一眼看清——这就是 for 的主场
表达式1 和表达式3 都是语句, 它们的值无关紧要——起作用的是副作用 (赋值、自增自减)
所以表达式3 不一定非要是 i++/i--——步长 2 也行 (如右):
表达式3 写 i-- 还是 --i 结果一样 (只看副作用, 不看值); 但在复杂表达式里二者不同——第 3 周讲过
/* 求 2+4+6+...+n 的偶数和 */
#include <stdio.h>
int main(void) {
int n = 100, sum = 0;
int i;
for (i = 2; i <= n; i += 2) {
sum += i;
}
printf("偶数和 = %d\n", sum);
return 0;
}
for 最适合向上计数或向下计数, 四种常见写法:
/* 从0到n-1, 共n次 */ for (i = 0; i < n; i++)
printf("%d ", i);
/* 从1到n, 共n次 */ for (i = 1; i <= n; i++)
printf("%d ", i);
/* 从n-1倒数到0 */ for (i = n - 1; i >= 0; i--)
printf("%d ", i);
/* 从n倒数到1 */ for (i = n; i > 0; i--)
printf("%d ", i);
输出依次为 0 1 2 …、1 2 3 …、n-1 … 0、n … 1
常见的 for 错误:
方向写反: 向上计数用 </<=, 向下计数用 >/>=——把 > 写成 < 方向就反了
把比较符写成 ==: 初始值就不成立, 循环体一次都不执行
差一错误 (off-by-one): 把 i < n 写成 i <= n 会多循环一次
#include <stdio.h>
int main(void) {
int i;
double x;
for (i = 0, x = 0.0; i <= 10; i++, x += 0.1) {
printf("%2d 次累加后: %.17f\n", i, x);
}
return 0;
}
运行结果 (节选):
1 次累加后: 0.10000000000000001
2 次累加后: 0.20000000000000001
3 次累加后: 0.30000000000000004
...
10 次累加后: 0.99999999999999989
0.1 在二进制里是无限循环小数 (第2周讲过), 累加 10 次并不是 1.0
若用 for (x = 0.0; x != 1.0; x += 0.1): 第 10 次后 x 并不等于 1.0, 循环停不下来 (或次数不对)
教训: 不要用浮点数计数/判等; 循环次数用整数控制、浮点只参与运算; 比较浮点用 x < 1.0 而不是 x != 1.0
省略表达式 1: 初始化挪到循环前
#include <stdio.h>
int main(void) {
int n = 100, i = 1, sum = 0;
for (; i <= n; ++i) {
sum += i;
}
printf("1+...+%d = %d\n", n, sum);
return 0;
}
循环变量的初值已经在前面算好了, 表达式 1 的位置就空着 (分号照写)
运行结果: 1+...+100 = 5050
常见于初值来自前面计算结果的循环
省略表达式 3: 递增/递减写进循环体
#include <stdio.h>
int main(void) {
int n = 100, i, sum = 0;
for (i = 1; i <= n;) {
sum += i++;
}
printf("1+...+%d = %d\n", n, sum);
return 0;
}
递增挪进循环体 (藏在 sum += i++ 里), 表达式 3 的位置空着
风险: 循环体必须保证条件最终变假——忘了递增就是死循环
运行结果: 同样 1+...+100 = 5050
省 1 又省 3 的 for: 两个分号之间只剩条件:
#include <stdio.h>
int main(void) {
int n = 100, i = 1, sum = 0;
for (; i <= n;) {
sum += i++;
}
printf("1+...+%d = %d\n", n, sum);
return 0;
}
等价 while 版:
#include <stdio.h>
int main(void) {
int n = 100, i = 1, sum = 0;
while (i <= n) {
sum += i++;
}
printf("1+...+%d = %d\n", n, sum);
return 0;
}
两版输出相同: 1+...+100 = 5050——都省了初始化与递增, 语义完全等价
这种场合 while 更清晰, 更推荐——看到 for (; …;) 时想想是不是 while 更合适
三个表达式全省: 条件默认为真——for(;;) 是最常见的无限循环写法
#include <stdio.h>
int main(void) {
int n;
for (;;) {
printf("输入一个数 (0 结束): ");
scanf("%d", &n);
if (n == 0)
break; /* 靠 break 跳出 */
printf("%d 的平方是 %d\n", n, n * n);
}
return 0;
}
分号不能少: for(;;) 里两个分号都在
没有 break/return, 循环永远不停——试试把 break 删掉再跑, 用 Ctrl+C 打断
break 下页细讲; 和 5-1 的 while (1) 是同类写法, 习惯用哪个都行
C99 里, for 的表达式1 可以换成声明——循环变量直接在 for 里声明:
#include <stdio.h>
int main(void) {
int n = 100, sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
printf("1+...+%d = %d\n", n, sum);
return 0;
}
变量 i 不用提前声明, 在 for 里声明即用
运行结果: 1+...+100 = 5050
好处: 循环变量成了循环的"私有变量", 外面看不见也改不了
循环内声明的变量出了循环就用不了:
#include <stdio.h>
int main(void) {
int n = 100, sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
/* printf("%d", i); 编译错误: i 出了循环不可见 */
printf("1+...+%d = %d\n", n, sum);
return 0;
}
变量 i 的作用域只在循环内——循环结束后访问会编译报错 (把注释打开试试)
如果要循环结束后还要用这个变量, 就只能在循环外声明
多数情况下 for 自管循环变量更方便、可读性更好
for 需要两个初始化表达式或一次递增多个变量时, 可用逗号表达式作第一或第三个表达式, 形式为:
表达式1, 表达式2
计算逗号表达式的步骤:
首先计算表达式1并且丢弃计算出的值
然后计算表达式2, 并把它的值作为整个表达式的值
逗号运算符的优先级低于所有其他运算符, 结合方向为左结合
例子:
i = 1, j = 2, k = i + j /* (i=1), (j=2), (k=i+j) */
for 可以一次声明多个变量 (类型相同), 用逗号分隔:
#include <stdio.h>
int main(void) {
int n = 5, sum = 0;
for (int i = 1, j = 1; i <= n; i++, j += 2) {
sum += j;
}
printf("前 %d 个奇数之和 = %d\n", n, sum);
return 0;
}
两个变量 i、j 都在表达式 1 声明, 表达式 3 同时递增 (i++、j += 2)
运行结果: 前 5 个奇数之和 = 25 (1+3+5+7+9)
表达式3 的 i++, j += 2 正是刚讲的逗号运算符——概念立刻兑现
常见场景: 一个循环里需要两个同步变化的变量 (计数的 + 参与的)
现场演示: 用 for 重写上一课的平方表程序——输出一模一样
样例 (输入 5):
请输入行数: 5
1 1
2 4
3 9
4 16
5 25
提示: while 版的三件事 (初始化、判定、递增) 怎么搬进 for 的三个表达式?
/* 打印平方表 (for 版) */
#include <stdio.h>
int main(void)
{
int i, n;
printf("请输入行数: ");
scanf("%d", &n);
for (i = 1; i <= n; i++) {
printf("%10d%10d\n", i, i * i);
}
return 0;
}
三件事搬进三个表达式: i = 1 → 表达式1; i <= n → 表达式2; i++ → 表达式3
输出与 while 版完全一致 (输入 5 → 5 行平方表)
for 把三件事集中一行, 计数循环更清晰——这正是 for 的主场
现场演示: 输入高度 n, 打印 n 行的居中星号塔 (第 i 行: 前面补空格, 星号 2i-1 颗)
提示: 每行 = 空格 + 星号——空格负责居中 (第 i 行 n-i 个), 星号第 i 行 2i-1 颗; 加上外层, 一共三层循环
/* stars.c: 打印居中星号塔 */
#include <stdio.h>
int main(void)
{
int n;
printf("输入高度: ");
scanf("%d", &n);
for (int i = 1; i <= n; i++) { /* 外层: 控制行数 */
for (int j = 1; j <= n - i; j++) { /* 空格: 居中 */
printf(" ");
}
for (int j = 1; j <= 2 * i - 1; j++) { /* 星号: 2i-1 颗 */
printf("*");
}
printf("\n");
}
return 0;
}
外层 i 管行数; 两个内层分别管空格和星号——三层的上限都依赖 i
空格数 n - i 递减、星号数 2 * i - 1 递增——居中的数学
内层上限依赖外层变量是嵌套循环的精髓; 输入 5 → 5 层金字塔
累加: 计算 1+2+...+n
累乘: 计算 n! (注意: 初始值为 1, 且很快溢出)
累加器初始化为 0, 累乘器初始化为 1——这是循环的经典写法, 初值写错结果全错
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
int product = 1;
for (int i = 1; i <= n; i++) {
product *= i;
}
现场演示: 输入一个整数, 判断它是不是素数 (只能被 1 和自己整除)
样例:
输入一个数: 13
13 是素数
提示: 检查 2 ~ n-1 里有没有约数; 找到第一个约数就可以停了——想一想用什么
/* primes.c: 判断 n 是否为素数 */
#include <stdio.h>
int main(void) {
int n, d;
printf("输入一个数: ");
scanf("%d", &n);
for (d = 2; d < n; d++) {
if (n % d == 0)
break; /* 找到约数, 提前退出 */
}
if (d < n)
printf("%d 不是素数\n", n);
else
printf("%d 是素数\n", n);
return 0;
}
循环两种退法: 找到约数 break (提前退出) 或跑完 2~n-1 (正常结束)——用 d < n 区分
改进: 只需检查到 \(\sqrt{n}\)——左栏 divisor * divisor <= n 就是等价写法 (不用 math.h); 现场试写 sqrt 版: divisor <= sqrt(n) 对比效果
现场演示: 输入 n, 计算 1~n 中所有非完全平方数的和 (完全平方数: 1、4、9、16、…)
样例 (n = 10):
输入 n: 10
非完全平方数之和 = 41
(1~10 的和 55, 减去完全平方数 1+4+9 = 41)
提示: 怎么判断一个数是不是完全平方数? (想想第 3 周的工具箱); 遇到完全平方数要跳过——用什么?
/* perfectsquare.c: 1~n 中非完全平方数之和 */
#include <stdio.h>
#include <math.h>
int main(void) {
int n, sum = 0;
printf("输入 n: ");
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int r = (int) sqrt(i);
if (r * r == i)
continue; /* 完全平方数, 跳过 */
sum += i;
}
printf("非完全平方数之和 = %d\n", sum);
return 0;
}
r = (int) sqrt(i) + r * r == i: 第 3 周的强制转换在这里兑现
continue 跳过完全平方数——循环与数学库函数的第一次合作
看代码, 回答: 循环体执行了多少次?
int i;
for (i = 0; i < n; i++) {
printf("%d ", i);
}
输出会是从几到几? 一共几个数?
看代码, 回答: 这是什么循环? 怎么停下来?
for (;;) {
scanf("%d", &n);
if (n == 0)
break;
printf("%d 的平方是 %d\n", n, n * n);
}
for(;;) 省略的三个表达式分别默认什么?
用 for 求 1+2+...+n
样例 (n = 100):
1+...+100 = 5050
写完后编译运行验证
打印乘法口诀表: 第 i 行输出 i 个式子
样例 (前 3 行):
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
写完后编译运行验证
找错: 下面代码哪里错了?
int sum = 0;
for (i = 1; i <= n; i--)
sum += i;
想想 i 的值怎么变化, 循环什么时候停?
找错: 想打印乘法口诀表, 下面代码哪里错了?
for (int i = 1; i <= 9; i++)
for (int j = 1; j <= i; j++)
printf("%d×%d=%d\n", j, i, j * i);
运行一下会看到什么? 和真正的口诀表差在哪?
分析运行结果: 下面嵌套循环输出什么?
for (int i = 1; i <= 3; i++) {
int j = 1;
while (j <= i) {
printf("*");
j++;
}
printf("\n");
}
分析运行结果: count 最后是多少?
int count = 0;
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 2; j++)
count++;
printf("%d\n", count);