教材《C程序设计》第五版(谭浩强)第 5 章,书页 110–138,共 29 页|建议 2h
前置:第 03–04 课(变量、数据类型、运算符、printf/scanf、if/switch)。这一章和选择结构一起,构成期末考试"读程序写结果"和"编程题"的主战场。
课前一句话
顺序结构让程序"走一遍",选择结构让程序"拐个弯",循环结构让程序"绕圈跑"。这一课就干三件事:学会三种让程序绕圈的写法(while / do…while / for)、学会控制绕圈的节奏(break / continue / 嵌套)、用循环解决教材 11 道例题和书后 17 道习题。学完你应该能:拿到"求和、阶乘、素数、水仙花、斐波那契、最大公约数、九九乘法表、打印图形"这类题,不用想就能搭出循环骨架。
一、先补地基:本章要解决的问题
1.1 一个"笨办法"引出的问题
教材开篇的例子:统计全班 50 个学生的平均成绩。最原始的做法,是先写好一个学生的:
scanf("%f%f%f%f%f", &score1, &score2, &score3, &score4, &score5);
aver = (score1 + score2 + score3 + score4 + score5) / 5;
printf("aver=%7.2f\n", aver);然后把这三行原样再抄 49 遍。缺点一目了然:
- 程序长、重复、难读;
- 改一处(比如 5 门课变 6 门)要改 50 处;
- 50 个还能忍,5 万条数据怎么办?
于是几乎所有高级语言都提供了循环控制:同一段代码只写一次,让计算机自己重复执行。
1.2 本章的比喻:操场跑圈
这一章从头到尾只用一个比喻:你被体育老师罚跑圈。
| 跑圈里的角色 | C 语言里的名字 | 例子 |
|---|---|---|
| 要跑的那个"圈" | 循环(loop,环、圈) | while / for |
| 每圈里做的事 | 循环体 | sum = sum + i; |
| 站在起跑线上 | 循环变量初值 | i = 1 |
| 裁判问"还跑吗" | 循环条件 | i <= 100 |
| 每跑完一圈记一笔 | 循环变量更新 | i++ |
| 没有停下来的条件 | 死循环(infinite loop) | 忘了 i++ |
| 跑到一半直接退赛 | break | 立刻离开操场 |
| 这圈不算,直接进下一圈 | continue | 跳过剩下的动作 |
1.3 循环三要素(本章最重要的一张表)
写任何循环之前,先在草稿纸上回答三个问题:
| 要素 | 问自己 | 写在哪 |
|---|---|---|
| 初值 | 从几开始? | while / do…while:循环之前;for:表达式 1 |
| 条件 | 跑到什么时候停? | while / do…while:括号里;for:表达式 2 |
| 更新 | 每圈怎么变? | while / do…while:循环体内;for:表达式 3 |
考点考试给一段循环让你写输出,正确姿势是:先列三要素,再在草稿纸上手动跑前 3 圈,比盯着代码看快得多。
易错三要素少一个都不行:
- 少初值 → 变量是垃圾值,结果不可预测;
- 少条件 → 不知道什么时候停;
- 少更新 → 死循环(本章最高频的错误)。
二、知识点讲解
2.1(教材 5.1)为什么需要循环控制
一句话:生活里大量"重复做同一件事"的问题,用顺序和选择都写不出来,只能用循环。
教材举的四个例子:
- 输入全班 50 个学生的成绩 —— 重复 50 次相同的输入操作;
- 分别统计全班 50 个学生的平均成绩 —— 重复 50 次相同的计算操作;
- 求 30 个整数之和 —— 重复 30 次相同的加法操作;
- 检查 30 个学生的成绩是否及格 —— 重复 30 次相同的判别操作。
为什么:不用循环,就只能把相同或相似的语句写若干遍,程序冗长、重复、难以阅读和维护。
结论(教材原话):顺序结构、选择结构、循环结构是结构化程序设计的 3 种基本结构,它们是各种复杂程序的基本构成单元。
考点三种基本结构 = 顺序 + 选择 + 循环;循环结构又称"重复结构"。
2.2(教材 5.2)用 while 语句实现循环
一句话定义:只要条件成立,就一遍一遍地执行循环体。
拆名字:while 不是缩写,是英语连词"当……的时候"。while (i <= 100) 读作"当 i 小于等于 100 的时候"。
生活比喻:你跑到裁判面前,裁判先问"还跑吗?"——你说"跑",才放你跑一圈;跑完回来再问一次。先问后跑,这就是 while。
怎么用(一般形式):
while (表达式)
语句表达式:循环条件表达式,值为非 0(真)就执行循环体,为 0(假)就结束循环;语句:循环体。只能是一条语句;要写多条,必须用{ }括成复合语句。
执行过程(4 步,背下来):
- 计算表达式的值;
- 非 0(真)→ 执行循环体;为 0(假)→ 跳过循环体,循环结束;
- 循环体执行完,回到第 1 步;
- 重复,直到表达式为假。
易错三个高频坑:
- while 后面多写一个分号:
while (i <= 100);—— 循环体变成空语句,i 永远不变 → 死循环。 - 循环体多语句不加花括号:
while (i <= 100) sum = sum + i; i++;—— while 只管到第一个分号,i++不属于循环 → 死循环。 - 忘了给循环变量赋初值:
int i; while (i <= 100)—— i 是随机值,可能一次都不执行,也可能跑疯。
教材对例 5.1 的三条分析(原文要点,考试爱考):
- 循环体如果包含一个以上的语句,应该用花括号括起来;不加花括号,while 的范围只到后面第 1 个分号处。
- 不要忽略给 i 和 sum 赋初值(这是未累加前的初始情况),否则值不可预测。
- 在循环体中应有使循环趋于结束的语句(本例的
i++),否则循环永不结束。
2.3(教材 5.3)用 do…while 语句实现循环
一句话定义:先无条件做一次,再问要不要继续。
拆名字:do = 英语"做";while = "当……的时候"。合起来:"先做,再当……的时候继续做"。
生活比喻:体育老师这次改规则了——"先跑一圈,跑完我再问你还要不要跑"。所以哪怕你一开始就说"我不跑",这一圈也躲不掉。先跑后问,至少跑一圈。
怎么用(一般形式):
do
语句
while (表达式);执行过程:
- 先执行一次循环体;
- 计算 while 后面的表达式;
- 非 0(真)→ 回到第 1 步;为 0(假)→ 循环结束。
易错while (表达式) 后面的分号不能丢!这是 do…while 独有的、也是考试最爱挖的坑。丢了分号不会"把下一行当成循环体",而是直接语法错误、编译不通过(MSVC 报 C2146,GCC 报 expected ';')——因为 do…while 是一个完整语句,末尾必须用分号收尾。
易错do 后面的花括号不是必须的(循环体只有一条语句时可以省),但教材建议一律加花括号,否则看到 while (...) 那一行末尾的分号,容易误以为整个语句结束了。
while 与 do…while 的本质区别:
| while | do…while | |
|---|---|---|
| 判断时机 | 先判断,后执行 | 先执行,后判断 |
| 最少执行次数 | 0 次(条件一开始就假) | 1 次(雷打不动) |
| 分号 | while (表达式) 后没有分号 | while (表达式); 后必须有分号 |
教材结论(原话):当 while 后面的表达式的第 1 次值为"真"时,两种循环结果相同;否则结果不同(指二者循环体相同的情况)。
2.4(教材 5.4)用 for 语句实现循环
一句话定义:把"初值、条件、更新"三件事写在同一行括号里的循环。
拆名字:for 是英语"对于 / 为了"。C 里读作"对于(初值;条件;增量)"。
生活比喻:标准跑道边上立了一块牌子,上面写三行字——"从第 1 圈开始;跑到第 100 圈;每圈加 1"。三件事一次说清,不用你另外记。这就是 for。
怎么用(一般形式):
for (表达式1; 表达式2; 表达式3)
语句| 表达式 | 作用 | 执行时机 |
|---|---|---|
| 表达式 1 | 设置初始条件(给循环变量赋初值) | 整个循环只执行 1 次 |
| 表达式 2 | 循环条件表达式,判断是否继续循环 | 每次执行循环体前都要算 |
| 表达式 3 | 循环的调整(如循环变量增值) | 每次执行完循环体后执行 |
最常用的形式:
for (循环变量赋初值; 循环条件; 循环变量增值)
语句例如 for (i = 1; i <= 100; i++):i 初值 1,终值 100,每圈加 1,共 100 圈。
执行过程(5 步):
- 求表达式 1(只做一次);
- 求表达式 2:为真 → 执行循环体,转第 3 步;为假 → 结束循环,转第 5 步;
- 求表达式 3;
- 转回第 2 步;
- 循环结束,执行 for 下面的语句。
考点循环结束后循环变量的值 = "超过"终值的那个数。例如 for (i = 1; i <= 100; i++) 结束后 i = 101;for (i = 100; i >= 1; i--) 结束后 i = 0。规律:循环变量沿着变化方向"超过"循环终值,循环就结束了。这一条几乎每年都考。
for 与 while 等价改写(二者无条件等价):for (表达式1; 表达式2; 表达式3) 语句; 等价于 表达式1; while (表达式2) { 语句; 表达式3; }。
for 的花样(教材 10 条说明里的重点):
| 写法 | 含义 |
|---|---|
for (; i <= 100; i++) | 省略表达式 1(初值在 for 之前给),分号不能省 |
for (i = 1; ; i++) | 省略表达式 2 → 条件恒真 → 死循环 |
for (i = 1; i <= 100;) | 省略表达式 3,更新写在循环体里 |
for (; i <= 100;) | 只留条件,初值和更新都在外面/体内 |
for (;;) | 三个都省 → 等价 while (1) → 死循环 |
for (sum = 0, i = 1; i <= 100; i++) | 表达式 1 可以是逗号表达式,同时给多个变量赋初值 |
for (i = 0, j = 100; i <= j; i++, j--) | 表达式 1、3 都是逗号表达式,两个变量同时变 |
for (i = 1; i <= 100; i++, i++) | 每圈 i 加 2,等价于 i = i + 2 |
for (; (c = getchar()) != '\n'; ) | 表达式 2 可以是任意表达式,只要值非 0 就继续 |
for (int i = 1; i <= 100; i++) | C99 允许在表达式 1 中定义变量,作用域仅限本循环 |
易错for 括号里三个部分之间用的是分号 ;,不是逗号!写成 for (i = 1, i <= 100, i++) 是编译错误(分号数量不对)。逗号只能出现在某一个表达式内部(逗号表达式)。
易错for (int i = 1; ...) 定义的 i 出了循环就不能用。想在循环外再用 i,要在 for 之前定义。
易错表达式 2 是"非 0 即真"。for (i = 0; i = 5; i++) 里 i = 5 是赋值,值恒为 5 → 死循环(应该是 i == 5)。
2.5(教材 5.5)循环的嵌套
一句话定义:一个循环体里又包含另一个完整的循环结构,叫循环的嵌套。
拆名字:嵌套 = "套娃",一层套一层。内嵌的循环里还可以再嵌套,叫多层循环。
生活比喻:大圈套小圈——"跑 4 个大圈,每个大圈里做 5 个俯卧撑"。大圈(外循环)跑 4 次,小圈(内循环)每次都要从头做 5 个。
怎么用:3 种循环(while、do…while、for)可以互相嵌套,教材列了 6 种合法组合(while 套 while、do 套 while、for 套 for、while 套 for、for 套 while、do 套 for),全部合法。
关键规则:
- 外层循环执行 1 次,内层循环要完整地执行一遍;
- 无 break 时,总执行次数 = 外层次数 × 内层次数;
- 内层循环变量每次都要重新赋初值(用 for 最省心,因为表达式 1 每次进内层都会执行)。
易错用 while 写嵌套时,内层循环变量忘了重新赋初值,内层只会执行第一次,后面全部跳过。这是嵌套最经典的 bug。
考点双层 for 循环的执行次数、输出图形、输出矩阵,是期末必考。
2.6(教材 5.6)几种循环的比较
一句话:三种循环大多数情况下可以互相替代,但各有最顺手的场合。
| 比较项 | while | do…while | for |
|---|---|---|---|
| 判断与执行的先后 | 先判断后执行 | 先执行后判断 | 先判断后执行 |
| 最少执行次数 | 0 | 1 | 0 |
| 初值写在哪 | 循环之前 | 循环之前 | 表达式 1 |
| 更新写在哪 | 循环体内 | 循环体内 | 表达式 3 |
| 适合场景 | 次数不确定 | 至少要执行一次 | 次数确定 |
| 末尾分号 | 无 | 有 | 无 |
教材的 4 条结论:
- 3 种循环都可以用来处理同一问题,一般情况下它们可以互相代替。
- while 和 do…while 只在 while 后面括号内指定循环条件,因此循环体内应包含使循环趋于结束的语句;for 可以在表达式 3 中包含该操作,功能更强,凡 while 能完成的,for 都能实现。
- while 和 do…while 的循环变量初始化应在语句之前完成;for 可以在表达式 1 中完成。
- 三种循环都可以用 break 跳出循环,用 continue 结束本次循环。
考点"for 比 while 功能强"这句话常出现在选择题里;"do…while 至少执行一次"是判断题常客。
2.7(教材 5.7)改变循环执行的状态
正常循环靠"条件变假"结束。但有时需要提前结束:例如慈善募捐,收到 10 万元就结束,事先并不知道要收多少次。C 提供两个语句:break 和 continue。
2.7.1 用 break 语句提前终止循环
一句话定义:立刻终止整个循环,跳到循环体外面,接着执行循环下面的语句。
拆名字:break = 英语"打断、折断"。你在 switch 里见过它(第 04 课),它的作用就是"打断当前结构"。
生活比喻:跑到第 3 圈,裁判突然吹哨"停!不用跑了"。你直接离开操场,后面的圈数全部作废。
怎么用:
break;- 一般形式就是
break;; - 通常写成
if (条件) break;,即"满足某个条件就提前退出"。
易错break 只能用于循环语句和 switch 语句之中,不能单独使用(不能写在 if 里单独用——if 里用 break 的前提是 if 外面有循环或 switch)。
考点双层循环里,break 只终止内层循环,外层循环继续。教材原文的结论:内循环体内的 break"提前终止内循环",流程跳到内循环体之外(执行内循环体下面的语句),不是跳到外循环体之外。
2.7.2 用 continue 语句提前结束本次循环
一句话定义:只结束这一次循环,跳过循环体中 continue 后面尚未执行的语句,直接进入下一次循环。
拆名字:continue = con-(一起、继续)+ tinue(保持),英语"继续"。注意:是"继续循环",不是"结束循环"。
生活比喻:跑到第 3 圈,裁判说"这一圈不算,剩下的不用做了,直接进第 4 圈"。圈数照记,只是这一圈的活儿没干完。
怎么用:
continue;- 一般形式
continue;; - 作用:跳到循环体结束点之前,然后:
- 对 for:执行表达式 3(如
i++),再判断表达式 2; - 对 while / do…while:直接回到条件判断处(不会自动执行循环体里的
i++!)。
易错在 while 里把 i++ 写在 continue 后面,会导致 i 永远不增加 → 死循环。要么把 i++ 挪到 continue 之前,要么改用 for。
2.7.3 break 语句和 continue 语句的区别
一句话:break 是"整个循环不干了",continue 是"这一圈不干了,下一圈照干"。
| 比较项 | break | continue |
|---|---|---|
| 作用范围 | 整个循环 | 本次循环 |
| 之后流程 | 跳出循环体,执行循环下面的语句 | 回到循环条件处(for 先执行表达式 3) |
| 还判断循环条件吗 | 不再判断 | 继续判断 |
| 能否用于 switch | 能 | 不能 |
| 双层循环中 | 只跳出内层 | 只跳过内层的本次 |
教材用两个 while 结构对比(图 5.13、图 5.14):表达式 2 为真时,break 的箭头跳出整个循环,continue 的箭头回到循环开头。
2.8(教材 5.8)循环程序举例
一句话:这一节是前面所有语法知识的"综合演练场",5 道例题分别代表 5 类经典算法:
| 例题 | 算法类型 | 核心手法 |
|---|---|---|
| 例 5.7 | 求 π 的近似值 | 迭代:由上一项推出下一项,累加到精度够为止 |
| 例 5.8 | Fibonacci 数列 | 递推:f3 = f1 + f2,再滚动 f1 = f2; f2 = f3; |
| 例 5.9 | 判断素数 | 试除 + break:能被 2~(n-1) 中任一数整除就不是素数 |
| 例 5.10 | 求 100~200 全部素数 | 嵌套循环:外层遍历,内层判定 |
| 例 5.11 | 译密码 | 字符循环 + ASCII 运算 |
这 5 道题的完整代码、逐行解释和运行结果都放在第三部分。
三、现场写代码
例 5.1 / 例 5.2:1+2+…+100 的三种写法
题目:求 1+2+3+…+100。 思路:累加。sum 存和(初值 0),i 存当前要加的数(初值 1);重复 100 次"sum = sum + i; i++;"。
(1)while 版
#include <stdio.h>
int main()
{
int i = 1, sum = 0;
while (i <= 100)
{
sum = sum + i;
i++;
}
printf("sum=%d\n", sum);
return 0;
}运行结果:sum=5050
逐行:
#include <stdio.h>:引入标准输入输出库,后面才能用 printf。int main():主函数,程序从这里开始。int i = 1, sum = 0;:i 是循环变量(当前加数),初值 1;sum 是累加器,初值 0(求和一定要先清零)。while (i <= 100):当 i 不超过 100 时继续;i 变成 101 时条件为假,循环结束。sum = sum + i;:把当前 i 累加到 sum。i++;:i 加 1,为下一圈准备;没有这一行就是死循环。printf(...):循环结束后输出,注意它在花括号外面。return 0;:正常结束。
(2)do…while 版
#include <stdio.h>
int main()
{
int i = 1, sum = 0;
do
{
sum = sum + i;
i++;
} while (i <= 100);
printf("sum=%d\n", sum);
return 0;
}运行结果:sum=5050
逐行:与 while 版唯一的区别是先执行一次循环体再判断;本题 i=1 满足条件,所以结果相同。注意 } while (i <= 100); 末尾的分号。
(3)for 版
#include <stdio.h>
int main()
{
int i, sum = 0;
for (i = 1; i <= 100; i++)
sum = sum + i;
printf("sum=%d\n", sum);
return 0;
}运行结果:sum=5050
逐行:
int i, sum = 0;:i 只声明,不赋初值(初值放到 for 里)。for (i = 1; i <= 100; i++):i=1 只做一次;每圈先判断 i<=100;每圈结束 i++。sum = sum + i;:循环体只有一条,可以不写花括号(但建议写)。- 循环结束时 i = 101。
考点三种写法等价;考试让你"用 while 改写 for"或反过来,照上面模板搬。
例 5.3:while 和 do…while 的比较
题目:输入 i,分别用 while 和 do…while 求 1+2+…+i。 思路:循环体相同,只有判断时机不同。
(1)while 版
#include <stdio.h>
int main()
{
int i, sum = 0;
printf("please enter i,i=?");
scanf("%d", &i);
while (i <= 10)
{
sum = sum + i;
i++;
}
printf("sum=%d\n", sum);
return 0;
}(2)do…while 版
#include <stdio.h>
int main()
{
int i, sum = 0;
printf("please enter i,i=?");
scanf("%d", &i);
do
{
sum = sum + i;
i++;
} while (i <= 10);
printf("sum=%d\n", sum);
return 0;
}运行结果(教材两次):
- 输入 1:两者都输出
sum=55; - 输入 11:while 版输出
sum=0;do…while 版输出sum=11。
逐行解释:
- 输入 11 时,while 的判断
11 <= 10一开始就是假,循环体一次都不执行,sum 保持 0。 - do…while 先执行一次:sum = 0+11 = 11,i 变 12,再判断
12 <= 10为假,退出。 - 这一组对比就是"do…while 至少执行一次"的最好证据,考试常拿它出读程序题。
例 5.4:break 提前终止循环(慈善募捐)
题目:全系 1000 名学生募捐,总数达到 10 万元就结束,统计此时捐款人数和人均捐款数。 思路:for 最多循环 1000 次;每输入一笔就累加,一旦 total ≥ 100000 就 break。
#include <stdio.h>
#define SUM 100000
int main()
{
float amount, aver, total;
int i;
for (i = 1, total = 0; i <= 1000; i++)
{
printf("please enter amount:");
scanf("%f", &amount);
total = total + amount;
if (total >= SUM) break;
}
aver = total / i;
printf("num=%d\naver=%10.2f\n", i, aver);
return 0;
}运行结果(教材输入 12000、24600、3200、5643、21900、12345、23000):
num=7
aver= 14669.71逐行:
#define SUM 100000:符号常量,编译前把 SUM 全部替换成 100000。float amount, aver, total;:amount 单人捐款,total 总额,aver 人均。for (i = 1, total = 0; ...):表达式 1 是逗号表达式,同时给 i 和 total 赋初值(total 必须清零)。scanf("%f", &amount);:读入一笔捐款。total = total + amount;:累加。if (total >= SUM) break;:达到 10 万立刻跳出 for。aver = total / i;:break 时 i 正好是已捐款人数(第 7 个人捐完触发,i=7 还没执行 i++)。printf("num=%d\naver=%10.2f\n", i, aver);:%10.2f表示总宽 10 列、两位小数。
考点break 之后 i 的值是多少?—— 已经输入数据的人数,不会再 +1。这是本题最值钱的一句话。
例 5.5:continue 提前结束本次循环
题目:输出 100~200 之间不能被 3 整除的数。 思路:逐个检查;能被 3 整除就 continue 跳过输出,否则输出。
#include <stdio.h>
int main()
{
int n;
for (n = 100; n <= 200; n++)
{
if (n % 3 == 0)
continue;
printf("%d ", n);
}
printf("\n");
return 0;
}运行结果(前几个):
100 101 103 104 106 107 109 110 112 113 ...共 68 个数;循环固定执行 101 次(100~200 含两端)。
逐行:
for (n = 100; n <= 200; n++):n 从 100 到 200。if (n % 3 == 0):n 能被 3 整除(余数为 0)。continue;:跳过本轮剩下的printf,直接去执行n++。printf("%d ", n);:只有不能被 3 整除的 n 才会走到这里。printf("\n");:循环结束后换行,注意在循环外。
易错continue 跳过的是"本轮剩下的语句",不跳过 n++(因为 n++ 在 for 的表达式 3 里)。如果这是 while 循环,且 n++ 写在 continue 后面,就会死循环。
例 5.6:循环的嵌套(输出 4×5 矩阵)
题目:输出
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20思路:外循环 i 控制行(1~4),内循环 j 控制列(1~5),每格输出 i*j;用 n 计数,每输出 5 个数换行。
#include <stdio.h>
int main()
{
int i, j, n = 0;
for (i = 1; i <= 4; i++)
for (j = 1; j <= 5; j++, n++)
{
if (n % 5 == 0) printf("\n");
printf("%d\t", i * j);
}
printf("\n");
return 0;
}运行结果:4 行 5 列,如题(教材截图中第一行前会先空一行,因为 n=0 时先执行了换行)。
逐行:
int i, j, n = 0;:i 外层(行),j 内层(列),n 记录已输出个数。for (i = 1; i <= 4; i++):外循环 4 次。for (j = 1; j <= 5; j++, n++):内循环 5 次,同时 n 每格加 1(逗号表达式)。if (n % 5 == 0) printf("\n");:n 是 5 的倍数就换行。printf("%d\t", i * j);:输出乘积,\t是制表符,对齐用。- 内循环结束一轮,外循环 i 加 1,内层 j 重新从 1 开始。
考点嵌套循环的执行次数 = 4×5 = 20 次。
教材的两次改动(必看,专治 break/continue 混淆):
- 在内循环体最前面加
if (i == 3 && j == 1) break;→ 第 3 行整行不输出,因为 break 结束的是内层循环,流程进入下一次外循环(i=4)。 - 改成
if (i == 3 && j == 1) continue;→ 第 3 行只少了第一个数 3,从 6 开始输出,后面 4 个数整体左移一格(因为少输出了一次\t)。
例 5.7:求 π 的近似值
题目:π/4 = 1 - 1/3 + 1/5 - 1/7 + …,直到某一项绝对值小于 10⁻⁶ 为止(该项不累加)。 思路:找规律——分子恒为 1;分母 n 每项 +2;符号每项取反。用 term 存当前项,sign 存符号。
#include <stdio.h>
#include <math.h>
int main()
{
int sign = 1;
double pi = 0.0, n = 1.0, term = 1.0;
while (fabs(term) >= 1e-6)
{
pi = pi + term;
n = n + 2;
sign = -sign;
term = sign / n;
}
pi = pi * 4;
printf("pi=%10.8f\n", pi);
return 0;
}运行结果:pi=3.14159065
逐行:
#include <math.h>:用到 fabs,必须包含数学库头文件。int sign = 1;:符号,第一项为正。double pi = 0.0, n = 1.0, term = 1.0;:pi 先存多项式的和(最后才乘 4);n 是分母;term 是当前项,初值 1。while (fabs(term) >= 1e-6):当前项绝对值 ≥ 10⁻⁶ 就继续;fabs求双精度绝对值(整数用 abs)。pi = pi + term;:累加当前项。n = n + 2;:下一项的分母。sign = -sign;:符号取反。term = sign / n;:算出下一项(sign 是 int、n 是 double,除法自动按 double 算)。pi = pi * 4;:和乘以 4 才是 π。printf("pi=%10.8f\n", pi);:输出 8 位小数。
教材的三个补充(考点密集):
fabs和abs的区别:abs(x)求整数绝对值,结果是整型;fabs(x)求双精度绝对值。term 是 double,必须用 fabs。- 想提高精度只改循环条件即可:
while (fabs(term) >= 1e-8)→ 输出 3.14159263。 - 本程序 1e-6 时循环体执行 50 万次,1e-8 时执行 5000 万次(这就是习题 2 的答案)。
例 5.8:Fibonacci 数列前 40 个数
题目:前两个数为 1、1,从第 3 个数起每个数等于前两个数之和,求前 40 个。 思路:滚动法。只用 f1、f2 两个变量:先输出 f1、f2,然后 f1 = f1 + f2; 得到下一个,f2 = f2 + f1; 再得到下一个。
#include <stdio.h>
int main()
{
int f1 = 1, f2 = 1;
int i;
for (i = 1; i <= 20; i++)
{
printf("%12d %12d", f1, f2);
if (i % 2 == 0) printf("\n");
f1 = f1 + f2;
f2 = f2 + f1;
}
return 0;
}运行结果(前两行):
1 1 2 3
5 8 13 21最后一行:24157817 39088169 63245986 102334155。
逐行:
int f1 = 1, f2 = 1;:前两个月各 1 对兔子。for (i = 1; i <= 20; i++):每圈输出 2 个数,20 圈正好 40 个。printf("%12d %12d", f1, f2);:每个数占 12 列,右对齐。if (i % 2 == 0) printf("\n");:每输出 4 个数(2 圈)换行。f1 = f1 + f2;:f1 变成下一个数(第 3 个月)。f2 = f2 + f1;:此时 f1 已是新数,f2 变成再下一个(第 4 个月)。
易错两个赋值顺序不能反。如果先写 f2 = f2 + f1;,f2 被改掉后,f1 = f1 + f2; 用的就不是原来的 f2,整个数列全错。
考点"滚动变量"是递推题的标准套路,第 6 章数组、第 7 章函数都会再用。
例 5.9:判断素数
题目:输入一个大于 3 的整数 n,判断它是否为素数。 思路:让 n 被 2~(n-1) 的每个整数除;只要有一个能整除,就 break。循环结束后看 i:i<n 说明是 break 出来的 → 不是素数;i==n 说明正常结束 → 是素数。
#include <stdio.h>
int main()
{
int n, i;
printf("please enter a integer number,n=?");
scanf("%d", &n);
for (i = 2; i < n; i++)
if (n % i == 0) break;
if (i < n)
printf("%d is not a prime number.\n", n);
else
printf("%d is a prime number.\n", n);
return 0;
}运行结果:17 is a prime number.
逐行:
for (i = 2; i < n; i++):除数从 2 试到 n-1。if (n % i == 0) break;:能整除就提前退出。if (i < n):关键判断——break 出来时 i 还没走到 n;正常结束时 i 已经等于 n。- 否则(i == n)就是素数。
改进版(教材例 5.9 第二个程序):只需试除到 √n。
#include <stdio.h>
#include <math.h>
int main()
{
int n, i, k;
printf("please enter a integer number,n=?");
scanf("%d", &n);
k = sqrt(n);
for (i = 2; i <= k; i++)
if (n % i == 0) break;
if (i >= k + 1)
printf("%d is a prime number.\n", n);
else
printf("%d is not a prime number.\n", n);
return 0;
}运行结果:327 is not a prime number.
逐行:
k = sqrt(n);:k 取 √n 的整数部分(sqrt 返回 double,赋给 int 自动截断)。for (i = 2; i <= k; i++):只试到 k。if (i >= k + 1):正常结束(一次都没 break)→ 素数。
易错教材这个改进版写的是 if (i < k),当 n 是平方数(如 49,k=7)时会误判成素数。正确写法是 if (i >= k + 1)(与例 5.10 一致)。考试一律按"i 是否超过 k"判断最稳。
例 5.10:求 100~200 的全部素数
题目:求 100~200 的全部素数。 思路:外层遍历 101~199 的奇数,内层试除到 √n;用 m 计数,每 10 个换行。
#include <stdio.h>
#include <math.h>
int main()
{
int n, k, i, m = 0;
for (n = 101; n <= 200; n = n + 2)
{
k = sqrt(n);
for (i = 2; i <= k; i++)
if (n % i == 0) break;
if (i >= k + 1)
{
printf("%d ", n);
m = m + 1;
if (m % 10 == 0) printf("\n");
}
}
printf("\n");
return 0;
}运行结果:
101 103 107 109 113 127 131 137 139 149
151 157 163 167 173 179 181 191 193 197
199逐行:
for (n = 101; n <= 200; n = n + 2):偶数一定不是素数,只查奇数。k = sqrt(n);:内层上限。for (i = 2; i <= k; i++):内层试除。if (i >= k + 1):内层正常结束 → n 是素数。m = m + 1;:素数个数加 1。if (m % 10 == 0) printf("\n");:每 10 个换一行。
例 5.11:译密码
题目:把字母变成其后第 4 个字母(A→E,…,W→A,X→B,Y→C,Z→D),非字母不变。输入一行字符,输出密码。 思路:逐个读字符,是字母就 +4;如果在 W~Z(或 w~z)范围内,+4 会超出字母表,要再 -26 绕回来。
#include <stdio.h>
int main()
{
char c;
c = getchar();
while (c != '\n')
{
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
if (c >= 'W' && c <= 'Z' || c >= 'w' && c <= 'z')
c = c - 22;
else
c = c + 4;
}
printf("%c", c);
c = getchar();
}
printf("\n");
return 0;
}运行结果:输入 China! 输出 Glmre!
逐行:
char c;:字符变量。c = getchar();:读入一个字符。while (c != '\n'):没读到换行就继续。if (...字母判断...):只处理字母。if (c >= 'W' && c <= 'Z' || c >= 'w' && c <= 'z'):最后 4 个字母,+4 会越界,所以等价于 +4-26 = -22。else c = c + 4;:其余字母直接 +4。printf("%c", c);:输出变换后的字符。c = getchar();:读下一个字符(这一行不能漏,否则死循环)。
改进版(教材):把读字符合并进 while 条件。
#include <stdio.h>
int main()
{
char c;
while ((c = getchar()) != '\n')
{
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
c = c + 4;
if (c > 'Z' && c <= 'Z' + 4 || c > 'z')
c = c - 26;
}
printf("%c", c);
}
printf("\n");
return 0;
}逐行:
while ((c = getchar()) != '\n'):先读入再判断,省掉两处 getchar。c = c + 4;:字母先统一 +4。if (c > 'Z' && c <= 'Z' + 4 || c > 'z'):+4 后超出 Z(或 z)的,减 26 绕回。- 输出。
易错教材特别提醒:内嵌 if 不能写成 if (c > 'Z' || c > 'z'),因为所有小写字母都满足 c > 'Z',会被错误地减 26。必须限定范围。
四、书上习题精讲
书页 137–138,共 17 题,逐题覆盖。
第 1 题(画流程图)
题目:请画出例 5.6 中给出的 3 个程序段的流程图。
答案要点:三个流程图骨架相同,差别只在内循环体开头。
- 原程序:外层
i=1→ 判断i≤4→ 内层j=1→ 判断j≤5→ 判断n%5==0(是则换行)→ 输出i*j→j++, n++→ 回内层判断 → 内层结束 →i++→ 回外层判断。 - 加 break 的程序:在内层循环体最前面加判断框"
i==3 && j==1?",为是则执行break,流程跳出内层循环(不是跳出外层),直接去做i++。 - 加 continue 的程序:同一位置判断,为是则执行
continue,流程回到内层循环的j++, n++,继续内层的下一次,只是跳过本次的 printf。
考点画图时注意:break 的箭头指向内层循环框外,continue 的箭头指回内层循环的调整部分。
第 2 题(统计例 5.7 的循环次数)
题目:请补充例 5.7 程序,分别统计当 fabs(t) >= 1e-6 和 fabs(t) >= 1e-8 时执行循环体的次数。
思路:加一个计数器 count,每进一次循环体加 1。
#include <stdio.h>
#include <math.h>
int main()
{
int sign = 1, count = 0;
double pi = 0.0, n = 1.0, term = 1.0;
while (fabs(term) >= 1e-6)
{
pi = pi + term;
n = n + 2;
sign = -sign;
term = sign / n;
count++;
}
pi = pi * 4;
printf("pi=%10.8f count=%d\n", pi, count);
return 0;
}答案:
fabs(term) >= 1e-6:500000 次(50 万次),输出pi=3.14159065;- 把条件改成
>= 1e-8:50000000 次(5000 万次),输出pi=3.14159263。
考点这道题说明"精度提高 100 倍,计算量也大约提高 100 倍",是算法效率的直观例子。
第 3 题(最大公约数和最小公倍数)
题目:输入两个正整数 m 和 n,求其最大公约数和最小公倍数。
思路:辗转相除法求最大公约数——a % b 的余数记作 t,然后 a = b; b = t;,直到 b 为 0,此时的 a 就是 gcd。最小公倍数 = m / gcd * n(先除后乘,防止溢出)。
#include <stdio.h>
int main()
{
int m, n, a, b, t, gcd, lcm;
printf("请输入两个正整数 m n: ");
scanf("%d %d", &m, &n);
a = m; b = n;
while (b != 0)
{
t = a % b;
a = b;
b = t;
}
gcd = a;
lcm = m / gcd * n;
printf("最大公约数=%d 最小公倍数=%d\n", gcd, lcm);
return 0;
}运行结果(输入 12 18):最大公约数=6 最小公倍数=36
易错lcm = m * n / gcd 在 m、n 较大时会溢出;写成 m / gcd * n 更安全。若用 while 求 gcd,记得用副本 a、b,否则 m、n 被改掉,后面算 lcm 就错了。
第 4 题(统计一行字符)
题目:输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数。
思路:用 getchar() 一次读一个字符,读到 '\n' 结束;用四个计数器分别累加。
#include <stdio.h>
int main()
{
int letters = 0, spaces = 0, digits = 0, others = 0;
char c;
printf("请输入一行字符:\n");
while ((c = getchar()) != '\n')
{
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
letters++;
else if (c == ' ')
spaces++;
else if (c >= '0' && c <= '9')
digits++;
else
others++;
}
printf("字母=%d 空格=%d 数字=%d 其他=%d\n", letters, spaces, digits, others);
return 0;
}运行结果(输入 Abc 12!@#):字母=3 空格=1 数字=2 其他=3
易错判断字母必须大小写两段都写;c >= 'a' && c <= 'z' 不能写成 c >= 'a' || c <= 'z'(后者恒真)。
第 5 题(Sn = a + aa + aaa + …)
题目:求 Sn = a + aa + aaa + … + aa…a 之值,其中 a 是一个数字,n 表示 a 的位数,n 由键盘输入。例如 n=5、a=2 时为 2+22+222+2222+22222。
思路:后一项 = 前一项 × 10 + a;边生成边累加。
#include <stdio.h>
int main()
{
int a, n, i;
long term = 0, sum = 0;
printf("请输入 a 和 n: ");
scanf("%d %d", &a, &n);
for (i = 1; i <= n; i++)
{
term = term * 10 + a;
sum = sum + term;
}
printf("Sn=%ld\n", sum);
return 0;
}运行结果(输入 2 5):Sn=24690
考点"后一项由前一项推出来"是循环题的通用套路(和 Fibonacci 一个思路)。
第 6 题(1!+2!+…+20!)
题目:求 Σn!(即求 1!+2!+3!+4!+…+20!)。
思路:term = term * i 边乘边加。不要每项都从 1 重新乘一遍(那样是 O(n²))。
#include <stdio.h>
int main()
{
int i;
double term = 1, sum = 0;
for (i = 1; i <= 20; i++)
{
term = term * i;
sum = sum + term;
}
printf("1!+2!+...+20!=%.0f\n", sum);
return 0;
}运行结果:1!+2!+...+20!=2561327494111820288
易错真实值是 2561327494111820313,double 只有约 15~16 位有效数字,后几位是舍入误差。要精确结果,把两个变量改成 64 位整数 long long,输出用 %lld,结果就是 2561327494111820313。
第 7 题(三个求和)
题目:求 Σ(k=1→100)k + Σ(k=1→50)k² + Σ(k=1→10)1/k。
思路:三个独立的循环,分别累加;第三个要用浮点除法 1.0 / k。
#include <stdio.h>
int main()
{
int k;
double s1 = 0, s2 = 0, s3 = 0;
for (k = 1; k <= 100; k++) s1 = s1 + k;
for (k = 1; k <= 50; k++) s2 = s2 + k * k;
for (k = 1; k <= 10; k++) s3 = s3 + 1.0 / k;
printf("sum=%.6f\n", s1 + s2 + s3);
return 0;
}运行结果:sum=47977.928968
易错1 / k 是整数除法,k>1 时结果全是 0;必须写 1.0 / k(或 (double)1 / k)。
第 8 题(水仙花数)
题目:输出所有的"水仙花数"。所谓"水仙花数"是指一个 3 位数,其各位数字立方和等于该数本身。例如 153 是水仙花数,因为 153 = 1³ + 5³ + 3³。
思路:遍历 100~999;用 / 和 % 拆出百位 a、十位 b、个位 c;判断 a³+b³+c³ == n。
#include <stdio.h>
int main()
{
int n, a, b, c;
for (n = 100; n <= 999; n++)
{
a = n / 100;
b = n / 10 % 10;
c = n % 10;
if (a*a*a + b*b*b + c*c*c == n) printf("%d ", n);
}
printf("\n");
return 0;
}运行结果:153 370 371 407
考点拆位公式:百位 n/100、十位 n/10%10、个位 n%10,第 6 章数组还会再用。
第 9 题(完数)
题目:一个数如果恰好等于它的因子之和,这个数就称为"完数"。例如 6 的因子为 1、2、3,而 6 = 1+2+3,因此 6 是"完数"。编程找出 1000 之内的所有完数,并按下面格式输出其因子:6 its factors are 1,2,3。
思路:对每个 i,用内层循环累加它的真因子(1 ~ i-1 中能整除 i 的);和等于 i 就是完数,再输出因子。
#include <stdio.h>
int main()
{
int i, j, s;
for (i = 2; i < 1000; i++)
{
s = 0;
for (j = 1; j < i; j++)
if (i % j == 0) s = s + j;
if (s == i)
{
printf("%d its factors are 1", i);
for (j = 2; j < i; j++)
if (i % j == 0) printf(",%d", j);
printf("\n");
}
}
return 0;
}运行结果:
6 its factors are 1,2,3
28 its factors are 1,2,4,7,14
496 its factors are 1,2,4,8,16,31,62,124,248易错内层每次都要把 s 清零(s = 0; 写在外循环体内、内循环之前),否则会一直累加。
第 10 题(分数序列前 20 项和)
题目:有一个分数序列 2/1, 3/2, 5/3, 8/5, 13/8, 21/13, …,求出这个数列的前 20 项之和。
思路:分子、分母都符合 Fibonacci 规律:后一项的分子 = 前一项的分子 + 分母,后一项的分母 = 前一项的分子。用 a、b 两个变量滚动。
#include <stdio.h>
int main()
{
int i;
double a = 2, b = 1, t, sum = 0;
for (i = 1; i <= 20; i++)
{
sum = sum + a / b;
t = a;
a = a + b;
b = t;
}
printf("sum=%.6f\n", sum);
return 0;
}运行结果:sum=32.660261
易错交换 a、b 时必须用临时变量 t;写成 a = a + b; b = a; 会让 b 拿到新值,序列全错。
第 11 题(球落地)
题目:一个球从 100m 高度自由落下,每次落地后反弹回原高度的一半,再落下,再反弹。求它在第 10 次落地时共经过多少米,第 10 次反弹多高。
思路:第一次落地只算 100m 的下落;之后每次"反弹 + 下落"是 2 倍当前反弹高度。
#include <stdio.h>
int main()
{
double h = 100, s = 100;
int i;
for (i = 2; i <= 10; i++)
{
h = h / 2;
s = s + 2 * h;
}
printf("第10次落地共经过 %.6f 米\n", s);
printf("第10次反弹高度 %.8f 米\n", h / 2);
return 0;
}运行结果:
第10次落地共经过 299.609375 米
第10次反弹高度 0.09765625 米易错循环结束后 h 是第 9 次反弹高度(0.1953125),第 10 次反弹还要再除以 2。
第 12 题(猴子吃桃)
题目:猴子第 1 天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第 2 天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第 10 天早上想再吃时,就只剩一个桃子了。求第 1 天共摘多少个桃子。
思路:倒推。设第 10 天早上剩 1 个;前一天的数量 = (今天数量 + 1)× 2。从第 9 天倒推到第 1 天,共倒推 9 次。
#include <stdio.h>
int main()
{
int day, x = 1;
for (day = 9; day >= 1; day--)
x = (x + 1) * 2;
printf("第1天共摘了 %d 个桃子\n", x);
return 0;
}运行结果:第1天共摘了 1534 个桃子
考点倒推题的公式是"把正向操作反过来做":正向是 (x/2 - 1),反向就是 (x+1)*2。
第 13 题(迭代法求平方根)
题目:用迭代法求 x = √a。求平方根的迭代公式为 x(n+1) = (x(n) + a/x(n)) / 2,要求前后两次求出的 x 的差的绝对值小于 10⁻⁵。
#include <stdio.h>
#include <math.h>
int main()
{
double a, x0, x1;
printf("请输入 a: ");
scanf("%lf", &a);
x0 = a / 2;
x1 = (x0 + a / x0) / 2;
while (fabs(x1 - x0) >= 1e-5)
{
x0 = x1;
x1 = (x0 + a / x0) / 2;
}
printf("sqrt(%.2f)=%.6f\n", a, x1);
return 0;
}运行结果(输入 2):sqrt(2.00)=1.414214
考点迭代法的固定骨架:给初值 → 算新值 → 判断差值 → 不满足就滚回去。
第 14 题(牛顿迭代法)
题目:用牛顿迭代法求方程 2x³ - 4x² + 3x - 6 = 0 在 1.5 附近的根。
思路:牛顿公式 x = x - f(x)/f'(x)。这里 f(x)=2x³-4x²+3x-6,f'(x)=6x²-8x+3。
#include <stdio.h>
#include <math.h>
int main()
{
double x0 = 1.5, x1, f, d;
do
{
f = 2*x0*x0*x0 - 4*x0*x0 + 3*x0 - 6;
d = 6*x0*x0 - 8*x0 + 3;
x1 = x0 - f / d;
if (fabs(x1 - x0) < 1e-5) break;
x0 = x1;
} while (1);
printf("root=%.6f\n", x1);
return 0;
}运行结果:root=2.000000
考点本题的根恰好是 2,可以手算验证 f(2)=16-16+6-6=0。
第 15 题(二分法)
题目:用二分法求方程 2x³ - 4x² + 3x - 6 = 0 在 (-10, 10) 的根。
思路:区间两端函数值异号,取中点;中点函数值与左端同号就把左端移到中点,否则右端移到中点;区间足够小就结束。
#include <stdio.h>
#include <math.h>
int main()
{
double a = -10, b = 10, m;
double fa = 2*a*a*a - 4*a*a + 3*a - 6;
while (b - a > 1e-6)
{
m = (a + b) / 2;
if ((2*m*m*m - 4*m*m + 3*m - 6) * fa < 0) b = m;
else a = m;
}
printf("root=%.6f\n", (a + b) / 2);
return 0;
}运行结果:root=2.000000
易错判据是"两端函数值异号(乘积小于 0)",不是"中点函数值为 0"。本题中 fa 只用来提供"左端函数值的符号":a 被换成 m 时,f(m) 与 fa 必然同号,所以 fa 不必重算;如果觉得不放心,可以在每轮循环里重新计算 fa,结果一样。
第 16 题(输出菱形图案)
题目:输出以下图案:
*
***
*****
*******
*****
***
*思路:上半部分 4 行(i=1~4),每行先打 4-i 个空格,再打 2*i-1 个星号;下半部分 3 行(i=3~1),规律相同。
#include <stdio.h>
int main()
{
int i, j;
for (i = 1; i <= 4; i++)
{
for (j = 1; j <= 4 - i; j++) printf(" ");
for (j = 1; j <= 2 * i - 1; j++) printf("*");
printf("\n");
}
for (i = 3; i >= 1; i--)
{
for (j = 1; j <= 4 - i; j++) printf(" ");
for (j = 1; j <= 2 * i - 1; j++) printf("*");
printf("\n");
}
return 0;
}运行结果:如上图(7 行)。
考点图形打印的通用方法:先找行号 i 与"空格数""星号数"的函数关系,再用两个内层循环分别打印。
第 17 题(乒乓球比赛名单)
题目:两个乒乓球队进行比赛,各出 3 人。甲队为 A、B、C 3 人,乙队为 X、Y、Z 3 人。已抽签决定比赛名单。有人向队员打听比赛的名单,A 说他不和 X 比,C 说他不和 X、Z 比,请编程序找出 3 对赛手的名单。
思路:三重循环枚举所有配对;用 a != b && c != a && c != b 保证每人只配一次;再加上题目的两个条件。
#include <stdio.h>
int main()
{
char a, b, c;
for (a = 'X'; a <= 'Z'; a++)
for (b = 'X'; b <= 'Z'; b++)
if (a != b)
for (c = 'X'; c <= 'Z'; c++)
if (c != a && c != b && a != 'X' && c != 'X' && c != 'Z')
printf("A-%c B-%c C-%c\n", a, b, c);
return 0;
}运行结果:A-Z B-X C-Y
考点"枚举 + 约束"是逻辑推理题的标准解法;注意三个 != 保证 A、B、C 的对手互不相同。
🛠 随课练习(只给题不给答案)
🔮 猜输出
题 1
#include <stdio.h>
int main()
{
int i;
for (i = 1; i <= 5; i++)
{
if (i == 3) continue;
printf("%d ", i);
}
return 0;
}题 2
#include <stdio.h>
int main()
{
int i = 5, sum = 0;
do
{
sum = sum + i;
i++;
} while (i < 5);
printf("%d %d\n", sum, i);
return 0;
}题 3
#include <stdio.h>
int main()
{
int i, j;
for (i = 1; i <= 3; i++)
{
for (j = 1; j <= 3; j++)
{
if (j == 2) break;
printf("%d%d ", i, j);
}
}
return 0;
}题 4
#include <stdio.h>
int main()
{
int i = 0, s = 0;
while (i < 5)
{
i++;
if (i == 3) continue;
s = s + i;
}
printf("%d\n", s);
return 0;
}🛠 变式题
题 5(照猫画虎):把下面的 while 循环改写成等价的 for 循环,填在横线上。
int i = 1, sum = 0;
while (i <= 100)
{
sum = sum + i;
i++;
}
/* 改写为: */
for (______; ______; ______)
______;题 6(改需求):用循环求 1~100 中所有偶数的和(分别用 while 和 for 各写一遍)。
题 7(改需求):写一个程序模拟"输入密码,最多允许输错 3 次":用 do…while 实现,密码正确就提示"欢迎"并结束,3 次都错就提示"锁定"。
题 8(补全):补全下面的程序片段,输出九九乘法表(下三角)。
int i, j;
for (i = 1; i <= 9; i++)
{
for (j = 1; ______; j++)
printf("%d*%d=%-3d", j, i, ______);
______;
}🐛 改错题
题 9:下面的程序想求 1~100 的和,找出错误并改正。
#include <stdio.h>
int main()
{
int i = 1, sum = 0;
while (i <= 100)
{
sum = sum + i;
}
printf("sum=%d\n", sum);
return 0;
}题 10:下面的 for 语句有语法错误,指出并改正。
for (i = 1, i <= 100, i++)
sum = sum + i;题 11:下面的程序想输出 1~5 中除 3 以外的数,找出错误并说明为什么。
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 5)
{
if (i == 3) continue;
printf("%d ", i);
i++;
}
return 0;
}📈 加练题(你随时喊,我随时追加到这里)
(暂无。想加练就说"再出几道 XX 题",我出的题会标上日期追加在这块。)
💬 问答区(你问过的问题 + 我的解答)
(暂无。你问的问题和我的解答会按 Q/A 整理进这里。)
该带走的 6 句话
- 循环三要素:初值、条件、更新——写任何循环前,先在草稿纸上回答这三个问题。
- while 先问后跑(可能 0 次),do…while 先跑后问(至少 1 次),for 把三要素写在一行。
- 循环结束后循环变量的值是"超过终值"的那个数:
for (i=1; i<=100; i++)结束后 i=101。 - break 是整个循环不干了,continue 是这一圈不干了;双层循环里 break 只跳内层。
- 死循环三大来源:忘了更新、while 后多写分号、循环体不加花括号。
- 经典算法都有固定骨架:累加
sum += i、累乘term *= i、素数(试除 + break 后看 i)、递推(滚动变量)。
学完这一课,第 5 章的循环就算拿下了。下一课进入第 6 章《利用数组处理批量数据》——循环的"最佳拍档":一维数组、二维数组、字符数组,还有排序和查找。到那时你会发现,本课的"拆位公式"和"滚动变量"马上就能用上。
📚 教材覆盖清单(第5章,循环结构程序设计)
这张表把本课和教材逐节对上:从网页可以回查书页,从书页也能找到网页位置。标「未展开」的,就是教材里有、本课没讲的部分,已明确列出。
| 教材节号 | 标题 | 书页 | 网站对应 | 状态 |
|---|---|---|---|---|
| 5.1 | 为什么需要循环控制 | p.110 | 2.1(教材 5.1)为什么需要循环控制 | 精讲 |
| 5.2 | 用while语句实现循环 | p.111–112 | 2.2(教材 5.2)用 while 语句实现循环 | 精讲 |
| 5.3 | 用do…while语句实现循环 | p.113–115 | 2.3(教材 5.3)用 do…while 语句实现循环 | 精讲 |
| 5.4 | 用for语句实现循环 | p.116–119 | 2.4(教材 5.4)用 for 语句实现循环 | 精讲 |
| 5.5 | 循环的嵌套 | p.120 | 2.5(教材 5.5)循环的嵌套 | 精讲 |
| 5.6 | 几种循环的比较 | p.121 | 2.6(教材 5.6)几种循环的比较 | 精讲 |
| 5.7 | 改变循环执行的状态 | p.121–126 | 2.7(教材 5.7)改变循环执行的状态 | 精讲 |
| 5.7.1 | 用break语句提前终止循环 | p.122 | 2.7.1 用 break 语句提前终止循环 | 精讲 |
| 5.7.2 | 用continue语句提前结束本次循环 | p.123 | 2.7.2 用 continue 语句提前结束本次循环 | 精讲 |
| 5.7.3 | break语句和continue语句的区别 | p.124–126 | 2.7.3 break 语句和 continue 语句的区别 | 精讲 |
| 5.8 | 循环程序举例 | p.127–136 | 2.8(教材 5.8)循环程序举例 | 精讲 |
| 习题 | 第5章书末习题 | p.137–138 | 本课「书上习题精讲」 | 逐题精讲(第1–17题) |
| 插图 | 本课范围内教材插图 | p.110–136 | 用文字/ASCII 重画 | 未逐张复刻 |
| 附录 | 附录 A–E | p.357–370 | 全书对照页 | 未单独成课 |
- 教材插图(流程图、内存示意图等)本课用文字/ASCII 重画,未逐张复刻,页码见上表。
- 附录 A–E(p.357–370)是速查表,未单独成课;全站对照页列出。
- 标「本课补充」的小节(先补地基、比喻、随课练习等)不是教材原文,是为理解而加的。