Como sair de um loop de dentro de um switch?

113

Estou escrevendo um código parecido com este:

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

Existe alguma maneira direta de fazer isso?

Sei que posso usar um sinalizador e interromper o loop colocando uma quebra condicional logo após a troca. Só quero saber se o C ++ já tem alguma construção para isso.

jrharshath
fonte
20
Por que você precisa de uma quebra condicional após a troca? Basta mudar o seu tempo de while (true) para while (flag) ...
Tal Pressman
7
@Dave_Jarvis Presumo que esta seja uma versão simplificada que ele colocou aqui para ilustrar o que estava tentando fazer.
Alterlife
6
Se você é um desses programadores que produzem funções com várias páginas, você achará gotoatraente e, às vezes, a única saída limpa. Se você tende a organizar seu código em pequenas funções com apenas algumas linhas e fazer uma única coisa cada, você nunca terá esse problema. (Aliás, seu código também será mais fácil de ler.)
sbi
13
Se quiser receber um conselho para parar de fumar, tudo o que você quer saber é como chegar à estação de metrô mais próxima.
Michael Krelin - hacker de
7
@hacker: Bem, se você não consegue ver a estação de metrô à sua frente por causa de toda a fumaça, esse conselho pode não ser tão ruim assim. :)
sbi de

Respostas:

49

Premissa

O código a seguir deve ser considerado um formato incorreto, independentemente do idioma ou da funcionalidade desejada:

while( true ) {
}

Argumentos de Apoio

O while( true )loop é de má forma porque:

  • Quebra o contrato implícito de um loop while.
    • A declaração do loop while deve indicar explicitamente a única condição de saída.
  • Implica que ele faz um loop para sempre.
    • O código dentro do loop deve ser lido para compreender a cláusula de terminação.
    • Os loops que se repetem para sempre evitam que o usuário finalize o programa de dentro do programa.
  • É ineficiente.
    • Existem várias condições de encerramento do loop, incluindo a verificação de "verdadeiro".
  • Está sujeito a bugs.
    • Não é possível determinar facilmente onde colocar o código que sempre será executado para cada iteração.
  • Leva a um código desnecessariamente complexo.
  • Análise automática de código-fonte.
    • Para encontrar bugs, análise de complexidade de programa, verificações de segurança ou derivar automaticamente qualquer outro comportamento de código-fonte sem execução de código, a especificação da (s) condição (ões) de quebra inicial permite que os algoritmos determinem invariantes úteis, melhorando assim as métricas de análise automática de código-fonte.
  • Loops infinitos.
    • Se todo mundo sempre usa while(true)loops for que não são infinitos, perdemos a capacidade de nos comunicarmos concisamente quando os loops, na verdade, não têm condição de terminação. (Provavelmente, isso já aconteceu, então a questão é discutível.)

Alternativa para "Ir para"

O código a seguir é melhor:

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

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

Vantagens

Sem bandeira. Não goto. Sem exceção. Fácil de mudar. Fácil de ler. Fácil de consertar. Além disso, o código:

  1. Isola o conhecimento da carga de trabalho do loop do próprio loop.
  2. Permite que alguém que mantém o código estenda facilmente a funcionalidade.
  3. Permite que várias condições de encerramento sejam atribuídas em um só lugar.
  4. Separa a cláusula de terminação do código a ser executado.
  5. É mais seguro para usinas nucleares. ;-)

O segundo ponto é importante. Sem saber como o código funciona, se alguém me pedir para fazer o loop principal permitir que outros threads (ou processos) tenham algum tempo de CPU, duas soluções vêm à mente:

Opção 1

Insira prontamente a pausa:

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

Opção 2

Substituir executar:

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

Este código é mais simples (portanto, mais fácil de ler) do que um loop com um incorporado switch. O isValidStatemétodo deve determinar apenas se o loop deve continuar. O burro de carga do método deve ser abstraído no executemétodo, o que permite que as subclasses substituam o comportamento padrão (uma tarefa difícil usando um switchegoto ).

Exemplo Python

Compare a seguinte resposta (a uma pergunta Python) que foi postada no StackOverflow:

  1. Loop para sempre.
  2. Peça ao usuário para inserir sua escolha.
  3. Se a entrada do usuário for 'reiniciar', continue o loop para sempre.
  4. Caso contrário, pare de repetir para sempre.
  5. Fim.
Código
while True: 
    choice = raw_input('What do you want? ')

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

print 'Break!' 

Versus:

  1. Inicialize a escolha do usuário.
  2. Loop enquanto a escolha do usuário é a palavra 'reiniciar'.
  3. Peça ao usuário para inserir sua escolha.
  4. Fim.
Código
choice = 'restart';

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

print 'Break!'

Aqui, while Trueresulta em código enganoso e excessivamente complexo.

Dave Jarvis
fonte
45
A ideia de que while(true)deveria ser prejudicial me parece bizarra. Existem loops que verificam antes de serem executados, há aqueles que verificam depois e há aqueles que verificam no meio. Como o último não tem construção sintática em C e C ++, você terá que usar while(true)ou for(;;). Se você considera isso errado, é porque ainda não pensou o suficiente nos diferentes tipos de loops.
sbi
34
Razões pelas quais eu discordo: while (true) e for (;;;) não são confusos (ao contrário de do {} while (true)) porque eles declaram o que estão fazendo logo de cara. Na verdade, é mais fácil procurar instruções "break" do que analisar uma condição de loop arbitrária e procurar onde ela está definida, e não é mais difícil provar a correção. O argumento sobre onde colocar o código é igualmente intratável na presença de uma instrução continue, e não muito melhor com instruções if. O argumento da eficiência é positivamente tolo.
David Thornley,
23
Sempre que você vir "while (true)", pode pensar em um loop que possui condições de finalização internas, que não são mais difíceis do que as condições de finalização arbitrárias. Simplesmente, um loop "while (foo)", onde foo é uma variável booleana definida de forma arbitrária, não é melhor do que "while (true)" com interrupções. Você deve examinar o código em ambos os casos. Apresentar uma razão boba (e microeficiência é um argumento bobo) não ajuda seu caso, aliás.
David Thornley,
5
@Dave: Eu dei um motivo: é a única maneira de obter um loop que verifica a condição do meio. Exemplo clássico: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}é claro que eu poderia transformar isso em um loop pré-condicionado (usando std::getlinecomo condição de loop), mas isso tem desvantagens por si só ( lineteria que ser uma variável fora do escopo do loop). E se eu fizesse, eu teria que perguntar: Por que temos três loops de qualquer maneira? Sempre poderíamos transformar tudo em um loop pré-condicionado. Use o que for melhor. Se while(true)couber, use-o.
sbi de
3
Como uma cortesia às pessoas que estão lendo esta resposta, eu evitaria comentar sobre a qualidade do bem que produziu a pergunta e me limitaria a responder a pergunta em si. A questão é uma variação da seguinte, que acredito ser uma característica de muitas linguagens: você tem um loop aninhado dentro de outro loop. Você deseja quebrar o loop externo do loop interno. Como isso é feito?
Alex Leibowitz
172

Você pode usar goto.

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

   }
}
exit_loop: ;
Mehrdad Afshari
fonte
12
Apenas não vá à loucura com essa palavra-chave.
Fragsworth
45
É engraçado eu ser rejeitado porque as pessoas não gostam goto. O OP claramente mencionado sem usar sinalizadores . Você pode sugerir uma maneira melhor, considerando as limitações do OP? :)
Mehrdad Afshari
18
votado apenas para compensar os haters goto estúpidos. Acho que Mehrdad sabia que perderia alguns pontos por sugerir uma solução sensata aqui.
Michael Krelin - hacker de
6
+1, não há maneira melhor imho, mesmo considerando as limitações dos OPs. Sinalizadores são feios, quebram a lógica em todo o loop e, portanto, mais difíceis de entender.
Johannes Schaub - litb
11
no final do dia, sem a construção goto, todos os idiomas falham. linguagens de alto nível o ofuscam, mas se você olhar bem embaixo do capô, encontrará cada loop ou condição que se resume a ramificações condicionais e incondicionais. enganamos a nós mesmos usando palavras-chave for, while, until, switch, if, mas eventualmente todas elas utilizam GOTO (ou JMP) de uma forma ou de outra. você pode se iludir, inventando todos os tipos de maneiras inteligentes de escondê-lo, ou pode apenas ser pragmaticamente honesto e usar goto, quando apropriado, e com moderação.
não sincronizado em
57

Uma solução alternativa é usar a palavra-chave continueem combinação com break, ou seja:

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

Use a continueinstrução para terminar cada rótulo de caso em que deseja que o loop continue e use obreak instrução para terminar os rótulos de caso que devem encerrar o loop.

É claro que essa solução só funciona se não houver código adicional a ser executado após a instrução switch.

sakra
fonte
9
Embora isso seja realmente muito elegante, tem a desvantagem de que a maioria dos desenvolvedores primeiro tem que olhar para ele por um minuto para entender como / se isso funciona. :(
sbi de
8
A última frase é o que torna isso não tão bom. :(Caso contrário, uma implementação muito limpa.
nawfal
Quando você pensa sobre isso, nenhum código de computador é bom. Somos apenas treinados para entendê-lo. Por exemplo, xml parecia lixo até que eu aprendi. Agora é menos parecido com lixo. for (int x = 0; x <10; ++ x, moveCursor (x, y)) realmente causou um erro lógico em meu programa. Esqueci que a condição de atualização aconteceu no final.
22

Uma maneira elegante de fazer isso seria colocar isso em uma função:

int yourfunc() {

    while(true) {

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

    }
}

Opcionalmente (mas 'más práticas'): como já sugerido, você pode usar um goto ou lançar uma exceção dentro do switch.

Alterlife
fonte
4
A exceção deve ser usada para, bem, lançar uma exceção, não um comportamento bem conhecido de uma função.
Clement Herreman
Eu concordo com a vida após a morte: coloque-o em uma função.
dalle
14
Lançar uma exceção é muito ruim neste caso. Significa "Eu quero usar um goto, mas li em algum lugar que não deveria usá-los, então vou usar um subterfúgio e fingir que estou inteligente".
Gorpik
Concordo que lançar uma exceção ou usar um goto é uma péssima ideia, mas são opções que funcionam e foram listadas como tal na minha resposta.
Alterlife
2
Lançar uma exceção é substituir um COMEFROM por um GOTO. Não faça isso. O goto funciona muito melhor.
David Thornley,
14

AFAIK não há "quebra dupla" ou construção semelhante em C ++. O mais próximo seria um goto- que, embora tenha uma conotação ruim para seu nome, existe na língua por uma razão - desde que usado com cuidado e parcimônia, é uma opção viável.

Âmbar
fonte
8

Você pode colocar seu switch em uma função separada como esta:

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())
    ;
Adam Pierce
fonte
7

Mesmo se você não gostar de goto, não use uma exceção para sair de um loop. O exemplo a seguir mostra como isso pode ser feio:

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

Eu usaria gotocomo nesta resposta. Nesse caso goto, o código ficará mais claro do que qualquer outra opção. Espero que esta pergunta seja útil.

Mas acho que usar gotoé a única opção aqui por causa da string while(true). Você deve considerar a refatoração de seu loop. Suponho que a seguinte solução:

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

Ou mesmo o seguinte:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Kirill V. Lyadvinsky
fonte
1
Sim, mas eu gosto de exceções ainda menos ;-)
quamrana
3
Usar uma exceção para emular um goto é obviamente pior do que realmente usar um goto! Provavelmente também será significativamente mais lento.
John Carter,
2
@Downvoter: É porque eu afirmo que você não deve usar exceção ou porque eu sugiro refatoração?
Kirill V. Lyadvinsky,
1
Não o votante negativo - mas ... Sua resposta não está clara se você está propondo ou condenando a ideia de usar uma exceção para sair do ciclo. Talvez a primeira frase devesse ser: "Mesmo que você não goste goto, não use uma exceção para sair de um loop:"?
Jonathan Leffler,
1
@Jonathan Leffler, Obrigado, você mostrou a amostra de comentário valioso. atualizou a resposta tendo em mente seus comentários.
Kirill V. Lyadvinsky,
5

Não há construção C ++ para quebrar o loop neste caso.

Use um sinalizador para interromper o loop ou (se apropriado) extraia seu código em uma função e use return.

dente afiado
fonte
2

Você poderia usar goto, mas prefiro definir um sinalizador que interrompa o loop. Em seguida, saia do interruptor.

Martin York
fonte
2

Por que não apenas corrigir a condição em seu loop while, fazendo com que o problema desapareça?

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**
    }
}
Mark B
fonte
2

Não, C ++ não possui uma construção para isso, visto que a palavra-chave "break" já está reservada para sair do bloco switch. Alternativamente, um do..while () com um sinalizador de saída pode ser suficiente.

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

Eu acho que;

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

A maneira mais simples de fazer isso é colocar um IF simples antes de fazer o SWITCH, e que IF teste sua condição para sair do loop .......... o mais simples possível

SpiriAd
fonte
2
Hum ... você poderia ser ainda mais simples colocando essa condição no argumento while. Quer dizer, é para isso que existe! :)
Mark A. Donohoe
0

A breakpalavra-chave em C ++ apenas termina a iteração ou switchinstrução envolvente mais aninhada . Portanto, você não poderia sair do while (true)loop diretamente dentro da switchinstrução; no entanto, você pode usar o seguinte código, que considero um excelente padrão para esse tipo de problema:

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

    //...
    }
}

Se você precisava fazer algo quando msg->stateigual DONE(como executar uma rotina de limpeza), coloque esse código imediatamente após o forloop; ou seja, se você atualmente tem:

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

    //...

    case DONE:
        do_cleanup();
        break;
    }

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

    msg = next_message();
}

Em seguida, use:

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

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
Daniel Trebbien
fonte
0

Me surpreende como isso é simples, considerando a profundidade das explicações ... Aqui está tudo que você precisa ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

RI MUITO!! Realmente! É tudo que você precisa! Uma variável extra!

Mark A. Donohoe
fonte
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**
}
}
Wajeed-MSFT
fonte
0

Eu tive o mesmo problema e resolvi usando um flag.

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;
}
Ram Suthar
fonte
-2

Se bem me lembro da sintaxe C ++, você pode adicionar um rótulo às breakinstruções, assim como para goto. Portanto, o que você deseja seria facilmente escrito:

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
Andrei Vajna II
fonte
1
Infelizmente, sua memória está com defeito. Seria um bom recurso, nas raras ocasiões em que seria útil. (Já vi propostas de vários níveis para quebrar, mas me parecem insetos esperando para acontecer.)
David Thornley
Deve ter sido Java, então. Obrigado por responder. E, claro, você está certo com o resto também.
Andrei Vajna II
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
fonte
1
Todas as quebras nesse exemplo de código saem da instrução switch.
Dan Hulme