1. 项目概述:为什么需要深挖while与do...while?
在C++编程的日常里,循环结构就像呼吸一样自然。for、while、do...while,这几个关键字构成了我们处理重复任务的基础。但在我十多年的编码和教学经历中,发现一个有趣的现象:很多开发者,包括一些有经验的程序员,对while和do...while的理解往往停留在“一个先判断后执行,一个先执行后判断”的层面。这种理解没错,但过于肤浅,就像只知道汽车有四个轮子能跑,却不清楚发动机的扭矩曲线和变速箱的换挡逻辑一样,遇到复杂场景就容易“抛锚”。
最近在辅导一些学员和浏览社区时,我频繁看到与循环相关的错误或困惑,例如在头歌等平台的实验题中,关于循环边界和初始条件的错误;在构建工具链时,因环境问题导致的“while handling”类错误;甚至在游戏逻辑和多线程控制中,因循环条件设计不当引发的诡异Bug。这些问题的根源,常常可以追溯到对这两种循环结构的细微差别和适用场景理解不透彻。
while和do...while远不止是语法上的小区别。它们代表了两种不同的“执行-验证”哲学,直接影响了代码的健壮性、可读性以及对边界情况的处理能力。理解它们,就是理解程序控制流中“确定性”与“试探性”的平衡艺术。这篇文章,我将结合大量实战案例,带你穿透语法表层,深入while和do...while的骨髓,看看它们到底在什么场景下能大放异彩,又会在哪里给你埋下坑。
2. 核心概念辨析:while与do...while的本质差异
2.1 语法结构与执行流程的直观对比
我们先从最基础的语法和流程图开始,建立清晰的认知框架。
while循环的语法结构:
while (condition) { // 循环体语句 }它的执行流程可以概括为:“先检查,后干活”。程序首先评估condition(循环条件),如果结果为true(非零),则执行一次循环体;执行完毕后,再次跳回开头检查条件,如此往复,直到condition变为false(零),循环终止,程序继续执行后面的代码。如果第一次检查条件就是false,那么循环体一次都不会执行。
do...while循环的语法结构:
do { // 循环体语句 } while (condition);它的执行流程则是:“先干一次活,再检查要不要继续”。程序首先无条件地执行一次循环体中的语句,执行完成后,再评估condition。如果条件为true,则跳回do处开始下一次循环;如果为false,则循环终止。这意味着,无论初始条件如何,do...while的循环体至少会执行一次。
这个“至少执行一次”的特性,是两者最根本、最核心的区别。它不是一个简单的顺序问题,而是影响了整个程序的逻辑起点。
注意:
do...while语句末尾的分号;是语法的一部分,绝对不能省略。这是许多初学者容易犯错的地方,编译器会因此报错。
2.2 从哲学层面理解两种循环的设计意图
理解了执行流程,我们再往深处想一层:为什么C++(以及C语言)要设计两种看似相似的循环?
while循环体现的是“验证前置”的保守主义思想。它假设“条件可能一开始就不成立”。这种思想在计算机科学中非常普遍,例如在查找链表中的元素、读取用户输入直到特定值出现、或者遍历一个可能为空的容器时。它的逻辑是:“在投入资源(执行循环体)之前,必须先确认有这个必要。” 这保证了代码的安全性,避免了在无效或危险状态下执行操作。
do...while循环体现的是“执行优先”的实践主义思想。它假设“至少需要尝试一次”。这种思想适用于那些必须至少执行一次才能进行评估,或者第一次执行本身就是初始化或探测过程的场景。例如,交互式菜单的显示(总得先显示一次菜单才能让用户选择)、基于上次迭代结果计算下次条件的算法、或者从某个必然存在的源头(如已打开的文件)读取数据直到结束。
我们可以用一个生活化的类比来理解:假设你要去一家餐馆吃饭。
- while循环:你先走到餐馆门口(检查条件),如果看到“正在营业”的牌子(条件为真),你才走进去点菜吃饭(执行循环体)。如果牌子是“暂停营业”(条件为假),你直接转身离开(循环体零次执行)。
- do...while循环:你直接走进餐馆坐下(先执行一次循环体),然后服务员过来告诉你今天有什么菜(评估条件)。如果菜合你心意(条件为真),你就开始点菜并准备吃(继续循环);如果发现今天只供应你不吃的菜(条件为假),你起身离开(循环结束)。无论如何,你“走进餐馆坐下”这个动作至少发生了一次。
这个根本性的设计意图差异,直接决定了它们的应用场景。
3. 经典应用场景与实战案例剖析
知道“是什么”和“为什么”之后,最关键的是“怎么用”。下面我将通过几个典型的、有深度的案例,展示如何根据场景正确选择循环结构。
3.1 while循环的典型战场:条件驱动型任务
场景一:读取未知长度的输入这是while循环最经典的应用之一。例如,从标准输入读取数字直到遇到文件结束符(EOF)或特定终止值。
#include <iostream> using namespace std; int main() { int sum = 0; int value; cout << "请输入一系列整数(输入非数字字符结束): " << endl; // 条件:cin >> value 这个表达式本身会返回流的状态。 // 当输入成功转换为整数时,状态为真,循环继续。 while (cin >> value) { sum += value; } // 当输入遇到非数字字符(如字母)或EOF(Ctrl+Z / Ctrl+D)时,条件变假,循环结束。 cout << "所有整数的和为: " << sum << endl; // 清除流的错误状态,以便后续可能的输入操作 cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); return 0; }为什么用while?因为用户可能直接输入EOF,或者第一个输入就是非法字符。在这种情况下,我们期望循环体(累加)一次都不执行,直接输出和为0。使用do...while会导致程序试图读取一个不存在的value来进行累加,引发未定义行为或逻辑错误。
场景二:遍历链表或其他动态数据结构
struct ListNode { int data; ListNode* next; }; void printList(ListNode* head) { ListNode* current = head; // 当前节点指针初始化为头节点 while (current != nullptr) { // 条件:当前节点不是空指针 cout << current->data << " -> "; current = current->next; // 移动到下一个节点 } cout << "NULL" << endl; }为什么用while?链表可能为空(head为nullptr)。使用while可以完美处理这种情况,如果链表为空,循环体(打印节点数据)一次都不会执行,直接输出“NULL”。这是安全且符合逻辑的。
场景三:等待或轮询满足某个条件在多线程、网络编程或硬件交互中,经常需要等待某个状态改变。
// 模拟一个等待任务完成的场景 bool isTaskCompleted = false; int retryCount = 0; const int MAX_RETRY = 10; while (!isTaskCompleted && retryCount < MAX_RETRY) { // 执行一些检查任务状态的操作... // simulateCheckTask() 是一个假设的函数,返回bool isTaskCompleted = simulateCheckTask(); retryCount++; this_thread::sleep_for(chrono::milliseconds(500)); // 等待500毫秒再检查 } if (isTaskCompleted) { cout << "任务成功完成!" << endl; } else { cout << "任务超时未完成。" << endl; }为什么用while?我们希望在进入循环检查之前,先判断是否已经满足条件(任务已完成或重试次数已满)。如果任务一开始就完成了,我们根本不需要进入循环体。这体现了“验证前置”的思想。
3.2 do...while循环的专属领域:至少执行一次的逻辑
场景一:交互式菜单系统这是教科书级的do...while应用场景。
#include <iostream> using namespace std; int main() { int choice; bool exitProgram = false; do { // 至少显示一次菜单 cout << "\n========== 主菜单 ==========" << endl; cout << "1. 新建文件" << endl; cout << "2. 打开文件" << endl; cout << "3. 保存文件" << endl; cout << "4. 退出程序" << endl; cout << "请输入您的选择 (1-4): "; cin >> choice; switch (choice) { case 1: cout << "执行新建文件操作..." << endl; // ... 具体逻辑 break; case 2: cout << "执行打开文件操作..." << endl; // ... 具体逻辑 break; case 3: cout << "执行保存文件操作..." << endl; // ... 具体逻辑 break; case 4: cout << "感谢使用,程序即将退出。" << endl; exitProgram = true; break; default: cout << "输入无效,请重新输入!" << endl; cin.clear(); // 清除错误状态 cin.ignore(1000, '\n'); // 忽略错误输入 } } while (!exitProgram); // 条件:是否退出程序 return 0; }为什么用do...while?程序必须先显示菜单,用户才能看到选项并做出选择。这个“显示菜单-获取输入-处理”的过程必须至少发生一次。如果用while,你需要一个额外的标志变量或者在循环外先显示一次菜单,代码会显得冗余。
场景二:基于前次结果的迭代计算(如数值逼近)某些算法,下一次迭代的参数依赖于上一次迭代的结果,并且至少需要进行一次计算才能得到有意义的初始比较值。
// 计算平方根的简单迭代法(巴比伦方法) double sqrtBabylonian(double number, double precision = 1e-7) { if (number < 0) return NAN; // 处理负数 double guess = number > 1 ? number / 2 : 1.0; // 初始猜测值 double prevGuess; do { prevGuess = guess; // 保存上一次的猜测值 guess = (guess + number / guess) / 2.0; // 核心迭代公式 cout << "当前猜测值: " << guess << endl; // 用于观察迭代过程 } while (fabs(guess - prevGuess) > precision); // 条件:两次猜测的差值是否大于精度要求 return guess; }为什么用do...while?循环条件fabs(guess - prevGuess) > precision依赖于guess和prevGuess的值。而prevGuess的值是在循环体内被赋值的(prevGuess = guess)。我们必须先执行一次循环体,计算出第一个guess和prevGuess,才能进行有意义的比较。如果使用while,你需要在循环开始前给prevGuess一个无意义的初始值(比如0),然后进行第一次可能无意义的比较,代码逻辑不清晰。
场景三:从必然存在的源读取直到结束假设你已经成功打开了一个文件,你知道文件至少有一个字节(哪怕是空文件,也可能有文件头信息,或者你明确知道文件非空)。
#include <fstream> #include <string> using namespace std; void processFile(const string& filename) { ifstream file(filename); if (!file.is_open()) { cerr << "无法打开文件: " << filename << endl; return; } string line; // 假设我们知道文件至少有一行标题或内容 do { getline(file, line); if (!line.empty()) { // 简单的处理逻辑 cout << "读取到: " << line << endl; } } while (!file.eof()); // 条件:是否到达文件末尾 file.close(); }注意:严格来说,处理文件读取更稳健的方式通常是while (getline(file, line)),因为它同时检查了读取操作是否成功。这里使用do...while是为了强调“至少读取一次”的场景。在实际中,需要根据文件内容的确定性来谨慎选择。
3.3 看似相似,实则不同:对比实验揭示的微妙差异
让我们设计一个小实验,直观感受两者的区别。考虑一个简单的需求:要求用户输入一个正数。
使用while循环的实现:
int getPositiveNumber_While() { int num; cout << "请输入一个正数: "; cin >> num; while (num <= 0) { cout << "输入无效,必须为正数。请重新输入: "; cin >> num; } return num; }流程:提示 -> 读取 ->检查-> 如果无效,则提示并重新读取 -> 再次检查...
使用do...while循环的实现:
int getPositiveNumber_DoWhile() { int num; do { cout << "请输入一个正数: "; cin >> num; if (num <= 0) { cout << "输入无效,必须为正数。" << endl; } } while (num <= 0); return num; }流程:执行(提示并读取)-> 检查 -> 如果无效,则输出错误信息并跳回开头再次执行 -> 再次检查...
关键差异分析:
- 错误提示的位置:在
while版本中,只有在首次输入无效后,才会出现“输入无效”的提示。在do...while版本中,只要输入无效,就会输出错误提示。这意味着,如果用户第一次就输入正确,while版本不会输出任何额外信息;而do...while版本则无法区分“首次提示”和“错误提示”,除非你在循环体内增加额外的状态判断(比如一个bool isFirstTime标志),这会让代码变复杂。 - 代码重复:
while版本中,提示语“请输入一个正数: ”出现了两次(循环前一次,循环体内一次)。这是一种轻微的代码重复。do...while版本则很自然地将提示语只放在循环体内一次。 - 逻辑清晰度:对于“至少需要询问一次”的场景,
do...while的语义更直接——“做(询问),当(条件不满足时)”。while循环的“先检查再询问”逻辑,需要读者稍微绕一下弯,理解第一次检查时num的初始值(来自循环外的输入)是如何来的。
这个例子告诉我们,选择哪种循环,不仅关乎功能正确性,也关乎用户体验和代码的优雅程度。
4. 深入原理与性能考量
4.1 编译器视角下的差异
从底层汇编的角度看,while和do...while编译后的代码结构略有不同,这源于它们控制流的不同。
一个典型的while循环可能被编译成类似下面的结构(伪汇编):
jmp CONDITION_CHECK LOOP_BODY: ; 循环体代码 CONDITION_CHECK: ; 计算条件表达式 cmp [condition], 0 jne LOOP_BODY ; 条件为真,跳回循环体 ; 循环结束,继续执行后续代码可以看到,它先跳转到条件检查处,符合“先判断”的逻辑。
而一个典型的do...while循环则更直接:
LOOP_BODY: ; 循环体代码 CONDITION_CHECK: ; 计算条件表达式 cmp [condition], 0 jne LOOP_BODY ; 条件为真,跳回循环体 ; 循环结束它没有初始的跳转,直接开始执行循环体,符合“至少执行一次”的语义。
在现代编译器的优化下,对于简单的循环,这种差异对性能的影响微乎其微。编译器非常擅长进行循环优化(如循环展开、强度削弱等)。因此,选择哪种循环,首要考虑因素永远是语义正确性和代码清晰度,而不是那可能根本不存在的性能差异。写出符合人类逻辑思维的代码,比试图写出“更高效”但晦涩的代码要重要得多。
4.2 与for循环的关系与选择
C++中还有第三种循环结构:for循环。它本质上是while循环的语法糖,将循环变量的初始化、条件检查和更新集中写在了一行,特别适合计数循环或遍历已知范围的序列。
// for循环:通常用于已知迭代次数的场景 for (int i = 0; i < 10; ++i) { cout << i << " "; } // 等价的while循环 int i = 0; // 初始化 while (i < 10) { // 条件检查 cout << i << " "; ++i; // 更新 }选择指南:
for循环:当你提前知道循环次数,或者有一个明确的循环变量需要初始化、检查、更新时,使用for循环会让意图非常清晰。例如遍历数组、容器,或者执行固定次数的操作。while循环:当循环次数未知,且循环的继续条件由一个复杂表达式或外部状态决定时,使用while。例如“读取直到EOF”、“等待某个标志位改变”、“链表遍历”。do...while循环:当循环体必须至少执行一次,并且循环的继续条件在第一次执行后才能被评估时,使用do...while。例如“显示菜单-获取选择”、“先执行再检查的迭代算法”。
简单来说:for用于“计数”,while用于“当...时”,do...while用于“做...直到...”。
5. 高级话题与常见陷阱
5.1 无限循环的故意与意外
两种循环都可能造成无限循环,但原因和形式略有不同。
故意的无限循环常用于服务器的主事件循环、游戏的主循环等。
// 使用while的常见写法 bool running = true; while (running) { // 处理事件、更新状态、渲染画面... // 在某些条件下设置 running = false; } // 也可以写成 while (true) { // 或 for(;;) // ... if (exitCondition) break; }do...while在这种场景下较少使用,因为通常也需要先检查是否应该退出(例如收到退出信号)。
意外的无限循环往往是编程错误:
while循环:忘记在循环体内更新循环条件相关的变量。int count = 10; while (count > 0) { cout << count << endl; // 忘记了 count--; // 这将导致无限循环打印10 }do...while循环:同样可能忘记更新条件,但由于它至少执行一次,有时问题暴露得更晚。更隐蔽的错误是条件逻辑写反。int input; do { cin >> input; // 我们希望当输入为0时退出 } while (input != 0); // 正确:输入不等于0时继续 // while (input == 0); // 错误!输入等于0时继续,除非输入非0,否则无限循环!
5.2 循环控制:break与continue的微妙影响
break和continue语句在两种循环中的行为一致,但结合循环特性,需要注意。
break:立即终止整个循环,跳到循环语句之后执行。continue:跳过本次循环体中continue之后的语句,直接进入下一次循环的条件判断(对于while和do...while)或更新/判断部分(对于for)。
在do...while中使用continue需要特别小心,因为它会跳过循环体末尾的部分,但程序流程依然会执行while(condition);处的条件判断。这与while循环中continue会跳转到循环开始处的条件判断是类似的,但由于do...while的条件判断在尾部,有时会让人产生误解。
int i = 0; do { i++; if (i == 5) { continue; // 跳过本次循环后续部分 } cout << i << " "; // 注意:这里没有机会再改变i或其他影响条件的变量, // continue后直接去判断 (i < 10) } while (i < 10); // 输出:1 2 3 4 6 7 8 9 10 (跳过了5)这个例子中,当i==5时,continue跳过了cout,但i在while(i < 10)处仍然被检查,并且因为i在循环开头已经自增,所以循环会继续。逻辑是正确的,但需要理清。
5.3 条件表达式的副作用与复杂度
循环条件可以是一个复杂的表达式,甚至可以包含有副作用的操作(如函数调用、赋值)。但这会降低代码的可读性和可维护性。
不推荐的写法(将输入和检查合并,副作用在条件中):
int value; while ((cin >> value) && value != 0) { // 条件中同时进行输入和检查 process(value); }虽然紧凑,但混合了I/O操作和逻辑判断。
更清晰、更推荐的写法:
int value; while (true) { // 使用“无限循环”+内部控制 if (!(cin >> value)) break; // 输入失败则退出 if (value == 0) break; // 遇到终止值则退出 process(value); } // 或者使用do...while的变体 int value; do { if (!(cin >> value)) break; if (value == 0) break; process(value); } while (true);这种写法将不同的退出条件清晰地分开,逻辑更易于理解和调试。记住,代码是写给人看的,其次才是给机器执行的。清晰的逻辑远比一行炫技的代码有价值。
6. 实战经验与避坑指南
结合我多年踩过的坑和调试的经验,这里总结一些关键的注意事项和技巧。
6.1 边界条件与初始状态的深思熟虑
这是使用循环时最容易出错的地方,尤其是while循环。
陷阱案例:使用while循环处理可能为空的容器
vector<int> vec; // 可能为空 int index = 0; // 错误示范:如果vec为空,vec[0]就是未定义行为! while (index < vec.size() && vec[index] > 0) { // ... index++; } // 正确做法:先检查size if (!vec.empty()) { int index = 0; while (index < vec.size() && vec[index] > 0) { // ... index++; } }对于do...while,你更要警惕“至少执行一次”带来的风险。如果循环体内的操作在初始状态下就是非法的(例如对空指针解引用、除以零),那么使用do...while就是灾难。
黄金法则:在写while循环时,问自己:“如果条件一开始就不满足,我的代码会怎样?”在写do...while循环时,问自己:“第一次执行循环体时,所有依赖的变量和状态都准备好了吗?这次执行总是安全的吗?”
6.2 循环变量的生命周期与作用域
在循环中声明的变量,其作用域仅限于该次循环迭代。
while (condition) { int temp = someFunction(); // temp在每次迭代开始时创建,结束时销毁 // 使用temp... } // 这里无法访问temp如果你需要在多次迭代间保持一个状态,就需要将变量声明在循环外部。
int persistentState = 0; do { persistentState += calculateSomething(); // ... } while (persistentState < threshold);对于for循环,循环变量(如int i)的传统声明方式使其作用域延伸至循环体外(C++11之前)。但从C++11开始,在for语句内声明的变量,其作用域通常被限制在循环体内,这是更安全的做法。为了清晰和避免错误,尽量缩小变量的作用域。
6.3 调试循环的实用技巧
- 添加打印语句(最朴素但最有效):在循环开始、结束、以及关键变量改变的地方打印它们的值。这是定位“差一错误”(off-by-one error)和无限循环的利器。
int count = 0; while (someCondition) { cout << "[DEBUG] 进入循环,count = " << count << ", someCondition = " << someCondition << endl; // ... 业务逻辑 count++; cout << "[DEBUG] 更新后 count = " << count << endl; } - 使用调试器设置条件断点:现代IDE(如VS Code、CLion、Visual Studio)都支持条件断点。你可以在循环条件处,或者循环体内设置断点,并附加条件(如
i == 5),这样程序只会在满足条件时暂停,极大提高调试效率。 - 心理模拟或“桌面检查”:对于复杂的循环条件,拿一张纸和一支笔,手动模拟程序执行,记录每次迭代后关键变量的值。这个过程常常能帮你发现逻辑上的漏洞。
- 单元测试:为包含复杂循环的函数编写单元测试,覆盖各种边界情况:空输入、单个元素、大量元素、恰好满足/不满足条件的临界值等。
6.4 代码可读性与重构建议
- 给循环起个“名字”:如果循环体很长或者逻辑复杂,考虑将其提取成一个命名清晰的函数。
// 重构前 while (node != nullptr) { // ... 几十行处理节点的代码 node = node->next; } // 重构后 while (node != nullptr) { processLinkedListNode(node); node = node->next; } // 函数 processLinkedListNode 封装了具体逻辑 - 避免过深的嵌套:循环内部如果又有大量的
if-else和另一个循环,会急剧降低代码可读性。考虑使用continue提前跳过某些情况,或者将内层逻辑提取成函数。 - 使用有意义的变量名:
i,j,k用于简单的计数循环没问题,但对于复杂的条件,使用index,currentNode,retriesLeft这样的名字更能传达意图。
7. 综合案例:一个简单的猜数字游戏
让我们用一个完整的、可运行的小程序来综合运用while和do...while,并展示一些良好的编程习惯。
#include <iostream> #include <cstdlib> #include <ctime> #include <limits> using namespace std; int main() { // 1. 初始化随机数种子 srand(static_cast<unsigned int>(time(nullptr))); bool playAgain = false; int totalGames = 0; int totalGuesses = 0; // 外层do...while:控制是否进行新一轮游戏 do { totalGames++; const int secretNumber = rand() % 100 + 1; // 生成1-100的随机数 int guess = 0; int numberOfGuesses = 0; const int maxGuesses = 7; cout << "\n=== 新一轮猜数字游戏 (1-100) ===" << endl; cout << "你有 " << maxGuesses << " 次机会。" << endl; // 内层while:控制单轮游戏中的猜测过程 bool hasWon = false; while (numberOfGuesses < maxGuesses && !hasWon) { numberOfGuesses++; cout << "第 " << numberOfGuesses << " 次尝试,请输入你的猜测: "; // 健壮的输入处理:使用while循环处理非数字输入 while (!(cin >> guess)) { cin.clear(); // 清除错误状态 cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 丢弃错误输入 cout << "输入无效,请输入一个整数: "; } if (guess < secretNumber) { cout << "太小了!" << endl; } else if (guess > secretNumber) { cout << "太大了!" << endl; } else { hasWon = true; cout << "恭喜你!猜对了!数字就是 " << secretNumber << "。" << endl; cout << "你用了 " << numberOfGuesses << " 次机会。" << endl; } } if (!hasWon) { cout << "很遗憾,机会用完了。正确的数字是 " << secretNumber << "。" << endl; } totalGuesses += numberOfGuesses; // 询问是否再玩一次,使用do...while确保至少询问一次 char choice; bool validChoice = false; do { cout << "\n是否再玩一次?(y/n): "; cin >> choice; // 清理输入缓冲区,防止残留的换行符影响下次读取 cin.ignore(numeric_limits<streamsize>::max(), '\n'); if (choice == 'y' || choice == 'Y') { playAgain = true; validChoice = true; } else if (choice == 'n' || choice == 'N') { playAgain = false; validChoice = true; } else { cout << "无效选择,请输入 y 或 n。" << endl; validChoice = false; // 继续循环 } } while (!validChoice); // 当获得有效输入时退出 } while (playAgain); // 当用户选择继续时,开始新一轮游戏 // 游戏结束统计 cout << "\n=== 游戏结束 ===" << endl; cout << "你总共玩了 " << totalGames << " 轮游戏。" << endl; if (totalGames > 0) { double averageGuesses = static_cast<double>(totalGuesses) / totalGames; cout << "平均每轮用了 " << averageGuesses << " 次猜测。" << endl; } return 0; }在这个案例中,我们看到了三种循环的协同工作:
- 最外层的
do...while (playAgain):用于控制整个游戏流程的重复。它必须至少执行一次(玩一轮游戏),然后根据用户输入决定是否继续。这是do...while的完美场景。 - 内层的
while (numberOfGuesses < maxGuesses && !hasWon):用于控制单轮游戏中的猜测次数。这是一个典型的条件驱动循环,在机会用完或猜中时结束。使用while是因为用户可能在第一次就猜中(循环体零次执行?不,这里至少会进入一次以获取输入,但核心的“检查-反馈”逻辑可能因猜中而提前终止,while的“先检查”在这里体现在检查机会是否用完和是否已赢)。 - 输入验证的
while (!(cin >> guess)):用于处理用户非数字输入的错误情况。这是一个“当输入无效时持续尝试”的循环,也是while的典型应用。
通过这个案例,你可以体会到,在真实的程序中,不同的循环结构会根据其语义各司其职,共同构建出清晰、健壮的程序逻辑。选择正确的循环,能让你的代码意图一目了然,减少Bug,也让自己和未来的维护者读起来更舒服。