如何从交换机内部跳出循环?


113

我正在写一些看起来像这样的代码:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        break; // **HERE, I want to break out of the loop itself**
    }
}

有没有直接的方法可以做到这一点?

我知道我可以使用一个标志,并通过在开关之后放置条件中断来中断循环。我只想知道C ++是否已经为此做了一些构造。


20
为什么切换后需要有条件的休息?只需将while时间从while(true)更改为while(flag)...
Tal Pressman 2009年

7
@Dave_Jarvis我认为这是他在此处放置的简化版本,以说明他正在尝试执行的操作。
Alterlife

6
如果您是产生几页长的函数的程序员之一,那么您会发现goto有吸引力的,有时是唯一的出路。如果您倾向于将代码组织成只有几行长的小型函数,并且每个函数只做一件事情,那么您将永远不会遇到这个问题。(顺便说一下,您的代码也将更易于阅读。)
sbi

13
如果您想获得戒烟建议,那么您仅想知道如何前往最近的地铁站。
Michael Krelin-黑客

7
@hacker:好吧,如果由于烟雾smoke绕而看不到前面的地铁站,那建议可能还不是那么糟糕。:)
2009年

Answers:


49

前提

不管语言或所需功能如何,以下代码都应视为错误的形式:

while( true ) {
}

支持论点

while( true )循环形式不佳,因为它:

  • 打破while循环的隐含契约。
    • while循环声明应显式声明唯一的退出条件。
  • 暗示它永远循环。
    • 必须阅读循环中的代码以了解终止子句。
    • 永远重复的循环会阻止用户终止程序中的程序。
  • 效率低下。
    • 存在多个循环终止条件,包括检查“ true”。
  • 很容易出现错误。
    • 无法轻松确定将始终在每次迭代中执行的代码放在何处。
  • 导致不必要的复杂代码。
  • 自动源代码分析。
    • 为了查找错误,程序复杂性分析,安全检查或自动导出任何其他源代码行为而无需执行代码,指定初始中断条件可使算法确定有用的不变式,从而改善自动源代码分析指标。
  • 无限循环。
    • 如果每个人都总是使用while(true)非无限循环,那么当循环实际上没有终止条件时,我们将失去简洁通信的能力。(可以说,这已经发生了,所以要点是没有意义的。)

替代“转到”

以下代码是更好的形式:

while( isValidState() ) {
  execute();
}

bool isValidState() {
  return msg->state != DONE;
}

优点

没有标志。没有goto。没有例外。容易改变。易于阅读。易于修复。另外的代码:

  1. 从循环本身中分离出有关循环工作量的知识。
  2. 允许维护代码的人员轻松扩展功能。
  3. 允许在一个位置分配多个终止条件。
  4. 将终止子句与要执行的代码分开。
  5. 对于核电厂更安全。;-)

第二点很重要。在不知道代码是如何工作的情况下,如果有人要求我让主循环让其他线程(或进程)有一些CPU时间,就会想到两种解决方案:

选项1

立即插入暂停:

while( isValidState() ) {
  execute();
  sleep();
}

选项#2

覆盖执行:

void execute() {
  super->execute();
  sleep();
}

该代码比带有嵌入式循环的代码更简单(因此更易于阅读)switch。该isValidState方法应仅确定循环是否应继续。该方法的主力应该抽象到该execute方法中,该方法允许子类覆盖默认行为(使用嵌入式switchgoto)。

Python范例

将以下答案(针对Python问题)与StackOverflow上发布的内容进行对比:

  1. 永远循环。
  2. 要求用户输入他们的选择。
  3. 如果用户的输入是“重新启动”,则永远继续循环。
  4. 否则,请永远停止循环。
  5. 结束。
while True: 
    choice = raw_input('What do you want? ')

    if choice == 'restart':
        continue
    else:
        break

print 'Break!' 

与:

  1. 初始化用户的选择。
  2. 当用户选择“重新启动”一词时循环播放。
  3. 要求用户输入他们的选择。
  4. 结束。
choice = 'restart';

while choice == 'restart': 
    choice = raw_input('What do you want? ')

print 'Break!'

在这里,while True导致误导和过于复杂的代码。


45
while(true)对我来说,应该有害的想法似乎很奇怪。有循环在执行前检查,有循环在执行后检查,还有在中间进行检查的循环。由于后者在C和C ++中没有语法构造,因此您必须使用while(true)for(;;)。如果您认为这是错误的,则您尚未充分考虑到不同类型的循环。
09年

34
我不同意的原因:while(true)和for(;;;)不会引起混淆(与do {} while(true)不同),因为它们先声明了自己在做什么。实际上,查找“ break”语句比解析任意循环条件并查找其设置位置要容易得多,并且更难以证明其正确性。在存在continue语句的情况下,关于将代码放置在何处的争论同样棘手,而if语句则没有那么好。效率论点肯定是愚蠢的。
David Thornley,2009年

23
只要看到“ while(true)”,就可以想到一个具有内部终止条件的循环,该条件比任意终止条件难得多。简而言之,“ while(foo)”循环(其中foo是以任意方式设置的布尔变量)并不比带有中断的“ while(true)”更好。在这两种情况下,您都必须仔细阅读代码。提出一个愚蠢的理由(微效率是一个愚蠢的论点)对您的情况没有帮助。
David Thornley

5
@Dave:我给出了一个理由:这是获取检查中间条件的循环的唯一方法。经典示例:while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}当然,我可以将其转换为预处理循环(std::getline用作循环条件),但这本身就有缺点(line必须是循环范围之外的变量)。如果我这样做了,我将不得不问:为什么我们仍然有三个循环?我们总是可以将所有内容转换为预定循环。使用最合适的。如果while(true)合适,请使用它。
2009年

3
出于对阅读此答案的人们的礼貌,我将避免评论产生该问题的商品的质量,并坚持回答问题本身。问题是以下内容的变体,我相信这是许多语言的功能:您有一个嵌套在另一个循环内的循环。您想从内循环中跳出外循环。怎么做?
Alex Leibowitz

172

您可以使用goto

while ( ... ) {
   switch( ... ) {
     case ...:
         goto exit_loop;

   }
}
exit_loop: ;

12
只是不要对那个关键字发狂。
Fragsworth

45
可笑的是我被人们拒绝了,因为人们不喜欢goto。OP明确提到不使用标志。考虑到OP的局限性,您能否提出更好的方法?:)
Mehrdad Afshari

18
仅推荐t来补偿无意识的goto仇恨者。我猜Mehrdad知道他会因为在这里提出明智的解决方案而失去两分。
Michael Krelin-黑客

6
+1,即使考虑到OP的限制,也没有更好的方法。标志很丑陋,破坏了整个循环的逻辑,因此更难掌握。
Johannes Schaub-litb

11
最终,如果没有goto构造,所有语言都会失败。高级语言对其进行了模糊处理,但是如果您在幕后花了足够的功夫,就会发现每个循环,或者条件归结为有条件和无条件分支。我们使用for,while,直到,switch,if关键字来自欺欺人,但最终它们都以一种或另一种方式利用GOTO(或JMP)。您可以自欺欺人,发明各种巧妙的隐藏方法,也可以务实诚实,并在适当的地方谨慎使用goto。
2012年

57

另一种解决方案是将关键字continue与结合使用break,即:

for (;;) {
    switch(msg->state) {
    case MSGTYPE
        // code
        continue; // continue with loop
    case DONE:
        break;
    }
    break;
}

使用该continue语句在希望循环继续的地方完成每个案例标签,并使用break语句完成应终止循环的案例标签。

当然,只有在switch语句之后没有其他代码要执行的情况下,此解决方案才有效。


9
尽管这确实非常优雅,但是它的缺点是大多数开发人员必须先仔细看一分钟才能了解其工作方式/是否有效。:(
2009年

8
最后一句话是什么使它不太好,:(否则将是一个非常干净的实现。
2013年

当您考虑它时,没有计算机代码是好的。我们仅受过了解的培训。例如,直到我了解xml为止,它看起来都是垃圾。现在减少了垃圾的查找。for(int x = 0; x <10; ++ x,moveCursor(x,y))实际上在我的程序中引起了逻辑错误。我忘了更新条件发生在最后。

22

一种简单的方法是将其放入函数中:

int yourfunc() {

    while(true) {

        switch(msg->state) {
        case MSGTYPE: // ... 
            break;
        // ... more stuff ...
        case DONE:
            return; 
        }

    }
}

可选(但“不良做法”):如已经建议的那样,您可以使用goto或在开关内引发异常。


4
应当使用异常抛出异常,而不是众所周知的函数行为。
克莱门特·赫雷曼

我同意来世:将其放入函数中。
dalle

14
在这种情况下,抛出异常真的很邪恶。它的意思是“我想使用goto,但我读到某个地方不应该使用它们,因此我会带着诡计,假装看起来很聪明”。
Gorpik

我同意抛出异常或使用goto是一个糟糕的主意,但它们是可行的选择,并已在我的回答中列出。
Alterlife

2
引发异常是用COMEFROM代替GOTO。不要这样 goto工作得更好。
David Thornley,2009年

14

AFAIK在C ++中没有“双破”或类似的构造。最接近的将是goto-尽管名称含义不正确,但由于某种原因在语言中存在-只要谨慎且谨慎地使用它,这是一个可行的选择。


8

您可以将开关置于这样的单独函数中:

bool myswitchfunction()
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        return false; // **HERE, I want to break out of the loop itself**
    }
    return true;
}

while(myswitchfunction())
    ;

7

即使您不喜欢goto,也不要使用异常退出循环。下面的示例显示了它的丑陋程度:

try {
  while ( ... ) {
    switch( ... ) {
      case ...:
        throw 777; // I'm afraid of goto
     }
  }
}
catch ( int )
{
}

我会用goto这个答案。在这种情况下,goto代码将比其他任何选项都更清晰。我希望这个问题会有所帮助。

但是我认为使用goto是唯一的选择,因为有字符串while(true)。您应该考虑重构循环。我想下面的解决方案:

bool end_loop = false;
while ( !end_loop ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        end_loop = true; break;
    }
}

甚至以下内容:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}

1
是的,但是我更喜欢例外;-)
quamrana

3
当然,使用异常模拟goto比实际使用goto更糟糕!可能还会明显慢一些。
约翰·卡特

2
@Downvoter:是因为我声明您不应该使用异常,还是因为我建议重构?
Kirill V. Lyadvinsky 09年

1
不是低级投票者-但是...您的答案不清楚,是您是提议还是谴责使用异常退出循环的想法。也许第一句话应该是:“即使您不喜欢goto,也不要使用异常退出循环:”?
乔纳森·莱夫勒

1
@Jonathan Leffler,谢谢,您展示了有价值的评论示例。牢记您的评论,更新了答案。
Kirill V. Lyadvinsky 09年

5

在这种情况下,没有C ++构造可以打破循环。

使用标志来中断循环,或者(如果适用)将代码提取到函数中并使用return


2

您可能会使用goto,但我希望设置一个标志来停止循环。然后断开开关。


2

为什么不只在while循环中修复条件,导致问题消失?

while(msg->state != DONE)
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        // We can't get here, but for completeness we list it.
        break; // **HERE, I want to break out of the loop itself**
    }
}

2

不,C ++没有为此构造,因为已经为退出开关块保留了关键字“ break”。或者,带有退出标志的do..while()可能就足够了。

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);

1

我认为;

while(msg->state != mExit) 
{
    switch(msg->state) 
    {
      case MSGTYPE: // ...
         break;
      case DONE:
      //  .. 
      //  ..
      msg->state =mExit;
      break;
    }
}
if (msg->state ==mExit)
     msg->state =DONE;

0

最简单的方法是在执行SWITCH之前放置一个简单的IF,并且该IF测试退出循环的条件.......尽可能简单


2
嗯...将条件放在while参数中可能会更加简单。我的意思是那就是它的目的!:)
Mark A. Donohoe

0

breakC ++中的关键字仅终止最嵌套的封闭迭代或switch语句。因此,您无法while (true)直接在switch语句内跳出循环;但是,您可以使用以下代码,我认为这是解决此类问题的绝佳模式:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

如果需要在msg->state相等时执行某些操作DONE(例如运行清理例程),则将该代码放在for循环后;即,如果您当前有:

while (true) {
    switch (msg->state) {
    case MSGTYPE:
        //... 
        break;

    //...

    case DONE:
        do_cleanup();
        break;
    }

    if (msg->state == DONE)
        break;

    msg = next_message();
}

然后改用:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();

0

令我感到惊讶的是,如此深入的解释如此简单...这就是您所需要的...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

大声笑!!真!这就是您所需要的!一个额外的变量!


0
while(MyCondition) {
switch(msg->state) {
case MSGTYPE: // ... 
    break;
// ... more stuff ...
case DONE:
   MyCondition=false; // just add this code and you will be out of loop.
    break; // **HERE, you want to break out of the loop itself**
}
}

0

我遇到了同样的问题,并使用标志解决了。

bool flag = false;
while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        flag = true; // **HERE, I want to break out of the loop itself**
    }
    if(flag) break;
}

-2

如果我还记得C ++语法,则可break以为语句添加标签,就像for一样goto。因此,您想要的内容将很容易编写:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ...
        break;
    // ... more stuff ...
    case DONE:
        break outofloop; // **HERE, I want to break out of the loop itself**
    }
}

outofloop:
// rest of your code here

1
不幸的是,您的记忆力有问题。在那些很少有用的场合,这将是一个不错的功能。(我已经看到了突破一些级别的提议,但在我看来,这些象是等待发生的错误。)
David Thornley

那一定是Java。谢谢回答。而且,当然,其余的您也对。
Andrei Vajna II

-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}

1
该代码示例中的所有中断均来自switch语句。
丹·赫尔姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.