只要条件保持为真,while 循环就会重复执行它的循环体。当你事先不知道需要迭代多少次时就用它:一直执行,直到余额达到目标、队列为空,或者输入读完为止。
输出:
Year 1: 1070.00
Year 2: 1144.90
Year 3: 1225.04
Year 4: 1310.80
Year 5: 1402.55
Year 6: 1500.73
Goal reached after 6 years
条件会在每次迭代之前检查,包括第一次。如果 savings 一开始就已经达到目标,循环体就一次都不会执行,程序会打印“after 0 years”。循环依赖于循环体去改变条件读取的某个值;这里 savings 每年都在增长,直到 savings < goal 变为 false。
do while:至少执行一次
do while 循环把检查移到了末尾,所以循环体至少会执行一次:
输出:
Attempt 1: got 7
Attempt 2: got 4
Attempt 3: got 1
Done after 3 attempts
code 的第一个值只有在循环体执行过一次之后才存在,这正是适合用 do while 的场景。有两个语法细节经常让人出错:
while (code != 1)后面的分号是必需的。漏掉它会导致编译错误。- 条件中用到的变量必须在
do之前声明。在大括号内声明的变量,到条件执行时已经超出了作用域,所以do { int code = ...; } while (code != 1);无法编译。
同样的循环如果用 while 来写,要么在循环之前重复调用一次,要么设一个假的初始值来强制执行第一遍。do while 直接表达了“先试一次,再决定要不要再试”。
读取输入直到遇到哨兵值
逐行读取输入,直到遇到停止词或输入结束,是 while 循环的经典用法。没有更多输入时,Console.ReadLine() 会返回 null,所以两种情况都要检查:
输出:
skipping "abc": not a number
3 amounts, total 49.50
(line = Console.ReadLine()) != null 在一个表达式中完成赋值下一行并检查它;赋值两边的括号是必需的,因为 != 的优先级比 = 高。循环在 done 处停止,所以后面的 99 永远不会被读取。decimal.TryParse 拒绝 abc 时不会抛出异常,continue 会跳到下一行。如果没有 null 检查,读到输入末尾之后,程序会永远把 null 传给 TryParse,因为 ReadLine 会一直返回 null。
do while 适合“提示并验证”的模式,因为提示至少要出现一次:
输出:
Enter your age:
"-4" is not a valid age
Enter your age:
"fifteen" is not a valid age
Enter your age:
Age accepted: 15
提示语放在循环内部,所以每次回答被拒绝后都会重复出现;age 在 do 之前声明,所以循环之后的代码可以使用它。如果是用户在键盘上输入,每个回答都会显示在对应提示的下方;而像这里这样使用重定向输入时,只会显示程序自己的输出。
while (true) 配合 break
有时最自然的退出点在循环体的中间:在做完一部分工作之后、其余工作之前。这时把循环写成 while (true),然后用 break 离开:
输出:
processing: resize photo
processing: send email
stop signal received
1 job(s) left in the queue
Dequeue 从队列的前端取出下一个任务,循环一看到停止标记就立即离开,所以最后一个任务仍然留在队列中。当退出条件清晰并且靠近循环体顶部时,while (true) 并不算坏味道。但如果一个很长的循环体中散布着好几个 break,它就成了坏味道;这时用一个有名字的 bool 条件,或者用带 return 的方法,会更容易读懂。break 和 continue 那一页介绍了如何跳出嵌套循环。
永远不会结束的循环
意外的无限循环通常有三种原因。
循环体从不改变条件。 计数器从来没有递增,或者递增错了变量:
int i = 0;
while (i < 5)
{
Console.WriteLine(i); // i++ forgotten: prints 0 forever
}
条件后面多了一个分号。 这个分号就是整个循环体,下面的代码块会在循环结束后执行(而循环永远不会结束):
while (retries < 3); // warning CS0642: Possible mistaken empty statement
{
retries++;
}
条件越过了目标值。 当变量按步长变化时,用 != 判断可能会跳过停止值。while (x != 10) x += 3; 会依次得到 0、3、6、9、12,直接越过 10(只有在 int 溢出并回绕、几十亿步之后,才会碰巧落在 10 上)。只要步长不是 1,就用 < 或 > 代替 != 来判断。double 值也一样,它们很少能精确命中目标:while (x != 1.0) x += 0.1; 永远不会结束。
如何在 while、do while 和 for 之间选择
| 场景 | 循环 |
|---|---|
| 迭代次数已知,或需要索引 | for |
| 遍历集合中的每个元素 | foreach |
| 条件成立时重复,可能一次都不执行 | while |
| 循环体必须先执行一次,条件才有意义 | do while |
| 退出判断在循环体中间 | while (true) 配合 break |
任何 for 循环都可以改写成 while 循环,反之亦然;选择哪一种,取决于哪种写法能把循环的逻辑放在读者期望的位置。当计数器、条件和步进都存在时,for 把它们放在同一行。当循环由循环体内部变化的状态驱动时,用 while 就能清楚地表达这一点。
常见问题
C# 中 while 和 do while 有什么区别?
while 循环在每次迭代之前检查条件,所以循环体可能一次都不执行。do { } while (condition); 循环在每次迭代之后检查条件,所以循环体至少会执行一次。当第一遍执行才产生条件要检查的值时(比如先读取输入再验证),就用 do while。
C# 中 do while 循环的语法是什么?
do { body } while (condition);。注意右括号后面的分号:它是必需的,漏掉会导致编译错误。条件可以使用在循环体中赋值的变量,但这些变量必须在 do 之前声明,因为循环体的大括号是一个独立的作用域。
C# 中 while (true) 怎么写?
while (true) { ... } 会一直循环,直到循环体内部通过 break、return 或异常离开。当退出判断应该放在循环体中间时,这是最自然的写法,比如读取一行,遇到空行就停止。
在 C# 中如何读取输入,直到用户输入某个词或输入结束?
对 Console.ReadLine() 进行循环,它在输入结束时返回 null:string line; while ((line = Console.ReadLine()) != null && line != "done") { ... }。条件里的赋值一步完成读取下一行并检查它。
为什么我的 C# while 循环一直不结束?
通常是循环体从来没有改变条件所检查的东西(忘了写 i++),或者条件后面紧跟了一个分号:while (count < 10); 是一个循环体为空的循环,会永远空转。对于第二种情况,编译器会给出警告 CS0642,Possible mistaken empty statement。