循环结构(一)
while:条件控制的重复

从连续脉搏记录输入到格雷戈里公式,理解“次数未知但条件明确”的循环

本课核心问题

如果我们不知道要输入多少条数据,程序还能重复处理吗?

上节已有

循环的基本概念:把重复操作交给程序。

今天推进

循环次数未知时,由条件决定是否继续。

关键句型

while (条件) { ... }

第1课时学习目标

知识目标

  • 理解 while 的语法与执行过程
  • 区分循环结束条件与继续条件
  • 理解计算值控制和输入值控制两类循环

能力目标

  • 能从问题中找出循环体、循环条件和状态更新
  • 能用教材例题训练循环条件设计
  • 能初步理解 eps 与浮点精度的关系
4.1 / while语句
不知道有多少条记录,
但知道什么时候停止
连续录入门诊脉搏测量值,直到出现结束标志。
while哨兵值输入值控制

脉搏数据流:哪一条才表示结束?

假设程序连续读取脉搏值,使用 -1 表示本次录入结束。

7268757169-1

正常数据

只要 pulse >= 0,就处理当前记录。

结束标志

遇到 -1 时,不再作为脉搏数据处理。

先不写代码:循环设计五问

问题 在本例中的答案
什么操作重复? 读取并处理一条脉搏记录
什么时候继续? pulse >= 0
什么时候停止? pulse < 0
每轮什么会变化? 下一次输入的 pulse
进入循环前要准备什么? 先读取第一条数据

🤖 AI 协作互动:哨兵值逻辑审查

我正在用 C 语言设计哨兵值循环(读取脉搏,输入 -1 结束)。终止条件是 pulse >= 0。请帮我分析:如果用户第一条就输入 -1、或者连续输入负数,我的逻辑是否安全?请按【边界情况—潜在 Bug—测试数据】提供检查建议。

💡 课堂互动:同桌两人尝试用此 Prompt 提问 AI,对比自己的五问设计并验证极端边界。

while 的执行路径

先读入 pulse
→
判断 pulse >= 0
→
处理当前数据
→
再次读入
scanf("%d", &pulse);

while (pulse >= 0) {
    /* 处理当前脉搏值 */
    sum = sum + pulse;
    count++;
    scanf("%d", &pulse);
}

while 语句

while (表达式) {
    循环体语句;
}

执行顺序

  1. 先判断表达式是否为真
  2. 真:执行循环体
  3. 回到条件继续判断
  4. 假:跳出循环
重点:循环条件表示“继续循环”的条件,而不是“结束循环”的条件。

脉搏数据流与 while 循环

脉搏数据流与while循环
教材例4-1
格雷戈里公式求 π:
由计算值控制循环
循环次数不是提前给定,而由当前项是否足够小决定。
fabseps计算值控制

教材例4-1 题面描述

📋 题目要求

使用格雷戈里公式求 \(\pi\) 的近似值,要求精确到最后一项的绝对值小于给定精度 eps:

\[\frac{\pi}{4}=1-\frac{1}{3}+\frac{1}{5}-\frac{1}{7}+\ldots\]

教材例4-1:用格雷戈里公式求 π

\(\frac{\pi}{4}=1-\frac{1}{3}+\frac{1}{5}-\frac{1}{7}+\cdots\)

每一项

item 依次为 1, -1/3, 1/5, ...

变化规律

符号交替,分母每次加2。

停止规则

当 fabs(item) < eps 时停止。

从结束条件推出循环条件

题目要求的结束条件

fabs(item) < eps

当前项的绝对值已经小于给定精度。

继续循环的条件

fabs(item) >= eps

还没有达到停止要求,就继续累加。

循环条件通常是“结束条件”的逻辑否定。这里保留 = 是有依据的。

格雷戈里公式:代码骨架

flag = 1;
denominator = 1;
item = 1.0;
pi = 0;

while (fabs(item) >= eps) {
    pi = pi + item;
    flag = -flag;
    denominator = denominator + 2;
    item = flag * 1.0 / denominator;
}

pi = pi * 4;

重点然僧:每轮循环之后,item 必须向停止条件靠近。

跟踪变量:每轮循环改变了什么?

轮次 flag denominator item 动作
初始 1 1 1.0 准备第1项
第1轮后 -1 3 -1/3 准备下一项
第2轮后 1 5 1/5 准备下一项
第3轮后 -1 7 -1/7 准备下一项

循环体中最关键的不是“累加”,而是更新下一轮的状态。

扩展问题:这个 = 能不能去掉?

版本A

while (fabs(item) >= eps)

对应结束条件:fabs(item) < eps

版本B

while (fabs(item) > eps)

对应结束条件:fabs(item) <= eps

先定义边界,再写条件。

如果 item == eps 呢?

进一步追问:浮点数真的会精确等于我们写下来的十进制小数吗?

#include <stdio.h>

int main(void)
{
    double x = 0.1 + 0.2;

    printf("%.17f\n", x);
    printf("%.17f\n", 0.3);

    if (x == 0.3)
        printf("equal\n");
    else
        printf("not equal\n");
}

典型输出:数学值与机器表示并不总一致

可能看到的结果

0.30000000000000004
0.29999999999999999
not equal

课堂解释

  • 很多十进制小数无法用有限二进制精确表示
  • 计算机会保存一个足够接近的近似值
  • 浮点数做“相等判断”时要特别谨慎

整数与小数的表示差异

整数例子

13₁₀ = 1101₂

在一定范围内可以精确表示。

有限小数

0.5₁₀ = 0.1₂

可以有限表示。

循环小数

0.1₁₀ ≈ 0.000110011...

只能近似表示。

那为什么 fabs(item) >= eps 仍然合理?

阈值比较

fabs(item) >= eps

判断当前项是否还没有达到精度阈值。

相等比较

x == 0.3

判断两个浮点计算结果是否完全相等,通常要谨慎。

本例中的 eps 不是普通数字,而是“允许误差/精度阈值”。

🤖 AI 协作互动:浮点精度探秘

在 C 语言计算格雷戈里公式求 π 时,为什么不能用 item == 0 作为循环终止条件,而必须用 fabs(item) >= 1e-4?请用二进制 IEEE 754 浮点数舍入误差帮我清晰解释。

💡 课堂互动:提问 AI 并记录:二进制中哪些常见十进制小数(如 0.1)是无限循环的?

交互演示:浮点数与循环精度显微镜

🔬 D04-EXT01 · 浮点数与循环精度显微镜

本演示工具用于直观剖析 C 语言中浮点数运算的二进制近似现象与循环控制精度:

  • 十进制转二进制表示:即时显示十进制数在内存中的二进制位结构。
  • 浮点数舍入误差:对比 0.1、0.2、0.3 及 0.1 + 0.2 的 17 位高精度输出。
  • 格雷戈里公式单步模拟:逐步跟踪 while (fabs(item) >= eps) 的迭代过程,实时观察项值 item 与 \(\pi\) 近似值的收敛。
教材例4-2
统计一批学生成绩:
由输入值控制循环
从“已知n个成绩”迁移到“输入多少个不确定”。
哨兵值平均值不及格人数

教材例4-2 题面描述

📋 题目要求

从键盘输入一批学生的成绩,计算平均成绩,并统计不及格学生的人数。
(注:输入负数时标志数据输入结束)

从 for 到 while:问题变在哪里?

已知人数

for (i = 1; i <= n; i++) {
    scanf("%lf", &score);
    total += score;
}

未知人数

scanf("%lf", &score);
while (score >= 0) {
    total += score;
    scanf("%lf", &score);
}

统计平均成绩与不及格人数

设计问题 答案
重复操作 处理一个成绩:累计、计数、判断是否不及格
循环条件 score >= 0
结束标志 输入负数
需要维护的状态 total、num、fail

教材例4-2:修正后的代码框架

num = 0;
total = 0;
fail = 0;

scanf("%lf", &score);
while (score >= 0) {
    total += score;
    num++;
    if (score < 60)
        fail++;
    scanf("%lf", &score);
}

if (num > 0) {
    printf("Average: %.2f\n", total / num);
    printf("Number of failures: %d\n", fail);
}

注意:判断不及格用的是当前成绩 score,不是累计总分 total。

🤖 AI 协作互动:交互式 Code Review

请检查我这段 C 语言 while 成绩统计代码:如果我在循环体内不小心遗漏了更新 score 的 scanf 语句,或者把 initial scanf 删除了,程序会发生什么?请给出调试指导。

💡 课堂互动:观察 AI 提示的死循环和垃圾值读入现象,并在 IDE 中验证单步执行。

本课总结:三类循环控制

计数控制

次数已知,常用 for。

计算值控制

如格里高里公式,由 fabs(item) 与 eps 控制。

输入值控制

如连续成绩输入,由哨兵值决定结束。

设计循环时先问:重复什么?什么时候继续?每轮如何接近结束?