在while循环中使用高级switch语句?

8

我刚开始学习C++,但我对其他语言有一些先前的了解(不幸的是,曾经学过VB),但我有一个奇怪的问题。我不喜欢使用太多IF语句,希望使用switch / case语句,因为它看起来更清晰,我想练习一下。但是...

假设我有以下情况(理论代码):

while(1) {

  //Loop can be conditional or 1, I use it alot, for example in my game
  char something;
  std::cout << "Enter something\n -->";
  std::cin  >> something;

  //Switch to read "something"
  switch(something) {
    case 'a':
      cout << "You entered A, which is correct";
      break;
    case 'b':
      cout << "...";
      break;
  }
}

我的问题是这样的。假设我想要退出 WHILE 循环,那么需要两个 break 语句吗?

显然,这看起来不太对:

case 'a':
  cout << "You entered A, which is correct";
  break;
  break;

那么我只能在'a'上使用IF语句来使用break吗?我是不是漏掉了什么非常简单的东西?
这将解决我现在遇到的很多问题。

3
根据标题,我以为这可能是关于 Duff's Device 的问题。但不是。是否有一个“无聊”的标签? - Ben Voigt
很高兴你的问题得到解决,这些事情并不是最令人愉快的。 - oni-kun
9个回答

32

我会将该检查重构为另一个函数。

bool is_correct_answer(char input)
{
    switch(input)
    {
    case 'a':
        cout << "You entered A, which is correct";
        return true;
    case 'b':
        cout << "...";
        return false;
    }
    return false;
}

int main()
{
    char input;
    do
    {
        std::cout << "Enter something\n -->";
        std::cin  >> input;
    } while (!is_correct_answer(input));
}

其中一个函数需要修改以考虑大写或小写的选择。 - Thomas Matthews
只需将 switch(input) 更改为 switch(tolower(input)) - fredoverflow

13

你可以在一个 case 语句中设置一个布尔值,然后在 while 循环中检查它。

bool done = false;    
while(!done)
{
 char something;
  std::cout << "Enter something\n -->";
  std::cin  >> something;

  //Switch to read "something"
  switch(something) {
    case 'a':
      cout << "You entered A, which is correct";
      done = true; // exit condition here
      break;
    case 'b':
      cout << "...";
      break;
  }
}

1
我觉得这个答案有点不够优化(但并没有错)。 如果在switch语句下面添加代码,即使问题是直接退出外层循环,它也会被执行。 我会使用Dimas if(done) break;方法,或者一个goto。 - Hannes Ovrén
是的,这个解决方案的一个陷阱就是代码在该开关下执行。同意。 - Robb
有趣的是,我总是使用 while (running)。 - kenny
1
你们两个都没有抓住重点。在 done 后执行一些样板代码(例如重置计数器或任何其他杂项代码)并没有什么问题。这不是一个陷阱,而是一个奖励。你必须跳出思维定势,看到在设置 done = true 后能够执行代码的积极影响所在。这不是短路,而是布尔值。 - user195488

5

是的,C和C++没有办法说“退出多个可中断块”(其中“可中断块”是任何循环或开关)。解决方法包括使用goto和使用布尔变量记录外部“可中断块”是否也应该中断(两者都不太优雅,但这就是生活)。


5

两个 break 语句无法使您退出 while 循环。第一个 break 只会让您跳出 switch 语句,而第二个则永远不会到达。

您需要做的是使 while 循环的条件为 false,假设在 switch 语句之后没有其他代码。如果 switch 后面有其他代码,则应该在 switch 之后检查条件,并在那里使用 break


bool done = false;
while(! done) { // 做一些事情 switch(something) { case 'a': done = true; // 退出循环 break; }
// 如果除了 switch 之外还有其他代码,请执行此操作 if(done) break; // 让您跳出 while 循环
// 在 switch 之后执行必要的操作
}

如果你要检查(done),为什么不直接使用while(true)呢?这是多余的。 - user195488
这取决于...您可能希望在switch之后立即中断,也可能不希望。 - Dima

3

标志(flag)和将块封装到函数中都有效。使用Goto是不好的习惯。在这里使用异常不太合适。退出循环并不是错误。 - Dima
5
Goto并不是不好的。我认为在这种情况下使用goto是合适的,标志(flag)也是如此。 - Johannes Schaub - litb
在示例代码中,我会使用goto,如果在switch语句之后还有代码,它会保持整洁。 - mikek3332002

2

如果您对C ++中的命名循环习惯用法感兴趣,那么这篇文章可能会对您有所帮助。

#define named(blockname) goto blockname; \
                         blockname##_skip: if (0) \
                         blockname:

#define break(blockname) goto blockname##_skip;

named(outer)
while(1) {

  //Loop can be conditional or 1, I use it alot, for example in my game
  char something;
  std::cout << "Enter something\n -->";
  std::cin  >> something;

  //Switch to read "something"
  switch(something) {
    case 'a':
      cout << "You entered A, which is correct";
      break(outer);
    case 'b':
      cout << "...";
      break(outer);
  }
}

1
你也可以将循环封装到一个函数中,在 case 中调用 return,以防止 while 循环被打断的情况。 这不是一种好的编程习惯,但如果你保持函数简单,我认为没有什么问题。

0

你可以用一个稍微过度设计的面向对象的解决方案来替换这个 switch 语句...

#include <iostream>
#include <map>
#include <set>

class input_responder
{
    std::set<char> correct_inputs;
    std::map<char, const char*> wrong_inputs;

public:

    input_responder()
    {
        correct_inputs.insert('a');
        wrong_inputs['b'] = "...";
    }

    bool respond(char input) const
    {
        if (correct_inputs.find(input) != correct_inputs.end())
        {
            std::cout << "You entered " << input << ", which is correct\n";
            return true;
        }
        else
        {
            std::map<char, const char*>::const_iterator it = wrong_inputs.find(input);
            if (it != wrong_inputs.end())
            {
                std::cout << it->second << '\n';
            }
            else
            {
                std::cout << "You entered " << input << ", which is wrong\n";
            }
            return false;
        }
    }
};

int main()
{
    const input_responder responder;
    char input;
    do
    {
        std::cout << "Enter something\n -->";
        std::cin  >> input;
    } while (responder.respond(input) == false);
}

0
你可以将你的 switch 语句改成 if 语句。无论如何,它都会被编译成相同的东西。

好的论点,如果不重要的话,我不介意稍微结构化一些的代码。 :P - Nullw0rm
一定要添加注释,解释为什么要使用 if/else-if,这样你(或其他人)以后不会看到它并将其更改为更明显的 switch 语句,从而破坏一切。无意冒犯。 - Nick Lewis
如果有人重构你的代码,却没有注意到他们改变了方法的基本行为,我不确定我是否会信任他们首先阅读注释。 - Dennis Zickefoose
它可能不会编译成相同的东西。switch通常使用跳转表来实现,如果有许多case,则很可能更快。 - Thomas
@Nick Lewis:我认为大多数人都会注意到break问题,就像@Jason一样? @Thomas:如果编译器注意到else if中的简单重复模式,我相信它们也会使用跳转表。 - Thomas Ahle

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