打破包含switch语句的while循环

71

我在尝试找出如何跳出一个包含switch语句的循环时遇到了问题。break语句只能跳出switch语句,而不能跳出整个循环。

可能有更优雅的解决方案。我已经实现了一个标志(flag),它一开始为true,当被设置为false时结束循环。你能提供更好的解决方案吗?

背景:这段代码用于条形码工作流系统中。我们有内置条形码扫描仪的PocketPC。该代码用于其中一个功能。在整个过程中,它提示用户输入不同的数据。这部分允许他们在PocketPC终端上滚动查看一些库存记录(分页结果),并允许他们键入"D"表示完成,"Q"表示退出。

这是需要改进的C#示例:


```csharp while (true) { switch (action) { case "D": done = true; break;
case "Q": quit = true; break;
// Code to handle other cases...
default: break; }
if (done || quit) { break; } } ```
do
{
    switch (MLTWatcherTCPIP.Get().ToUpper())
    {
        case "": //scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "P": //scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "D": //DONE (exit out of this Do Loop)
            // break; // this breaks out of the switch, not the loop
            // return; // this exists entire method; not what I'm after
            keepOnLooping = false;
            break;
        case "Q": //QUIT (exit out to main menu)
            return;
        default:
            break;
    }
} while (keepOnLooping);

以下是 VB.NET 实现该功能的示例代码:

Do
    Select Case MLTWatcherTCPIP.Get().ToUpper
        Case "" ''#scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown()
        Case "P" ''#scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextUp()
        Case "D" ''#DONE (exit out of this Do Loop)
            Exit Do
        Case "Q" ''#QUIT (exit out to main menu)
            Return
    End Select
Loop

谢谢。


8
对我来说,这看起来不错,flag变量是检查循环条件的标准方式。 - Ron Warholic
在Java(以及其他一些语言)中,给循环加上标签并使用带标签的break将是最直接的答案。 - Roy Tinker
15个回答

58

我会尽量避免使用它,但您可以使用

goto

关键字。

不过要注意,如果您这么做,可能会招来暴怒的群众持叉和火把发起抨击。


4
超过15年的经验告诉我,有时候goto是最好、最快和最优化的选择,将算法保持在一个小地方:保持它快速、高效、原子化,不要将其分成一千个碎片,减少不必要的调用。例如,声波分析、机器视觉、实时等。 一个标志不会使它变慢,我认为这是不可能的,但是太多的标志可能会导致阅读问题。 - Cheva

55

我发现这个表格稍微更易读一些:

bool done = false;
while (!done) 
{ 
    switch (MLTWatcherTCPIP.Get().ToUpper()) 
    { 
        case "": //scroll/display next inventory location 
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown(); 
            break; 
        case "P": //scroll/display previous inventory location 
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown(); 
            break; 
        case "D": //DONE (exit out of this Do Loop) 
            done = true;
            break; 
        case "Q": //QUIT (exit out to main menu) 
            return; 
        default: 
            break; 
    } 
}

为什么不用 if/else? - Ozkan
@Ozkan 我只是按照问题的要求回答。实际上,我从不会使用ToUpper()这种方式进行大小写不敏感的比较。 - Jeffrey L Whitledge

31

一种选择是将这个循环重构成一个方法("提取方法"),并使用 return


12

我所知道的另一种方法就是令人闻风丧胆的goto。 微软开发者网络也这么说。

然而,在这个情况下,我看不出你为什么要使用它。你已经实现的方式很好,比goto更易于维护。我建议保留你已经有的代码。


14
虽然 goto 可能令人望而生畏,但在少数情况下它确实很有用,我认为这就是其中之一。 - Steve Guidi

10

要进行多级中断,必须使用goto语句。在C#中,这似乎是唯一“干净”的方法。使用标志(flag)也很有用,但如果循环有其他运行情况,则需要额外的代码。

http://msdn.microsoft.com/zh-cn/library/aa664756(VS.71).aspx

值得注意的是,一些非C类语言通过执行break levels;来实现多级中断(尽管Java同样无用,因为它使用伪装成continue的goto.. :P)。


9

您无法轻松地跳出外层循环,但可以使用continue来处理它。

如果你反转你的逻辑,你会得到这个。注意,在switch语句后面立即有一个break退出循环。

在我看来,这段代码不太易读,我认为使用标志仍然是最好的。

   do
         {
            switch (Console.ReadKey().KeyChar.ToString())
            {
                case "U":
                    Console.WriteLine("Scrolling up");
                    continue;

                case "J":
                    Console.WriteLine("Scrolling down");
                    continue;

                case "D": //DONE (exit out of this Do Loop)
                    break;

                case "Q": //QUIT (exit out to main menu)
                    return;

                default:
                    Console.WriteLine("Continuing");
                    continue;
            }

            break;

        } while (true);

        Console.WriteLine("Exited");

有趣而独特的方法,从未想过这样的技巧,谁知道在某些情况下可能会有用! - Alexis Martial
1
@AlexisMartial 我相信有些情况下这可能是最好的答案,但是再次看到它后确实让我头疼……根本不可能是那么久以前的事情! - undefined

8
为什么不将开关包装成一个返回布尔值的方法,以保持循环运行?这样做还有一个好处,可以使代码更易读。有人写了一篇论文说我们实际上不需要使用goto语句 ;)
do
{
    bool keepOnLooping = TryToKeepLooping();
} while (keepOnLooping);

private bool TryToKeepLooping()
{
    switch (MLTWatcherTCPIP.Get().ToUpper())
    {
        case "": //scroll/display next inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "P": //scroll/display previous inventory location
            MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
            break;
        case "D": //DONE (exit out of this Do Loop)
            // break; // this breaks out of the switch, not the loop
            // return; // this exists entire method; not what I'm after
            return false;
        case "Q": //QUIT (exit out to main menu)
            return true;
        default:
            break;
    }

    return true;
}

4

您可以使用if/else语句替换switch语句。不需要使用goto,并且break语句将离开循环:

do
{
  String c = MLTWatcherTCPIP.Get().ToUpper();

  if (c = "")
    MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextDown();
  else if (c = "P")
    MLTWatcherTCPIP.TerminalPrompt.ScrollBodyTextUp();
  else if (c = "D")
     break;
  else if (c = "Q")
    return;
  else
  {
    // Handle bad input here.
  }
} while (keepLooping)

4
大多数情况下这样做是可以的,但switchif/else之间有一个重要区别:编译器通常可以通过使用跳转表来优化"分支"操作,从而产生更快的代码。 - Steve Guidi
6
Steve,这段代码正在等待用户输入。它已经在那里坐了数十亿纳秒,什么也没做。花费多少纳秒来计算所键入的字符是完全无关紧要的。优化应该由真实世界中用户关注的实证数据驱动,而不是由对编译器可能会做什么的猜测。 - Eric Lippert
1
我同意Steve的观点,但在某些简单情况下,将您的逻辑更改为if/else可能会奏效。 - Alex

4

标志是完成此操作的标准方法。 我所知道的另一种方法是使用 goto


1

在我看来,这似乎是一种完全可以接受的跳出 while 循环的方式。它按照你的期望执行,没有任何副作用。我也可以想到其他的做法。

if(!keepOnLooping)
  break;

但就执行而言,这并没有什么不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接