1. 项目概述:从“先问后做”到“先做后问”的逻辑分野
在C++的流程控制世界里,while和do...while这对兄弟循环语句,常常让初学者感到困惑。表面上看,它们都用于重复执行一段代码,语法也仅有一词之差,但正是这微小的结构差异,决定了它们截然不同的应用哲学和适用场景。我见过不少项目,因为循环选型不当,导致逻辑漏洞或代码冗余。今天,我们就来彻底拆解这对循环,不光是记住语法,更要理解它们背后的“为什么”——为什么C++要设计两种循环?在什么场景下,用哪一种才是更优解?这不仅仅是语法学习,更是培养程序员严谨逻辑思维的关键一步。
简单来说,while是“先判断,后执行”的谨慎派,而do...while则是“先执行,后判断”的行动派。这个根本区别,直接影响了它们在处理边界条件、用户交互、状态初始化等场景下的表现。理解这个区别,能让你在编写菜单系统、数据验证、游戏主循环等代码时,做出更精准、更健壮的设计选择。无论你是刚接触C++的新手,还是想巩固基础的开发者,这次深入的对比都将让你对循环控制有全新的认识。
2. 核心语法与执行流程的深度剖析
2.1 while循环:严谨的条件守卫者
while循环的语法结构非常清晰:
while (condition) { // 循环体语句 }它的执行流程可以用一个简单的决策图来描述:程序首先评估括号内的condition(循环条件)。这个条件必须是一个可以转换为布尔值(true或false)的表达式。如果条件为true,则进入循环体,执行其中的所有语句;执行完毕后,程序会再次跳转到while语句的开头,重新评估条件。只要条件一直为true,这个过程就会一直重复。一旦条件变为false,程序将立即跳出循环,继续执行循环体之后的代码。
这里有一个至关重要的细节:条件判断发生在循环体执行之前。这意味着,如果一开始条件就不满足(为false),那么循环体一次都不会执行。这是while循环最核心的特性,也决定了它的使用场景。
举个例子,我们想读取用户输入,直到输入一个正数为止:
int num; std::cout << "请输入一个正数: "; std::cin >> num; while (num <= 0) { std::cout << "输入无效,请重新输入一个正数: "; std::cin >> num; } std::cout << "你输入的正数是: " << num << std::endl;在这段代码中,我们首先进行了一次输入(这可以看作是一种“初始化”),然后进入while循环进行判断。如果用户第一次就输入了正数,循环条件num <= 0为false,循环体根本不会执行,程序直接输出结果。这种“先验”的判断方式,使得while循环在处理可能“零次迭代”的场景时非常自然和安全。
注意:
while循环的条件表达式必须能够在循环体内被改变,否则如果条件恒为true,就会产生无限循环。例如while(1)或while(true)如果不配合break语句,会导致程序卡死。
2.2 do...while循环:保证至少一次的行动派
do...while循环的语法结构如下:
do { // 循环体语句 } while (condition);请注意结尾的分号,这是语法要求,很容易被遗忘。它的执行流程与while恰恰相反:程序首先无条件地执行一次循环体内的所有语句,执行完毕后,再评估while后面的condition。如果条件为true,则跳转回do关键字处,开始下一次迭代;如果条件为false,则循环终止,继续执行后续代码。
这个“先执行,后判断”的特性是do...while的灵魂。它保证了循环体至少会被执行一次,无论初始条件如何。
一个经典的场景是交互式菜单系统:
int choice; do { std::cout << "\n===== 主菜单 =====\n"; std::cout << "1. 开始游戏\n"; std::cout << "2. 加载存档\n"; std::cout << "3. 设置\n"; std::cout << "0. 退出\n"; std::cout << "请选择: "; std::cin >> choice; // 根据choice执行不同操作,例如switch语句 switch(choice) { case 1: /* 开始游戏 */ break; case 2: /* 加载存档 */ break; case 3: /* 进入设置 */ break; case 0: std::cout << "再见!\n"; break; default: std::cout << "无效选择,请重试。\n"; } } while (choice != 0); // 只要不选择0(退出),就继续显示菜单在这个例子中,我们希望程序至少显示一次菜单,等待用户输入。即使用户一开始就想退出(输入0),他也需要先看到菜单才知道选项0是退出。使用do...while完美契合了这个需求:先展示菜单(执行循环体),再判断用户是否选择退出。如果强行用while来实现,就需要在循环外先重复一遍打印菜单和获取输入的代码,造成冗余。
2.3 对比表格与核心差异总结
为了更直观地把握两者的区别,我将核心差异总结如下表:
| 特性维度 | while循环 | do...while循环 |
|---|---|---|
| 语法结构 | while(条件) { 循环体 } | do { 循环体 } while(条件); |
| 执行顺序 | 先判断条件,后执行循环体 | 先执行循环体,后判断条件 |
| 迭代次数 | 0次或多次(条件初值为假则0次) | 1次或多次(至少执行1次) |
| 适用场景 | 循环次数未知,且可能一次都不需要执行时 | 循环体必须至少执行一次时 |
| 代码风格 | 条件检查前置,逻辑清晰 | 将后置条件检查封装在语法中 |
| 常见错误 | 忘记在循环体内更新条件,导致死循环 | 忘记结尾的分号(;) |
从底层逻辑上讲,while循环更像一个严格的“门卫”,每次进入都要验票(检查条件)。而do...while循环则像一个“体验式营销”,先让你进来体验一圈(执行循环体),出门时再问你是否还想再来一次(检查条件)。这个根本性的顺序差异,是指导我们选择两种循环的唯一金标准。
3. 典型应用场景与选型决策逻辑
理解了基本差异后,我们来看看在实际编程中,如何根据具体需求做出正确的选择。选型错误轻则导致代码冗余,重则引入逻辑错误。
3.1 必须使用 do...while 的场景
当你的业务逻辑强制要求某段代码至少执行一次时,do...while是更自然、更安全的选择。
场景一:基于状态的初始化与验证假设你在编写一个连接数据库的函数,连接过程可能失败,失败后需要重试,但无论如何至少要尝试连接一次。
bool connectToDatabase() { Connection conn; int retryCount = 0; bool isConnected = false; do { std::cout << "尝试连接数据库... (第" << retryCount + 1 << "次)\n"; isConnected = conn.establish(); // 尝试连接 retryCount++; if (!isConnected && retryCount < 3) { std::cout << "连接失败,2秒后重试...\n"; std::this_thread::sleep_for(std::chrono::seconds(2)); } } while (!isConnected && retryCount < 3); // 只要未连接且未超次数,就重试 return isConnected; }这里,establish()函数至少需要被调用一次。如果用while,你需要先在循环外调用一次establish()来初始化isConnected状态,代码会显得重复。
场景二:交互式输入验证(增强版)上面提到了菜单,再举一个数据输入的例子:要求用户输入一个在1-100范围内的数字。
int score; do { std::cout << "请输入成绩 (1-100): "; std::cin >> score; if (std::cin.fail()) { // 处理非数字输入 std::cin.clear(); // 清除错误状态 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略错误行 score = -1; // 设置为无效值,让循环继续 } } while (score < 1 || score > 100);这个逻辑必须至少向用户询问一次。do...while的结构让“询问-验证”这个单元非常紧凑。
实操心得:在写
do...while时,我习惯在while(condition)后面紧接着写一个注释,说明循环继续的条件是什么。比如} while (!isValid); // 当输入无效时继续循环。这能极大提高代码的可读性,尤其是在条件表达式比较复杂的时候。
3.2 必须使用 while 的场景
当循环有可能一次都不需要执行时,while循环是更安全、更符合直觉的选择。
场景一:遍历未知长度的数据最常见的例子是读取文件直到末尾,或者处理来自网络的数据流。
std::ifstream file("data.txt"); std::string line; while (std::getline(file, line)) { // getline成功读取返回true,失败(如到文件尾)返回false std::cout << "读取到: " << line << std::endl; // 处理每一行数据... } // 如果data.txt是空文件,循环体一次都不会执行,这是完全正确的行为。这里,如果文件为空,我们当然不希望进入循环体。while循环完美地处理了这种情况。
场景二:基于外部事件的循环(如游戏主循环)虽然游戏主循环通常是一个无限循环(while(true)),但更多时候,循环的继续与否依赖于一个外部状态标志。
bool isGameRunning = true; SDL_Event event; while (isGameRunning) { // 处理输入事件 while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { isGameRunning = false; // 用户点击关闭窗口,改变条件 } // ... 处理其他事件 } // 更新游戏逻辑 // 渲染画面 } // 游戏结束,释放资源...循环开始时isGameRunning为true,进入循环。在循环体内,根据用户操作(如点击关闭按钮)来改变这个条件,从而在合适的时机退出循环。如果使用do...while,即使游戏一开始就应该结束(比如加载资源失败),也会强制执行一次更新和渲染,可能引发错误。
场景三:查找满足条件的元素在容器中查找第一个满足某条件的元素,如果找不到,循环应该零次执行。
std::vector<int> numbers = {10, 20, 30, 40, 50}; auto it = numbers.begin(); int target = 25; // 寻找第一个大于target的数 while (it != numbers.end() && *it <= target) { ++it; } if (it != numbers.end()) { std::cout << "找到第一个大于" << target << "的数: " << *it << std::endl; } else { std::cout << "未找到符合条件的数。" << std::endl; }如果容器为空,或者所有数都不大于target,查找循环一次都不应该执行。while循环确保了这一点。
3.3 两者皆可但各有优劣的场景
有些场景下,两种循环都能实现功能,但其中一种在代码简洁性或可读性上更胜一筹。
场景:计算整数位数这是一个经典例子。计算一个正整数n的位数。
使用
while循环:int countDigits_while(int n) { int count = 0; int temp = n; while (temp != 0) { temp /= 10; count++; } return (n == 0) ? 1 : count; // 需要单独处理n为0的情况 }对于
n=0,while循环一次都不会执行,count保持为0。所以我们必须额外处理n==0的情况,返回1。逻辑上有点割裂。使用
do...while循环:int countDigits_doWhile(int n) { int count = 0; int temp = n; do { temp /= 10; count++; } while (temp != 0); return count; }对于
n=0,循环体会执行一次(0/10=0),count变为1,循环条件temp != 0为false,退出循环。结果直接就是1,无需特殊判断。代码更紧凑,逻辑更统一。
在这个特定问题上,do...while因为天然处理了“0次迭代”的边界情况(数字0的位数为1),代码更优雅。这告诉我们,选型时不仅要看主要逻辑,更要仔细考虑边界条件和极端情况。
4. 底层实现、效率考量与编译器视角
很多初学者会问:while和do...while在效率上有区别吗?从现代编译器的优化角度看,在绝大多数情况下,两者的效率差异可以忽略不计。编译器生成的机器码都非常高效。但是,理解它们潜在的细微差别,有助于我们写出对编译器更友好的代码。
4.1 可能的底层代码差异
我们来看一个简单的C++代码和它可能对应的汇编思路(非实际输出,仅为概念性说明)。
C++ 代码:
// 场景:循环累加到100 int i = 0; int sum = 0; while (i < 100) { sum += i; i++; }概念性汇编思路 (x86):
mov eax, 0 ; sum = 0 mov ecx, 0 ; i = 0 loop_start: cmp ecx, 100 ; 比较 i 和 100 jge loop_end ; 如果 i >= 100,跳转到循环结束 add eax, ecx ; sum += i inc ecx ; i++ jmp loop_start ; 无条件跳回循环开始 loop_end: ; ... 后续代码while循环在每次迭代开始时进行条件检查(cmp和jge)。
C++ 代码 (do...while):
int i = 0; int sum = 0; do { sum += i; i++; } while (i < 100);概念性汇编思路 (x86):
mov eax, 0 ; sum = 0 mov ecx, 0 ; i = 0 loop_start: add eax, ecx ; sum += i inc ecx ; i++ cmp ecx, 100 ; 比较 i 和 100 jl loop_start ; 如果 i < 100,跳回循环开始 ; ... 后续代码do...while循环在每次迭代结束时进行条件检查。
从指令条数看,两者非常接近。主要的区别在于首次条件检查的位置。while循环在第一次执行循环体前就有一次条件判断和跳转,而do...while是直接执行循环体。在循环次数很大(比如上百万次)时,do...while可能因为减少了一次初始跳转而带来极其微小的性能优势,但这种优势在现代CPU的流水线和分支预测面前,几乎无法被察觉。
4.2 对编译器优化的启示
对于编译器来说,它更关心的是循环的“可预测性”和“是否可展开”。无论是while还是do...while,一个结构清晰、条件简单的循环更容易被编译器优化。
- 循环展开:编译器可能会将多次迭代合并成一次,减少条件判断的次数。例如,把
for(int i=0; i<100; i++) sum+=i;展开成每次处理4个i。这对while和do...while同样适用。 - 分支预测:CPU会预测条件跳转的方向。如果循环条件在大部分时候都相同(例如通常为
true,只在最后一次为false),CPU预测的成功率就高,性能就好。这与使用哪种循环语句关系不大,更多取决于循环体内的逻辑和条件本身。
因此,在选择while还是do...while时,几乎永远不应该将性能作为首要考量因素。代码的正确性、清晰度和与业务逻辑的契合度,才是最重要的。
注意事项:有一种情况需要警惕,那就是在循环体内使用
continue语句。在while循环中,continue会跳转到条件判断处;在do...while循环中,continue会跳转到循环体末尾,即条件判断处。这看起来一样,但如果你在do...while循环的条件判断部分依赖循环体内continue之前更新的变量,逻辑就可能出错。通常建议在do...while循环中谨慎使用continue。
5. 常见误区、调试技巧与最佳实践
即使理解了原理,在实际编码和调试中,围绕这两种循环依然有不少坑。下面是我总结的一些常见问题和应对策略。
5.1 典型错误与反模式
混淆执行次数导致逻辑错误这是最经典的错误。例如,需要至少执行一次的操作错误地使用了
while,并且忘记了在循环外进行初始化执行。// 错误示例:想确保用户至少输入一次,但用了while std::string input; while (input.empty()) { // 第一轮判断时,input未初始化,行为未定义! std::cout << "请输入内容: "; std::getline(std::cin, input); }未初始化的
input进行.empty()判断是未定义行为。正确做法是使用do...while,或者在while循环前先获取一次输入。do...while 结尾遗漏分号语法错误,但很常见。编译器会报错,提示缺少分号。
do { // ... } while (condition) // 错误!这里必须有分号在循环体内忘记改变循环条件,导致无限循环这对两种循环都是致命的。
int i = 0; while (i < 10) { std::cout << "Stuck here forever!\n"; // 忘记了 i++,条件永远为真,无限循环 }使用浮点数作为循环条件由于浮点数的精度问题,用浮点数做循环控制变量可能导致意想不到的循环次数。
double d = 0.0; while (d != 1.0) { // 危险!由于精度误差,d可能永远无法精确等于1.0 d += 0.1; // ... }应改为判断范围,如
while (d < 1.0)或while (std::abs(d - 1.0) > 1e-9)。
5.2 调试循环问题的实用技巧
当循环行为不符合预期时,可以尝试以下调试方法:
打印关键变量:在循环开始、每次迭代后、循环结束后,打印循环控制变量和关键状态变量的值。这是最直接有效的方法。
int i = 0; std::cout << "[DEBUG] 进入循环前,i=" << i << std::endl; while (i < 5) { std::cout << "[DEBUG] 循环体内,i=" << i << std::endl; // ... 一些操作 i++; std::cout << "[DEBUG] 迭代后,i=" << i << std::endl; } std::cout << "[DEBUG] 循环结束,i=" << i << std::endl;使用调试器的断点和监视:在IDE(如Visual Studio、CLion、VSCode配合GDB/LLDB)中,在循环条件判断处和循环体内设置断点。添加对循环变量的监视,然后单步执行,观察变量如何变化,以及程序流程是否符合预期。
简化与隔离:如果循环逻辑复杂,尝试创建一个最小的、可复现问题的测试程序。移除无关的业务代码,只保留循环骨架和核心逻辑。这能帮你快速定位问题是出在循环结构本身,还是内部的复杂计算上。
边界条件测试:专门测试循环的边界情况。对于
while,测试“0次迭代”的情况(初始条件为假)。对于do...while,测试“仅1次迭代”的情况(执行一次后条件为假)。这些地方最容易出错。
5.3 选择循环语句的最佳实践指南
根据多年的经验,我总结了一个简单的决策流程图来帮助选择:
首先问:这段代码是否必须至少执行一次?
- 是-> 优先考虑
do...while。它能最直观地表达“至少一次”的语义,代码通常更简洁。 - 否-> 进入下一步。
- 是-> 优先考虑
其次问:循环次数是否可能为零?
- 是,且零次是合法且常见情况-> 使用
while。这是它的主场,能安全地处理零次迭代。 - 否,或者零次情况需要特殊处理-> 可以再次权衡。有时用
while并在循环前做一次操作来处理“至少一次”的逻辑也不错,特别是当循环初始化比较复杂时。
- 是,且零次是合法且常见情况-> 使用
最后,考虑代码可读性:
- 如果循环的“继续条件”在逻辑上是对上一次迭代结果的判断(例如“只要用户输入无效就继续问”),
do...while的语法while(condition)读起来非常自然。 - 如果循环的“继续条件”是一个独立的状态检查(例如“只要文件还有内容就继续读”),
while循环更清晰。 - 当两者皆可时,选择让你和你的团队成员一眼就能看懂意图的那一个。清晰的意图比微妙的语法差异更重要。
- 如果循环的“继续条件”在逻辑上是对上一次迭代结果的判断(例如“只要用户输入无效就继续问”),
此外,还有一些通用建议:
- 保持循环条件简单:复杂的条件判断应该提取到布尔变量或函数中,放在
while或do...while的括号里。例如while (isDataValid() && !isConnectionLost())比一长串逻辑判断更易读。 - 避免在循环条件内写有副作用的表达式:例如
while ((ch = getchar()) != EOF)这种写法虽然常见,但把赋值和判断混在一起降低了可读性,也容易出错。在某些编码规范中是被禁止的。 - 明确循环变量的生命周期:如果循环控制变量只在循环内使用,尽量在循环内声明(C++99后
for循环可以,while通常需要在外部声明)。这能限制其作用域,避免命名污染。
6. 从语言设计角度看循环的演进与替代方案
C++从C继承了while和do...while,但它们并不是处理循环的唯一方式。了解这些背景和替代方案,能让我们在更广阔的视野下理解流程控制。
6.1 for循环:结构化循环的典范
在对比while和do...while时,无法绕开for循环。for循环可以看作是while循环的一种语法糖,它将循环变量的初始化、条件判断和更新三个部分集中到了同一行,结构非常清晰。
for (int i = 0; i < 10; ++i) { // 循环体 } // 等价于 int i = 0; // 初始化 while (i < 10) { // 条件判断 // 循环体 ++i; // 更新 }何时用for,何时用while?
for循环:当循环次数明确,或者循环变量有清晰的初始化、判断和更新步骤时。遍历数组、容器是最典型的场景。while循环:当循环次数不确定,终止条件依赖于循环体内发生的某个事件(如读取文件直到末尾、等待某个信号)时。do...while循环:当循环体必须至少执行一次,且终止条件在循环体执行后才可知时。
C++11引入的范围for循环(for (auto& item : container))进一步简化了遍历操作,但它底层通常被编译器转换为基于迭代器的for或while循环。
6.2 基于范围的for循环 (Range-based for loop)
这是C++11带来的重大语法便利,用于遍历一个序列(如数组、std::vector、std::list等所有提供了begin()和end()方法的容器)。
std::vector<int> vec = {1, 2, 3, 4, 5}; for (int value : vec) { std::cout << value << " "; } // 输出: 1 2 3 4 5它完全隐藏了迭代器的细节,代码简洁不易错。在只需要顺序访问容器内所有元素的场景下,应优先使用范围for循环。它的出现,使得很多传统的while或for循环遍历变得不再必要。
6.3 算法库中的循环替代
现代C++鼓励使用“算法优于裸循环”的理念。标准模板库(STL)中的很多算法函数,如std::for_each,std::transform,std::copy_if等,可以替代手写的循环,使意图更明确,且可能更高效。
std::vector<int> src = {1, 2, 3, 4, 5}; std::vector<int> dst; // 传统while/for循环方式:复制大于2的元素 for (int num : src) { if (num > 2) { dst.push_back(num); } } // 使用STL算法方式 std::copy_if(src.begin(), src.end(), std::back_inserter(dst), [](int n) { return n > 2; });算法版本虽然语法稍复杂,但它明确表达了“复制-如果”这个操作,并且可能利用编译器的优化和并行算法库(如C++17的std::execution::par)获得性能提升。
6.4 循环控制语句:break, continue, goto
在循环内部,我们还有几个控制流语句:
break:立即终止整个循环,跳出到循环后面的语句。continue:跳过本次循环剩余的语句,直接开始下一次迭代(在while和do...while中跳转到条件判断处,在for中跳转到更新表达式处)。goto:无条件跳转到函数内某个标签处。强烈不建议在循环中使用goto来跳出多层嵌套循环,这会使程序流程极其难以跟踪。通常可以用break配合标志变量,或者将内层循环封装成函数并返回来替代。
理解这些控制语句,能让你更灵活地处理循环中的特殊情况。但切记,过度使用break和continue(尤其是多个)会破坏循环的单入口单出口结构,降低代码可读性。如果循环内部逻辑变得很复杂,经常需要提前退出,或许应该考虑重构这个循环,或者将部分逻辑提取成函数。
7. 综合案例:一个简单的猜数字游戏
最后,我们用一个完整的、可运行的小例子来综合运用while和do...while,并展示如何做出合适的选择。
游戏规则:程序随机生成一个1-100的数字,玩家有7次机会猜测。每次猜测后,程序会提示“太大了”、“太小了”或“猜对了”。如果7次内猜对,玩家获胜;否则,游戏失败。游戏结束后,询问玩家是否再玩一局。
#include <iostream> #include <cstdlib> // 用于 rand() 和 srand() #include <ctime> // 用于 time() int main() { // 初始化随机数种子 std::srand(static_cast<unsigned int>(std::time(nullptr))); char playAgain = 'y'; // 外层循环:控制是否开始新一局游戏 // 使用 do...while,因为至少要玩一局 do { // 生成目标数字 int targetNumber = std::rand() % 100 + 1; // 1 到 100 int remainingGuesses = 7; int playerGuess; bool hasWon = false; std::cout << "\n*** 新游戏开始!我已想好一个1-100之间的数字。你有7次机会。***\n"; // 内层循环:控制单局游戏中的猜测过程 // 使用 while,因为可能一次都没猜(直接退出?不,这里需要猜,但循环次数由剩余机会控制) // 更准确地说,这是一个“计数循环”,用for更合适,但用while演示条件控制。 while (remainingGuesses > 0 && !hasWon) { std::cout << "\n你还有 " << remainingGuesses << " 次机会。请输入你的猜测: "; std::cin >> playerGuess; // 处理输入失败(例如输入了非数字) if (std::cin.fail()) { std::cin.clear(); // 清除错误状态 std::cin.ignore(10000, '\n'); // 忽略错误输入 std::cout << "无效输入,请输入一个整数!\n"; continue; // 跳过本次循环的剩余部分,不扣除机会 } std::cin.ignore(10000, '\n'); // 忽略输入缓冲区中多余的字符(包括换行符) remainingGuesses--; if (playerGuess < targetNumber) { std::cout << "太小了!\n"; } else if (playerGuess > targetNumber) { std::cout << "太大了!\n"; } else { std::cout << "恭喜你!猜对了!数字就是 " << targetNumber << "。\n"; hasWon = true; } } // 内层循环结束,判断胜负 if (!hasWon) { std::cout << "很遗憾,机会用完了。正确的数字是 " << targetNumber << "。\n"; } // 询问是否再玩一局 // 这里使用 do...while 确保至少问一次,并且验证输入有效性 do { std::cout << "\n是否再玩一局?(y/n): "; std::cin >> playAgain; std::cin.ignore(10000, '\n'); // 清理缓冲区 // 转换为小写方便比较 playAgain = std::tolower(playAgain); } while (playAgain != 'y' && playAgain != 'n'); // 只要输入不是y或n,就继续问 } while (playAgain == 'y'); // 只要玩家输入'y',就开启新一局 std::cout << "\n游戏结束,谢谢游玩!\n"; return 0; }在这个案例中,我们看到了两种循环的典型应用:
- 最外层的
do...while:用于控制“是否开始新游戏”这个循环。因为程序至少要运行一局游戏,所以使用do...while。循环条件是playAgain == 'y'。 - 内层的
while:用于控制单局游戏中的猜测次数。循环条件是剩余次数>0 且 还未赢。这是一个典型的由两个条件控制的循环,使用while非常合适。注意,这里我们用了continue来处理无效输入,跳过本次循环的剩余部分但不退出循环。 - 内层的另一个
do...while:用于验证玩家是否输入有效的y/n选择。因为至少要问一次,并且需要反复询问直到得到有效输入,所以使用do...while。循环条件是输入既不是y也不是n。
通过这个案例,你可以清晰地看到,选择哪种循环取决于具体的业务逻辑需求,而不是个人习惯。正确的选择能让代码意图一目了然,结构更加健壮。