74

我经常使用这种代码模式:

while(true) {

    //do something

    if(<some condition>) {
        break;
    }

}   

另一位程序员告诉我,这是不好的做法,我应该用更标准的替换它:

while(!<some condition>) {

    //do something

}   

他的理由是,您可能太容易“忘记休息”并陷入无休止的循环。我告诉他,在第二个例子中,你可以很容易地放入一个永远不会返回 true 的条件,所以很容易有一个无限循环,所以两者都是同样有效的做法。

此外,我通常更喜欢前者,因为当您有多个断点(即退出循环的多个条件)时,它使代码更易于阅读。

任何人都可以通过为一方或另一方添加证据来丰富这一论点吗?

4

22 回答 22

58

这两个示例之间存在差异。第一个将每次至少执行一次“做某事”,即使该语句永远不会为真。当语句评估为真时,第二个只会“做某事”。

我认为您正在寻找的是一个 do-while 循环。我 100% 同意这while (true)不是一个好主意,因为它很难维护这段代码,而且你逃避循环的方式非常goto古怪,这被认为是不好的做法。

尝试:

do {
  //do something
} while (!something);

检查您的个人语言文档以了解确切的语法。但是看看这段代码,它基本上做了do中的事情,然后检查while部分看它是否应该再做一次。

于 2008-12-24T00:37:10.977 回答
32

引用过去著名的开发者华兹华斯的话:

......
事实上,我们自己注定要落入的
监狱,没有监狱;因此,对我来说,在各种情绪中,被束缚 在十四行诗的贫瘠土地上是一种
消遣; 很高兴有些灵魂(他们的需要必须如此) 感受到了太多自由的重量, 应该在那里找到短暂的慰藉,正如我所发现的那样。



华兹华斯接受十四行诗的严格要求,作为一个解放框架,而不是一件紧身衣。我建议“结构化编程”的核心是放弃构建任意复杂流程图的自由,转而易于理解。

我完全同意,有时提前退出是表达行动的最简单方式。然而,我的经验是,当我强迫自己使用尽可能简单的控制结构(并真正考虑在这些约束内进行设计)时,我通常会发现结果是更简单、更清晰的代码。缺点是

while (true) {
    action0;
    if (test0) break;
    action1;
}

是很容易让action0action1变得越来越大的代码块,或者添加“只是一个”测试-中断-动作序列,直到变得难以指向特定的行并回答问题,“我需要什么条件现在知道坚持吗?” 因此,在不为其他程序员制定规则的情况下,我尽量避免while (true) {...}在自己的代码中使用这种习惯用法。

于 2008-12-24T02:19:46.557 回答
25

当您可以在表单中编写代码时

while (condition) { ... }

或者

while (!condition) { ... }

正文中没有退出(break、、continuegoto,这种形式是首选,因为有人可以通过查看标题来阅读代码并理解终止条件。那挺好的。

但是很多循环不适合这个模型,中间有明确退出的无限循环是一个光荣的模型。(带 的循环continue通常比带 的循环更难理解break。)如果你想引用一些证据或权威,看看 Don Knuth 的著名论文《使用 Goto 语句进行结构化编程》;你会找到所有你想要的例子、论据和解释。

一个小成语:把while (true) { ... }你写成一个老 Pascal 程序员,或者现在可能是一个 Java 程序员。如果您使用 C 或 C++ 编写,首选的习惯用法是

for (;;) { ... }

这样做没有充分的理由,但你应该这样写,因为这是 C 程序员期望看到的方式。

于 2008-12-24T01:21:31.340 回答
13

我更喜欢

while(!<some condition>) {
    //do something
}

但我认为这更多的是可读性问题,而不是“忘记休息”的可能性。我认为忘记break是一个相当弱的论点,因为那将是一个错误,您会立即找到并修复它。

我反对使用 abreak来摆脱无限循环的论点是,您实际上是将break语句用作goto. 我并不反对使用goto(如果语言支持它,这是公平的游戏),但如果有更易读的替代方案,我会尝试替换它。

在很多break点的情况下,我会将它们替换为

while( !<some condition> ||
       !<some other condition> ||
       !<something completely different> ) {
    //do something
}

以这种方式合并所有停止条件可以更容易地查看将结束此循环的内容。 break语句可以散布在各处,那是不可读的。

于 2008-12-24T00:49:37.130 回答
11

while (true) 如果你有很多语句并且你想在任何失败时停止,那么它可能是有意义的

 while (true) {
     if (!function1() ) return;   
     if (!function2() ) return;   
     if (!function3() ) return;   
     if (!function4() ) return;  
   }

好于

 while (!fail) {
     if (!fail) {
       fail = function1()
     }           
     if (!fail) {
       fail = function2()
     }  
     ........

   }
于 2008-12-24T01:17:24.017 回答
7

哈维尔对我之前的回答(引用华兹华斯的那个)做了一个有趣的评论:

我认为 while(true){} 是一个比 while(condition){} 更“纯粹”的结构。

我无法在 300 个字符中做出充分回应(对不起!)

在我的教学和指导中,我非正式地将“复杂性”定义为“我需要在脑海中掌握多少其余代码才能理解这一行或表达式?” 我要记住的东西越多,代码就越复杂。代码明确告诉我的越多,复杂性就越低。

因此,以降低复杂性为目标,让我以完整性和强度而不是纯粹性来回答哈维尔。

我想到了这个代码片段:

while (c1) {
    // p1
    a1;
    // p2
    ...
    // pz
    az;
}

同时表达两件事:

  1. 只要c1保持真实,(整个)主体就会重复,并且
  2. 在第 1 点,a1执行,c1保证保持

区别在于视角之一;a1其中第一个通常与整个循环的外部动态行为有关,而第二个对于理解内部的静态保证很有用,我在特别考虑时可以依靠它。当然,a1可能无效的净效应c1,要求我更加努力地思考我在第 2 点可以指望什么,等等。

让我们放置一个特定的(小)示例来考虑条件和第一个操作:

while (index < length(someString)) {
    // p1
    char c = someString.charAt(index++);
    // p2
    ...
}

“外部”问题是循环显然在内部做一些someString事情,只要index定位在someString. 这建立了一个期望,即我们将在体内indexsomeString体内进行修改(在我检查身体之前不知道的位置和方式),以便最终发生终止。这给了我思考身体的背景和期望。

“内部”问题是我们保证第 1 点之后的操作是合法的,因此在阅读第 2 点的代码时,我可以考虑使用我知道已合法获得的 char 值做了什么。(如果是空引用,我们甚至无法评估条件someString,但我也假设我们已经在这个示例的上下文中防范了这种情况!)

相反,以下形式的循环:

while (true) {
    // p1
    a1;
    // p2
    ...
}

在这两个问题上让我失望。在外层,我想知道这是否意味着我真的应该期望这个循环永远循环(例如操作系统的主事件调度循环),或者是否还有其他事情发生。这既没有给我一个明确的阅读正文的上下文,也没有对构成(不确定的)终止进展的期望。

在内部层面上,我绝对不能明确保证在第 1 点可能存在的任何情况true。当然,在任何地方都为真的条件是关于我们在程序中的任何点所能知道的最弱的陈述。在尝试思考动作完成什么时,了解动作的先决条件是非常有价值的信息!

所以,我认为这个成语比我上面描述的逻辑while (true) ...更不完整、更弱,因此也更复杂。while (c1) ...

于 2008-12-25T17:25:45.700 回答
6

问题是并非每个算法都坚持“while(cond){action}”模型。

一般循环模型是这样的:

loop_prepare
 loop:
  action_A
  if(cond) exit_loop
  action_B
  goto loop
after_loop_code

当没有 action_A 时,您可以将其替换为:

loop_prepare
  while(cond)
  action_B
after_loop_code

当没有 action_B 时,您可以将其替换为:

loop_prepare
  do action_A
  while(cond)
after_loop_code

一般情况下,action_A 会被执行 n 次,而 action_B 会被执行 (n-1) 次。

一个真实的例子是:打印一个用逗号分隔的表格的所有元素。我们希望所有 n 个元素都带有 (n-1) 个逗号。

你总是可以做一些技巧来坚持 while-loop 模型,但这总是会重复代码或检查两次相同的条件(对于每个循环)或添加一个新变量。因此,与 while-true-break 循环模型相比,您的效率和可读性总是较低。

(坏)“技巧”示例:添加变量和条件

loop_prepare
b=true // one more local variable : more complex code
 while(b): // one more condition on every loop : less efficient
  action_A
  if(cond) b=false // the real condition is here
  else action_B
after_loop_code

(坏)“技巧”的例子:重复代码。修改两个部分之一时,不能忘记重复的代码。

loop_prepare
action_A
 while(cond):
  action_B
  action_A
after_loop_code

注意:在最后一个示例中,程序员可以通过将“loop_prepare”与第一个“action_A”混合,将 action_B 与第二个 action_A 混合来混淆(愿意或不愿意)代码。所以他可以感觉到他没有这样做。

于 2014-05-05T09:52:02.203 回答
4

如果从循环中中断的方式有很多,或者在循环的顶部不能很容易地表达中断条件(例如,循环的内容需要运行到一半但另一半不能运行),第一种是可以的,在最后一次迭代中)。

但如果你能避免它,你应该这样做,因为编程应该以最明显的方式编写非常复杂的东西,同时还要正确和高效地实现特性。这就是为什么你的朋友在一般情况下是正确的。你朋友写循环结构的方式要明显得多(假设上一段中描述的条件不成立)。

于 2008-12-24T00:35:57.203 回答
3

Is WHILE TRUE...BREAK...END WHILE 一个好的设计中已经有一个基本相同的问题?. @Glomek 回答(在一篇被低估的帖子中):

有时它是非常好的设计。有关示例,请参阅Donald Knuth 的使用 Goto 语句进行结构化编程。我经常将这个基本思想用于运行“n 次半”的循环,尤其是读取/处理循环。但是,我通常尝试只有一个 break 语句。这使得在循环终止后更容易推断程序的状态。

稍后,我回复了相关的,也被严重低估的评论(部分原因是我第一次没有注意到格洛梅克,我想):

一篇引人入胜的文章是 1974 年 Knuth 的“使用 go to 语句进行结构化编程”(可在他的《文学编程》一书中找到,也可能在其他地方也可以找到)。除其他事项外,它还讨论了打破循环的受控方式,以及(不使用该术语)循环半语句。

Ada 还提供循环结构,包括

loopname:
    loop
        ...
        exit loopname when ...condition...;
        ...
    end loop loopname;

原始问题的代码在意图上与此类似。

引用的 SO 项目与此项目之间的一个区别是“最终休息”;这是一个单次循环,它使用 break 提前退出循环。关于这是否也是一种好的风格也存在疑问——我手头没有交叉参考。

于 2008-12-24T02:45:43.103 回答
2

有时您需要无限循环,例如侦听端口或等待连接。

所以 while(true)... 不应该归类为好或坏,让情况决定使用什么

于 2008-12-24T07:30:43.997 回答
1

这取决于您要做什么,但总的来说,我更喜欢将条件放在 while 中。

  • 它更简单,因为您不需要在代码中进行其他测试。
  • 它更容易阅读,因为您不必在循环内寻找中断。
  • 你在重新发明轮子。while 的全部意义在于,只要测试为真,就可以做某事。为什么要通过将中断条件放在其他地方来颠覆它?

如果我正在编写一个守护进程或其他应该运行直到它被杀死的进程,我会使用一个 while(true) 循环。

于 2008-12-24T00:36:29.027 回答
1

如果有一个(并且只有一个)非异常中断条件,则最好将该条件直接放入控制流构造(while)中。看到 while(true) { ... } 让我作为一个代码阅读者认为没有简单的方法来枚举中断条件并让我思考“仔细看看这个并仔细考虑中断条件(之前设置了什么它们在当前循环中以及在前一个循环中可能设置的内容)”

简而言之,在最简单的情况下,我和你的同事在一起,但是 while(true){ ... } 并不少见。

于 2008-12-24T01:16:33.207 回答
1

完美顾问的回答:视情况而定。大多数情况下,正确的做法是使用 while 循环

while (condition is true ) {
    // do something
}

或用类 C 语言完成的“重复直到”

do {
    // do something
} while ( condition is true);

如果其中任何一种情况有效,请使用它们。

有时,就像在服务器的内部循环中一样,您的意思是程序应该继续运行,直到外部中断它为止。(考虑一下,例如,一个 httpd 守护进程——它不会停止,除非它崩溃或被关机停止。)

THEN AND ON THE使用 while(1):

while(1) {
   accept connection
   fork child process
}

最后一种情况是您希望在终止之前执行某些功能的罕见情况。在这种情况下,请使用:

while(1) { // or for(;;)
   // do some stuff
   if (condition met) break;
   // otherwise do more stuff.
}
于 2008-12-24T01:28:25.867 回答
1

我认为使用“while(true)”的好处可能是让多个退出条件更容易编写,特别是如果这些退出条件必须出现在代码块中的不同位置。但是,对我来说,当我必须干运行代码以查看代码如何交互时,这可能会很混乱。

就我个人而言,我会尽量避免 while(true)。原因是每当我回顾之前编写的代码时,我通常会发现我需要弄清楚它何时运行/终止的时间比实际执行的要多。因此,必须先找到“中断”对我来说有点麻烦。

如果需要多个退出条件,我倾向于将条件确定逻辑重构为单独的函数,以便循环块看起来干净且更易于理解。

于 2008-12-24T01:39:08.980 回答
1

不,这还不错,因为您在设置循环时可能并不总是知道退出条件,或者可能有多个退出条件。但是,它确实需要更加小心以防止无限循环。

于 2008-12-24T06:07:35.693 回答
0

他可能是正确的。

在功能上,两者可以相同。

然而,为了可读性和理解程序流程,while(condition) 更好。休息更像是一种goto。while(条件)在继续循环的条件等方面非常清楚。这并不意味着 break 是错误的,只是可读性较差。

于 2008-12-24T00:36:58.217 回答
0

我想到了使用后一种构造的一些优点:

  • 无需在循环代码中查找中断,就更容易理解循环在做什么。

  • 如果您不在循环代码中使用其他中断,则循环中只有一个退出点,那就是 while() 条件。

  • 通常最终会减少代码,这增加了可读性。

于 2008-12-24T00:37:33.113 回答
0

我更喜欢 while(!) 方法,因为它更清楚、更直接地传达了循环的意图。

于 2008-12-24T00:39:04.373 回答
0

这里有很多关于可读性及其结构良好的讨论,但是与所有大小不固定的循环一样(即执行 while 和 while),您将面临风险。

His reasoning was that you could "forget the break" too easily and have an endless loop.

在 while 循环中,您实际上是在要求一个无限期运行的进程,除非发生某些事情,如果在某个参数内没有发生某些事情,您将得到您想要的……一个无限循环。

于 2008-12-24T01:30:45.707 回答
0

你朋友推荐的和你做的不一样。你自己的代码更类似于

do{
    // do something
}while(!<some condition>);

无论条件如何,它总是至少运行一次循环。

但正如其他人所提到的,有时休息是完全可以的。针对你朋友“忘记休息”的担忧,我经常写成如下形式:

while(true){
    // do something
if(<some condition>) break;
    // continue do something
}

通过良好的缩进,断点对于第一次阅读代码的人来说是很清楚的,看起来就像在循环的开头或底部中断的代码一样结构化。

于 2008-12-24T07:25:15.707 回答
0

不是while(true)部分不好,而是你必须打破或摆脱它的事实才是问题所在。break 和 goto 并不是真正可以接受的流量控制方法。

我也不是很明白这一点。即使在循环整个程序持续时间的东西中,你至少可以有一个叫做 Quit 的布尔值或者你设置为 true 的东西,以便在循环中正确退出循环,比如 while(!Quit)... 不是只是在某个任意点调用 break 然后跳出来,

于 2008-12-24T08:29:44.393 回答
0

使用循环

while(1) { 做事 }

在某些情况下是必要的。如果您进行任何嵌入式系统编程(想想像 PIC、MSP430 和 DSP 编程这样的微控制器),那么几乎所有代码都将处于 while(1) 循环中。在为 DSP 编码时,有时您只需要一段时间 (1){},其余代码是中断服务例程 (ISR)。

于 2008-12-26T06:51:14.637 回答