第 12 章 不知道次数怎么办——while 与 do-while
🏗️ 前情回顾:上章你问了「知道次数用 for——但不知道次数怎么办?」这一章就是答案。while 和 do-while 不看次数,只看条件——密码输错一次再来一次,直到对了为止。
🎯 本章目标
学完这一章,你能:
- 用
while循环处理"不知道要重复多少次"的场景 - 区分
while和do-while:前者先判断后执行,后者先执行后判断 - 在 for、while、do-while 之间做出正确的选择
- 设计正确的循环条件,避免死循环
- 理解"循环前的准备"和"循环内的更新"这两个关键
📖 故事引入
你和朋友去吃自助餐。规则很简单:只要你还能吃,就可以继续拿。
你不会在进门的时候就想"我要吃 8 盘"——那太蠢了。你的策略是:拿一盘,吃完,感觉"还能吃吗?"→ 能,再去拿。不能,放下筷子。
这个过程就是 while 循环的精神——不知道要重复多少次,只知道"什么时候该停"。
另一个场景:你登录电脑。系统提示"请输入密码"。
- 你输入密码,系统判断对错
- 错了 → "请重试" → 你再输入 → 再判断
- 对了 → 进入桌面
你不知道会输错几次——可能一次就对,可能输错三次。循环次数取决于条件,不取决于事先的数字。
这就是 while 的用武之地。如果说 for 是"跑 5 圈",while 就是"跑到腿软为止"。
🧱 知识讲解
12.1 while 循环:先问再走
while 的语法比 for 更简单:
while (条件) {
// 条件成立就执行,执行完再回来检查条件
}跟 for 对比:
// for 版本:跑 5 圈
for (int i = 1; i <= 5; i++) {
cout << "第" << i << "圈" << endl;
}
// while 版本:跑到腿软
int i = 1; // 初始准备(对应 for 的初始化)
while (i <= 5) { // 条件判断
cout << "第" << i << "圈" << endl;
i++; // 更新(对应 for 的迭代)
}两个版本效果完全一样。区别在于:for 把三要素浓缩在括号里,while 把初始化和迭代拆到了外面。
while 的核心逻辑:先看条件,条件成立才进循环体;循环体跑完再回来看条件;条件不成立了就退出。
12.2 ❌ 经典翻车现场:忘了更新变量
先看这段代码——有什么问题?
int i = 1;
while (i <= 5) {
cout << i << " "; // 🤔 输出:1 1 1 1 1 1 1……停不下来了!
}程序卡住了!屏幕疯狂刷 1,风扇狂转——这是死循环。为什么?因为 i 始终是 1,i <= 5 永远成立,循环永远退不出来。
✅ 正确版本:循环体内别忘了更新
int i = 1;
while (i <= 5) {
cout << i << " ";
i++; // ✅ 每轮结束 i 加 1,最终会走到 6,退出循环
}
// 输出:1 2 3 4 5这就是 while 和 for 最大的区别:for 把三要素锁在括号里,不容易忘;while 把初始化和迭代拆出来,你必须自己管理——忘了任何一步都是灾难。
12.3 经典场景:密码验证
string password;
string correct = "cpp123";
cout << "请输入密码:";
cin >> password;
while (password != correct) {
cout << "密码错误,请重试:";
cin >> password;
}
cout << "登录成功!欢迎回来。" << endl;运行效果:
请输入密码:abc
密码错误,请重试:12345
密码错误,请重试:cpp123
登录成功!欢迎回来。这里 while 循环跑了几次?取决于你输错了几次——可能 0 次(一次就对),可能 3 次,可能 10 次。while 不在乎次数,它只盯着条件。
12.4 do-while:先走再问
while 的问题是——如果一开始条件就不成立,循环体一次都不执行。
但有些场景要求"至少做一次,然后看要不要继续"。比如:
- 菜单程序:先显示菜单让用户选,选"退出"才结束
- 游戏:先玩一局,问"再来一局吗?"
这时候用 do-while:
do {
// 循环体:先执行
} while (条件); // ← 注意这里有分号!关键区别:
| while | do-while | |
|---|---|---|
| 判断时机 | 先判断,后执行 | 先执行,后判断 |
| 最少执行次数 | 0 次 | 1 次 |
| 适合场景 | 可能一次都不需要做 | 至少要做一次 |
菜单程序的例子:
int choice;
do {
cout << "======== 菜单 ========" << endl;
cout << "1. 开始游戏" << endl;
cout << "2. 查看成绩" << endl;
cout << "3. 退出" << endl;
cout << "请选择:";
cin >> choice;
switch (choice) {
case 1: cout << "游戏开始!" << endl; break;
case 2: cout << "你的历史最高分:999" << endl; break;
case 3: cout << "再见!" << endl; break;
default: cout << "无效选项,请重选" << endl;
}
} while (choice != 3);菜单至少显示一次——用户看到菜单才能选择。所以用 do-while 刚好。
⚠️
while (条件)后面没有分号,但do { ... } while (条件);的末尾有分号。这是一个常见的"分号惊魂"——编译报错了别慌,先检查这里。
12.5 死循环:当条件永远成立
这是每个程序员都会经历的"社死"瞬间——程序卡住不动了,风扇狂转,只能强制关闭。罪魁祸首就是死循环:
int i = 1;
while (i > 0) { // i 永远不会 ≤ 0,条件永远成立
cout << i++ << " ";
}更经典的死循环:
int x = 1;
while (x != 10) {
x += 2; // x 是 1, 3, 5, 7, 9, 11... 永远跳不到 10!
}预防死循环的三个检查点:
- 循环变量初始化了吗?
- 循环体内有更新循环变量的语句吗?
- 更新方向会让条件最终变为 false 吗?
💡 如果刻意要写无限循环(比如游戏主循环),C++ 的标准写法是
while (true) { ... }。但要在循环体内用break退出(下一章会讲)。
12.6 循环条件设计技巧
写好循环条件,需要三个习惯:
习惯一:在循环前做好"第一次准备"
// 读入数据直到遇到 0 停止
int num;
cin >> num; // 先读第一个
while (num != 0) {
// 处理 num
cin >> num; // 再读下一个(在循环末尾)
}这种"读入 → 判断 → 处理 → 再读入"的模式极其常见,信息学竞赛题里到处都是。
习惯二:用"哨兵"简化条件
// 统计一串数字的和,以 -1 结束
int sum = 0, num;
while (cin >> num && num != -1) {
sum += num;
}cin >> num 本身也能作为条件——如果输入成功且不是 -1,就继续。这种写法比先读再判断更紧凑。
习惯三:循环条件要向着"能退出"的方向发展
// ❌ 危险:条件依赖用户输入,但用户不知道要输入什么才能退出
int x;
while (x != 42) {
cin >> x;
cout << x * 2 << endl;
}
// ✅ 改进:用哨兵值(如 -1 或 0)明确退出条件
int x;
while (cin >> x && x != -1) {
cout << x * 2 << endl;
}12.7 for vs while vs do-while:选谁?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 知道确切次数(1到100) | for | 三要素紧凑,不会漏 |
| 遍历数组/容器 | for | 天生一对 |
| 输入直到满足条件 | while | 次数不定 |
| 游戏主循环 | while | while(true) + break |
| 至少执行一次 | do-while | 先做再判断 |
| 菜单程序 | do-while | 菜单至少显示一次 |
简单口诀:"有数用 for,没数用 while,必须一次用 do-while。"
✋ 动手试试
试试 1:写一个"猜数字"游戏。程序预设一个秘密数字(如 42),让用户反复猜,每次猜完提示"大了"或"小了",直到猜对为止。用 while 循环。
试试 2:故意把 while 循环里的 i++ 删掉,运行看看。你制造了一个死循环——理解它是怎么发生的,以后就能避免。
试试 3:改写试试 1 的猜数字游戏,用 do-while 实现。对比两种写法,哪个更自然?
试试 4:写一个"数字反转"程序。输入一个正整数,用 while 循环逐位取出来并反转输出。例如输入 1234,输出 4321。(提示:重复做 n % 10 取个位,n /= 10 丢掉个位,直到 n 变成 0。)
⚠️ 你踩过这些坑吗
- [ ] 坑 1:while 里忘了更新变量——
int i = 1; while (i <= 5) { cout << i; }→ 死循环!i 永远是 1,条件永远成立。循环体内一定要有让条件最终不成立的代码。 - [ ] 坑 2:do-while 末尾忘了分号——
do { ... } while (条件)缺分号,编译报错。do-while末尾必须有分号。 - [ ] 坑 3:用
!=但更新方向不对——int x = 1; while (x != 10) { x += 2; }x 跳到 11 绕过了 10。确认更新路径能走到目标值,或者改用<=或哨兵值。 - [ ] 坑 4:混淆 while 和 do-while 的执行顺序——可能"一次都不需要做"的场景(如文件可能为空),用 do-while 会强行处理一条不存在的数据。该用 while 就用 while。
📝 练习
基础题
1. 选择题
(1)以下哪个场景最适合用 while 而不是 for?
A. 输出 1 到 100 B. 输入密码直到正确 C. 求 1+2+...+n D. 判断一个数是不是素数
(2)do-while 和 while 的最大区别是:
A. do-while 更快 B. do-while 至少执行一次 C. do-while 不需要条件 D. do-while 不能嵌套
(3)以下代码执行后,循环体执行了几次?
int i = 5;
while (i > 0) {
i--;
}A. 4 次 B. 5 次 C. 6 次 D. 0 次
2. 填空题
(1)如果条件一开始就不成立,while 循环体会执行 ____ 次,do-while 循环体会执行 ____ 次。
(2)do { ... } while (...); 末尾的 ____ 不能忘。
(3)循环三要素在 while 中需要自己管理的是:____ 和 ____。
3. 代码找错
以下代码想输出 1 到 5,为什么卡住了?修改成正确的版本:
int i = 1;
while (i <= 5) {
cout << i << " ";
}4. 判断题
判断以下说法是否正确:
- ( ) while 循环最少执行 0 次
- ( )
while (true) { }是一个合法的无限循环 - ( ) do-while 循环的条件写在循环体之前
提高题
5. 编程题 — 位数统计
输入一个正整数,用 while 循环统计它是几位数,并输出每一位数字(从低位到高位)。
示例运行:
请输入一个正整数:12345
这个数是 5 位数
从低位到高位:5 4 3 2 16. 编程题 — 最大公约数
输入两个正整数 a 和 b,用辗转相除法求它们的最大公约数(GCD)。算法:
- 计算 a % b 得到余数 r
- 如果 r = 0,b 就是最大公约数
- 否则,令 a = b, b = r,回到步骤 1
用 while 循环实现。
示例运行:
请输入两个数:12 18
最大公约数:67. 编程题 — 数位中是否包含数字 7
输入一个正整数,判断它的各位数字中是否包含数字 7。如果包含,输出"有7";否则输出"没有7"。
示例运行:
请输入一个数:1729
有7挑战题
8. 编程题 — 数字黑洞(角谷猜想)
"数字黑洞"是指对一个正整数反复做某种操作,最终会落入一个循环。角谷猜想:
- 如果 n 是偶数,把它除以 2
- 如果 n 是奇数,把它乘以 3 再加 1
- 重复操作,最终总会变成 1
输入一个正整数 n,输出操作过程中经过的每一个数,直到变成 1。
示例运行:
请输入一个数:6
6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1
共 8 步9. 编程题 — 回文数判断
"回文数"是指正着读和反着读一样的数,如 121、12321。输入一个正整数,判断它是不是回文数。
(提示:用 while 循环反转数字,然后比较反转前后的值是否相等。)
🧠 本章小结
┌─────────────────────────────────────────────────────────┐
│ │
│ while (条件) { ... } 先判断,后执行(最少0次) │
│ do { ... } while (条件); 先执行,后判断(最少1次) │
│ │
│ 选择口诀: │
│ 知道次数 → for │
│ 不知道次数 → while │
│ 至少一次 → do-while │
│ │
│ 死循环 = 条件永远成立 + 没有出口 │
│ 预防:初始化了吗?更新了吗?能走到退出条件吗? │
│ │
│ while 经典模式: │
│ · 密码验证:while (input != correct) { ... } │
│ · 逐位处理:while (n > 0) { n%10; n/=10; } │
│ · 哨兵输入:while (cin >> x && x != -1) { ... } │
│ │
└─────────────────────────────────────────────────────────┘📝 配套练习
共8题。while的核心差异是"不知道次数"——J0046(截半)/J0066(不定量输入)/J0067(猜次数)三种场景各一题。。★核心(课堂必做) ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0046 | https://hydro.ac/d/srqc/p/J0046 | while、/=、条件循环 |
| ◆ 拓展 | J0056 | https://hydro.ac/d/srqc/p/J0056 | while、取模、逐位拆解 |
| ◆ 拓展 | J0053 | https://hydro.ac/d/srqc/p/J0053 | for+while、break、质数 |
| ◆ 拓展 | J0054 | https://hydro.ac/d/srqc/p/J0054 | while、回文、质数双重判定 |
| ◆ 拓展 | J0066 | https://hydro.ac/d/srqc/p/J0066 | while读入不定量、数组倒序 |
| ◆ 拓展 | J0067 | https://hydro.ac/d/srqc/p/J0067 | while、冰雹猜想、状态变化 |
| ◆ 拓展 | J0055 | https://hydro.ac/d/srqc/p/J0055 | while、等比累加、阈值 |
| ◆ 拓展 | luogu-P1420 | https://hydro.ac/p/luogu-P1420 | while、连续计数、重置条件 |
💡 练习建议:先完成 1 道★核心题,确保掌握本章基本方法;再完成 7 道◆拓展题,覆盖不同变式和细节。
配套练习
共8题。while的核心差异是"不知道次数"——J0046(截半)/J0066(不定量输入)/J0067(猜次数)三种场景各一题。★核心(课堂必做) · ◆拓展(课后练习)
| 级别 | 题号 | 链接 | 覆盖知识点 |
|---|---|---|---|
| ★ 核心 | J0046 | https://hydro.ac/d/srqc/p/J0046 | while、/=、条件循环 |
| ◆ 拓展 | J0056 | https://hydro.ac/d/srqc/p/J0056 | while、取模、逐位拆解 |
| ◆ 拓展 | J0053 | https://hydro.ac/d/srqc/p/J0053 | for+while、break、质数 |
| ◆ 拓展 | J0054 | https://hydro.ac/d/srqc/p/J0054 | while、回文、质数双重判定 |
| ◆ 拓展 | J0066 | https://hydro.ac/d/srqc/p/J0066 | while读入不定量、数组倒序 |
| ◆ 拓展 | J0067 | https://hydro.ac/d/srqc/p/J0067 | while、冰雹猜想、状态变化 |
| ◆ 拓展 | J0055 | https://hydro.ac/d/srqc/p/J0055 | while、等比累加、阈值 |
| ◆ 拓展 | luogu-P1420 | https://hydro.ac/p/luogu-P1420 | while、连续计数、重置条件 |
练习建议:先在课堂完成 1 道★核心题,掌握本章基本方法;课后完成 7 道◆拓展题,覆盖不同变式和细节。
自查清单:
- [ ] 我能用 while 处理"不知道重复几次"的场景
- [ ] 我能区分 while 和 do-while,知道各用在哪里
- [ ] 我理解死循环的原因——忘了更新变量或条件永远成立
- [ ] 我能设计正确的循环条件,让它最终能退出
- [ ] 我会在 for、while、do-while 之间做出选择
🚀 下章预告:循环里干的事,本身也需要循环怎么办?比如打印一张表——外层管行、内层管列,循环套着循环跑。下章学嵌套循环,一层不够来两层。