Skip to content

C# do-while 循环:先上车,后验票 ​

引言:「先干了再说」的循环 ​

while 循环像坐地铁——先验票(判断条件),再上车(执行循环体),票不合格连车都上不去。

do-while 则像某些景区的「先入园后补票」——先让你进去玩一圈(执行循环体),出来时再验票(判断条件),票合格就再放你进去玩一圈,不合格就到此为止。

这个看似微小的顺序差异,带来了一个关键特性:do-while 的循环体至少执行一次。正是这个特性,让它在「必须先做一次才知道要不要继续」的场景中无可替代——比如「先让用户输入密码,再判断对不对」,你总不能还没输入就判断吧?


一、基础语法与执行逻辑 ​

1.1 语法结构拆解 ​

csharp
do
{
    // 循环体:无条件先执行一次
    // 通常包含更新循环变量的语句
} while (条件表达式);   // ⚠️ 注意:这里有一个分号!新手最常漏

三个结构要点:

  1. do 后面直接跟循环体,没有任何条件判断——先执行;
  2. while (条件) 在循环体之后——执行完才判断;
  3. while (条件) 后面必须带分号——这是 do-while 独有的语法要求。

1.2 执行流程分步解析 ​

第 1 步:执行循环体(不判断任何条件)
第 2 步:判断 while 条件
         ├── true  → 回到第 1 步,再执行一轮
         └── false → 退出循环,继续执行后续代码

与 while 的流程对比:

while:     判断 ──→ true ──→ 执行 ──→ 判断 ──→ ...
             │
             └── false ──→ 退出(可能一次都不执行)

do-while:  执行 ──→ 判断 ──→ true ──→ 执行 ──→ ...
                   │
                   └── false ──→ 退出(保证至少执行了 1 次)

1.3 与 while 的核心差异对照表 ​

对比维度whiledo-while
判断时机先判断,后执行先执行,后判断
最少执行次数0 次(条件初始为 false 时)1 次(无条件保底)
生活类比先验票再上车先入园后补票
语法细节while (条件) { } 无分号} while (条件); 有分号
典型场景条件不满足就完全不用做必须先做一次再决定要不要继续

1.4 极简入门示例 ​

csharp
// 场景:打印 1 到 5
int i = 1;                  // ① 初始化循环变量

do
{
    Console.WriteLine($"当前数字:{i}");  // ② 先执行:打印当前值
    i++;                    // ③ 更新循环变量
} while (i <= 5);           // ④ 后判断:i 是否小于等于 5?是则回到 ②

// 输出:
// 当前数字:1
// 当前数字:2
// 当前数字:3
// 当前数字:4
// 当前数字:5

见证差异的关键实验——让条件一开始就是 false:

csharp
// while 版本:一次都不执行
int a = 100;
while (a <= 5)              // 100 <= 5 为 false
{
    Console.WriteLine($"while 执行了:{a}");   // 这行永远不会执行
}

// do-while 版本:照样执行 1 次
int b = 100;
do
{
    Console.WriteLine($"do-while 执行了:{b}"); // 输出:do-while 执行了:100
} while (b <= 5);           // 执行完才判断,虽然 false 但已经跑过一轮

Console.WriteLine("两个循环都结束了");

这就是「保底一次」的直观体现:不管条件多离谱,do-while 的循环体都会先跑一圈。


二、进阶技术细节 ​

2.1 IL 编译层面的执行逻辑 ​

do-while 在 C# 编译后会被翻译成 IL(Intermediate Language,中间语言——C# 代码编译后的低级指令集)。理解 IL 层面的跳转逻辑,能帮你彻底看清「先执行后判断」的本质。

以下 C# 代码:

csharp
int i = 1;
do
{
    Console.WriteLine(i);
    i++;
} while (i <= 3);

编译后的 IL 指令(简化版)大致是:

cil
// IL_0000:  ldc.i4.1        // 把常量 1 压入栈
// IL_0001:  stloc.0         // 存入局部变量 i(即 i = 1)

LOOP_START:                  // 循环体入口标签(注意:入口在判断之前!)
// IL_0002:  ldloc.0         // 加载 i
// IL_0003:  call WriteLine  // 调用 Console.WriteLine(i)
// IL_0008:  ldloc.0         // 加载 i
// IL_0009:  ldc.i4.1        // 加载常量 1
// IL_000a:  add             // 相加
// IL_000b:  stloc.0         // 存回 i(即 i++)

// IL_000c:  ldloc.0         // 加载 i(开始判断条件)
// IL_000d:  ldc.i4.3        // 加载常量 3
// IL_000e:  ble.s LOOP_START// 若 i <= 3 则跳转回 LOOP_START
                             // 否则顺序往下,退出循环

关键观察:条件判断指令 ble.s(branch if less or equal,小于等于则跳转)位于循环体指令的末尾,循环体入口标签 LOOP_START 之前没有任何判断——程序流天然先落入循环体,执行完后才用一条条件跳转指令决定「回不回头」。

对比 while 循环的 IL:它会先在循环体之前生成一条 br 指令跳到末尾的判断处,判断通过再跳回循环体入口——多了一次初始跳转。这正是「先判断后执行」在机器层面的体现,也解释了为什么 while 可以执行 0 次而 do-while 保底 1 次。

2.2 边界情况一:循环体恰好只执行 1 次 ​

当条件在首次判断时就是 false,do-while 恰好执行 1 次——这不是 bug,是它的设计行为。

csharp
// 场景:无论用户输入是否合法,至少询问一次
string input;
do
{
    Console.Write("请输入 y 继续,其他键退出:");
    input = Console.ReadLine();     // 先拿到输入
} while (input == "y");             // 再判断要不要继续问

Console.WriteLine("程序结束");

// 运行情况:用户第一次输入 n
// 请输入 y 继续,其他键退出:n
// 程序结束
// ——循环体执行了且仅执行了 1 次,行为完全正确

2.3 边界情况二:无限循环的正确写法与风险规避 ​

正确的无限循环写法(配合内部退出机制):

csharp
// ✅ 服务器心跳检测:无限循环 + 显式退出条件
int heartbeat = 0;
do
{
    heartbeat++;
    Console.WriteLine($"第 {heartbeat} 次心跳检测:服务正常");

    // 模拟:检测到第 5 次时收到停机指令
    if (heartbeat >= 5)
    {
        Console.WriteLine("收到停机指令,退出循环");
        break;                      // 必须有退出机制!
    }
} while (true);                     // 条件恒真,循环本身永不退出

危险的无限循环(意外造成,程序卡死):

csharp
// ❌ 错误:忘记更新循环变量,条件永远为 true
int i = 1;
do
{
    Console.WriteLine(i);
    // i++;                        // 漏掉了!i 永远是 1
} while (i <= 5);                  // 1 <= 5 永远成立 → 死循环

// ❌ 错误:更新方向与条件矛盾
int j = 1;
do
{
    j++;                           // j 越来越大
} while (j > 0);                   // 永远 > 0 → 死循环

风险规避技巧(开发阶段加保险丝):

csharp
int i = 1;
int safety = 0;                    // 保险丝计数器
do
{
    if (++safety > 10000)          // 超过 1 万次必定有问题
    {
        throw new InvalidOperationException("疑似死循环!");
    }
    Console.WriteLine(i);
    i++;
} while (i <= 5);

2.4 跳转语句对 do-while 流程的影响 ​

break:立即退出整个循环

csharp
int i = 1;
do
{
    if (i == 4)
    {
        Console.WriteLine("遇到 4,提前收工");
        break;                  // 不再判断 while 条件,直接跳出
    }
    Console.WriteLine($"处理:{i}");
    i++;
} while (i <= 10);

// 输出:
// 处理:1
// 处理:2
// 处理:3
// 遇到 4,提前收工

continue:跳过本轮剩余代码,直接去判断 while 条件

csharp
// ⚠️ 高危:continue 会直接跳到 while 条件判断,
//          如果更新语句在 continue 之后,就永远执行不到 → 死循环!
int n = 0;
do
{
    n++;                        // ✅ 先更新,再继续,安全
    if (n % 2 == 0)
    {
        continue;               // 偶数:跳过输出,直接判断 n <= 10
    }
    Console.WriteLine($"奇数:{n}");
} while (n <= 10);
// 输出:奇数:1、3、5、7、9、11(注意:n=11 时输出后才退出)

return:整个方法直接结束

csharp
static void ProcessData()
{
    int i = 1;
    do
    {
        if (i == 3)
        {
            Console.WriteLine("发现致命数据,方法整体返回");
            return;             // 连方法都结束了,循环自然终止
        }
        Console.WriteLine($"处理数据 {i}");
        i++;
    } while (i <= 5);

    Console.WriteLine("这行不会执行——方法已提前返回");
}

三者对比记忆:

跳转语句退出范围类比
continue跳过本轮,直接去看「票」这一轮白玩了,去验票看能不能再玩
break退出整个循环不玩了,出园
return退出整个方法不但出园,直接回家了

三、常见误区解析 ​

3.1 误区1:漏写 while 条件后的分号 ​

错误代码:

csharp
int i = 1;
do
{
    Console.WriteLine(i);
    i++;
} while (i <= 5)    // ❌ 缺少分号!

问题分析:编译器报错 CS1002: 应输入 ;。这是 do-while 特有的语法要求——while 循环的条件后不能加分号,do-while 的条件后必须加分号,两者恰好相反,新手极易混淆。记忆技巧:do-while 的 while (条件); 是一条语句的结尾,所以要有分号;while 循环的 while (条件) 是代码块的开头,后面紧跟 {},不需要分号。

修正代码:

csharp
int i = 1;
do
{
    Console.WriteLine(i);
    i++;
} while (i <= 5);   // ✅ 补上分号

3.2 误区2:循环条件永真导致程序卡死 ​

错误代码:

csharp
// 原意:用户输入 q 退出,其他输入继续
string input = "";
do
{
    Console.Write("请输入指令(q 退出):");
    Console.ReadLine();             // ❌ 读了输入但没赋值给 input!
} while (input != "q");             // input 永远是 "",条件永真

问题分析:程序看似在响应输入,实际 ReadLine() 的返回值被丢弃了,input 从未改变——用户输入什么都没用,程序永远卡在循环里。

修正代码:

csharp
string input;
do
{
    Console.Write("请输入指令(q 退出):");
    input = Console.ReadLine();     // ✅ 接收返回值,更新循环变量
    Console.WriteLine($"执行指令:{input}");
} while (input != "q");             // 输入 q 时条件为 false,正常退出

3.3 误区3:do-while 与 while 混用导致的逻辑 bug ​

错误代码:

csharp
// 需求:最多询问用户 3 次年龄,输入合法(0-150)则停止
// 新手误用 while:
int age = -1;
int attempts = 0;
while (age < 0 || age > 150)
{
    if (attempts >= 3) break;       // 次数控制
    Console.Write("请输入年龄:");
    age = int.Parse(Console.ReadLine());
    attempts++;
}

这段代码碰巧能工作(因为 age 初始化为 -1 让条件首次为 true),但它隐藏着一个逻辑陷阱:如果哪天有人把 age 初始化为 25(比如从缓存读取了默认值),循环就一次都不执行,用户连输入的机会都没有——校验逻辑静默失效。

修正代码(do-while 天然匹配「先询问后校验」的语义):

csharp
int age;
int attempts = 0;
do
{
    Console.Write("请输入年龄(0-150):");
    age = int.Parse(Console.ReadLine());  // 先拿到输入
    attempts++;
} while ((age < 0 || age > 150) && attempts < 3);  // 再判断要不要重问

if (age >= 0 && age <= 150)
    Console.WriteLine($"年龄 {age} 校验通过");
else
    Console.WriteLine("超过尝试次数,校验失败");

do-while 让「至少问一次」成为语法层面的保证,不再依赖 age = -1 这种「恰好能让条件成立」的初始化技巧——代码意图更清晰,也不会被默认值变更坑到。


四、实战应用场景 ​

4.1 场景一:输入验证(do-while 的主场) ​

需求:菜单程序,显示菜单 → 接收用户选择 → 执行对应功能 → 询问是否继续。

csharp
string choice;
do
{
    // ① 必须先显示一次菜单(这就是「至少执行一次」的语义)
    Console.WriteLine("\n===== 主菜单 =====");
    Console.WriteLine("1. 查询余额");
    Console.WriteLine("2. 转账");
    Console.WriteLine("3. 退出");
    Console.Write("请选择:");
    choice = Console.ReadLine();

    switch (choice)
    {
        case "1":
            Console.WriteLine("当前余额:¥8,888.00");
            break;
        case "2":
            Console.WriteLine("转账功能处理中...");
            break;
        case "3":
            Console.WriteLine("感谢使用,再见!");
            break;
        default:
            Console.WriteLine("无效选项,请重新选择");
            break;
    }
} while (choice != "3");    // ② 选择"退出"以外的选项都继续循环

对比:用 while 实现同样的需求

csharp
// while 版本:必须先「伪造」一次输入或在循环外重复一遍菜单代码
string choice = "";                 // 技巧1:给个假初始值让条件成立
while (choice != "3")
{
    // 菜单代码与上面完全相同...
    choice = Console.ReadLine();
    // ...
}

// 或者另一种 while 写法:循环外先执行一遍 → 代码重复
Console.WriteLine("===== 主菜单 =====");   // 重复代码
choice = Console.ReadLine();
while (choice != "3")
{
    Console.WriteLine("===== 主菜单 =====");   // 又写一遍
    // ...
}

结论:while 要么靠「假初始值」绕过首次判断(语义不自然),要么把菜单代码写两遍(冗余)。do-while 一个结构完美解决,零冗余、语义直白。

4.2 场景二:批量数据处理(分页拉取) ​

需求:调用接口分页拉取数据,每页最多 100 条,拉到「不足一页」说明数据拉完了。

csharp
int pageNumber = 0;
int fetchedCount;
int totalCount = 0;

do
{
    pageNumber++;
    // 模拟接口调用:第 1 页 100 条,第 2 页 100 条,第 3 页只有 35 条
    fetchedCount = pageNumber switch
    {
        1 => 100,
        2 => 100,
        3 => 35,
        _ => 0
    };

    totalCount += fetchedCount;
    Console.WriteLine($"已拉取第 {pageNumber} 页,本页 {fetchedCount} 条,累计 {totalCount} 条");
} while (fetchedCount == 100);  // 拉满一整页,说明可能还有下一页

Console.WriteLine($"数据拉取完毕,共 {totalCount} 条");
// 输出:
// 已拉取第 1 页,本页 100 条,累计 100 条
// 已拉取第 2 页,本页 100 条,累计 200 条
// 已拉取第 3 页,本页 35 条,累计 235 条
// 数据拉取完毕,共 235 条

为什么 do-while 不可替代:必须先拉一次才知道这一页有多少条、要不要拉下一页——「先取样再决策」正是 do-while 的核心语义。用 while 就得在循环外先手动拉一次第 1 页,逻辑被拆成两截。

4.3 场景三:状态机循环(游戏回合) ​

需求:回合制游戏主循环,每回合:玩家行动 → 敌人行动 → 检查是否有角色死亡。

csharp
int playerHp = 30;              // 玩家血量
int enemyHp = 25;               // 敌人血量
int round = 0;
bool gameOver;

do
{
    round++;
    Console.WriteLine($"\n--- 第 {round} 回合 ---");

    // 状态转移1:玩家攻击(模拟造成 8-12 点伤害)
    int playerDamage = 8 + round;
    enemyHp -= playerDamage;
    Console.WriteLine($"玩家攻击,造成 {playerDamage} 伤害,敌人剩余 {Math.Max(enemyHp, 0)} HP");

    // 状态转移2:敌人没死才反击
    if (enemyHp > 0)
    {
        int enemyDamage = 5 + round;
        playerHp -= enemyDamage;
        Console.WriteLine($"敌人反击,造成 {enemyDamage} 伤害,玩家剩余 {Math.Max(playerHp, 0)} HP");
    }

    // 计算新状态:游戏是否结束
    gameOver = playerHp <= 0 || enemyHp <= 0;
} while (!gameOver);            // 回合结束才检查是否分出胜负

// 循环结束后宣布结果
Console.WriteLine(playerHp > 0 ? "\n玩家获胜!" : "\n敌人获胜!");

// 输出示例:
// --- 第 1 回合 ---
// 玩家攻击,造成 9 伤害,敌人剩余 16 HP
// 敌人反击,造成 6 伤害,玩家剩余 24 HP
// --- 第 2 回合 ---
// 玩家攻击,造成 10 伤害,敌人剩余 6 HP
// 敌人反击,造成 7 伤害,玩家剩余 17 HP
// --- 第 3 回合 ---
// 玩家攻击,造成 11 伤害,敌人剩余 0 HP
// 玩家获胜!

为什么选 do-while:状态机(State Machine,一种「根据当前状态决定下一步动作」的编程模型)的核心节奏是「先推进一个回合,再检查终止状态」——至少打 1 回合才有可能分出胜负,总不能游戏还没开始就判断谁赢了。


五、知识点自测题 ​

题目1(基础概念) ​

以下代码的输出是什么?

csharp
int x = 10;
do
{
    Console.WriteLine(x);
    x--;
} while (x > 20);
查看答案与解析

输出:10

解析:do-while 先执行后判断。循环体先打印 10,然后 x 减到 9;此时才判断 9 > 20,结果为 false,退出循环。虽然条件从一开始就不成立,但循环体已经保底执行了 1 次——这正是 do-while 与 while 的本质区别(换成 while 则什么都不输出)。

题目2(语法细节) ​

下面代码能否编译通过?为什么?

csharp
int i = 0;
do
    Console.WriteLine(i);
    i++;
while (i < 3);
查看答案与解析

不能编译通过。

解析:do 后面如果不加大括号 {},循环体只能是一条语句。这里 Console.WriteLine(i); 是循环体,而 i++; 被挡在了循环体外——编译器在 i++; 处就期望看到 while 关键字,结果没看到,报错。

修正:加上大括号把两条语句包起来:

csharp
int i = 0;
do
{
    Console.WriteLine(i);
    i++;
} while (i < 3);

经验法则:即使循环体只有一条语句,也建议始终写大括号,避免后续添加语句时踩坑。

题目3(死循环排查) ​

以下代码想打印 1、3、5,但实际死循环了,问题在哪?

csharp
int n = 1;
do
{
    if (n % 2 == 0)
    {
        continue;
    }
    Console.WriteLine(n);
    n++;
} while (n <= 5);
查看答案与解析

问题:continue 跳过了 n++,导致 n 卡在 2 永远不变。

执行轨迹:n=1 输出并自增到 2 → n=2 是偶数,continue 直接跳到条件判断 2 <= 5 为 true → 回到循环体,n 还是 2 → 又 continue……无限循环。

修正:先更新变量,再执行 continue:

csharp
int n = 0;
do
{
    n++;                        // 先自增,保证 continue 不会跳过它
    if (n % 2 == 0)
    {
        continue;
    }
    Console.WriteLine(n);
} while (n <= 5);
// 输出:1 3 5

题目4(场景选择) ​

以下哪个场景最适合用 do-while?

A. 遍历一个可能为空的数组,打印所有元素 B. 提示用户输入密码,直到输入正确为止 C. 计算 1 到 100 的累加和 D. 扫描整个目录查找指定文件

查看答案与解析

答案:B

解析:判断标准是「是否必须先做一次,才能决定要不要继续」。

  • B:必须先让用户输入一次密码,才有密码可校验 → 典型的「先执行后判断」,do-while 主场。
  • A:数组可能为空,一次都不应该执行 → 用 foreach 或 while。
  • C:次数固定的累加 → for 循环最直观。
  • D:目录可能为空目录 → while/foreach 更合适。

六、核心知识点总结 ​

6.1 do-while 核心特征速查 ​

特征说明
先执行后判断循环体保底执行 1 次,这是与 while 的唯一本质差异
分号必写} while (条件); 结尾的分号是语法强制
IL 层面条件跳转指令在循环体末尾,程序流天然先落入循环体
continue 高危会跳过更新语句直奔条件判断,必须先更新再 continue
无限循环do { ... } while (true); 合法,但必须配 break/return 退出机制

6.2 循环选型决策 ​

必须先做一次才知道要不要继续     → do-while
条件不满足就一次都不做           → while
已知循环次数 / 需要索引          → for
只读遍历集合                     → foreach

6.3 安全检查清单 ​

写完 do-while 循环后,检查:

  • [ ] while (条件) 后面的分号写了吗?
  • [ ] 循环体内是否有更新循环变量的语句?
  • [ ] 如果用了 continue,更新语句是否在它之前?
  • [ ] 「保底执行 1 次」是否符合业务预期(条件首次为 false 也会执行一次)?
  • [ ] 条件是否最终会变为 false(或有 break/return 兜底)?

结语 ​

do-while 是循环家族里的「行动派」——不管三七二十一,先干一轮再说。这个特性让它在 while 和 for 的阴影下显得低调,但在「先执行后决策」的场景中(输入验证、分页拉取、状态机推进),它是语义最贴切、代码最简洁的选择。

掌握 do-while 的关键心法:

  • 语义上:把它读作「做……直到条件不满足」,而不是「当条件满足时做」;
  • 语法上:记住那个特立独行的分号;
  • 安全上:警惕 continue 跳过更新语句引发的死循环。

下次当你写出「先给个假初始值骗过 while 的首次判断」这种代码时,就该意识到:这是 do-while 在向你招手。