Por que precisamos usar o interruptor de interrupção?


74

Quem decidiu (e com base em quais conceitos) que a switchconstrução (em várias línguas) deve usar breakem cada declaração?

Por que temos que escrever algo como isto:

switch(a)
{
    case 1:
        result = 'one';
        break;
    case 2:
        result = 'two';
        break;
    default:
        result = 'not determined';
        break;
}

(notei isso em PHP e JS; provavelmente existem muitas outras linguagens usando isso)

Se switché uma alternativa de if, por que não podemos usar a mesma construção que para if? Ou seja:

switch(a)
{
    case 1:
    {
        result = 'one';
    }
    case 2:
    {
        result = 'two';
    }
    default:
    {
        result = 'not determined';
    }
}

Diz-se que breakimpede a execução do bloco após o atual. Mas, alguém realmente se depara com a situação, onde havia necessidade de execução do bloco atual e dos seguintes? Eu não fiz. Para mim, breakestá sempre lá. Em cada bloco. Em todo código.


1
Como a maioria das respostas observou, isso está relacionado ao 'fall-through', em que várias condições são roteadas pelos mesmos blocos 'then'. No entanto, isso depende da linguagem - o RPG, por exemplo, trata uma CASEinstrução equivalente a um bloco if / elseif gigante.
Clockwork-Muse

34
Foi uma péssima decisão tomada pelos designers de C (como muitas decisões, foi feita para fazer a transição do assembly -> C e a tradução de volta, mais fácil do que para facilitar a programação) , que infelizmente foi herdada em outros idiomas baseados em C. Usando a regra simples "Sempre torne o caso comum o padrão !!" , podemos ver que o comportamento padrão deveria ter sido quebrado, com uma palavra-chave separada para falha, pois esse é de longe o caso mais comum.
BlueRaja - Danny Pflughoeft 28/08

4
Eu usei o fall-through em várias ocasiões, embora, sem dúvida, a declaração de troca no meu idioma preferido na época (C) pudesse ter sido projetada para evitá-lo; por exemplo, eu fiz, case 'a': case 'A': case 'b': case 'B'mas principalmente porque não posso fazer case in [ 'a', 'A', 'b', 'B' ]. Uma pergunta um pouco melhor é que, no meu idioma preferido atual (C #), a interrupção é obrigatória e não queda implícita; o esquecimento breaké um erro de sintaxe ...: \
KutuluMike

1
O detalhamento com o código real é frequentemente encontrado nas implementações do analisador. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
precisa saber é o seguinte

1
@ BlueRaja-DannyPflughoeft: C também foi projetado para ser fácil de compilar. “Emitir um salto se breakestiver presente em qualquer lugar” é uma regra significativamente mais simples de implementar do que “Não emitir um salto se fallthroughestiver presente em um switch”.
31412 Jon Purdy

Respostas:


95

C foi um dos primeiros idiomas a ter a switchdeclaração nesse formato, e todos os outros idiomas principais a herdaram a partir daí, optando principalmente por manter a semântica C por padrão - eles não pensaram nas vantagens de alterá-la ou julgaram menos importante do que manter o comportamento com o qual todos estavam acostumados.

Quanto ao motivo pelo qual o C foi projetado dessa maneira, provavelmente deriva do conceito de C como "montagem portátil". A switchinstrução é basicamente uma abstração de uma tabela de ramificação , e uma tabela de ramificação também possui uma queda implícita e requer uma instrução de salto adicional para evitá-la.

Então, basicamente, os designers de C também escolheram manter a semântica do assembler por padrão.


3
O VB.NET é exemplo de um idioma que o mudou. Não há perigo de que ele flua para a cláusula case.
Poke

1
Tcl é outra língua que nunca se aplica à próxima cláusula. Praticamente nunca quis fazer isso (ao contrário de C, onde é útil ao escrever certos tipos de programas).
Donal Fellows

4
Os idiomas ancestrais de C, B e o BCPL mais antigo , possuem (tiveram?) Instruções de troca; BCPL soletrou isto switchon.
28512 Keith Thompson

As declarações de caso de Ruby não caem; você pode obter um comportamento semelhante tendo dois valores de teste em um único when.
Nathan Long

86

Porque switchnão é uma alternativa de if ... elsedeclarações nesses idiomas.

Ao usar switch, podemos combinar mais de uma condição por vez, o que é muito apreciado em alguns casos.

Exemplo:

public Season SeasonFromMonth(Month month)
{
    Season season;
    switch (month)
    {
        case Month.December:
        case Month.January:
        case Month.February:
            season = Season.Winter;
            break;

        case Month.March:
        case Month.April:
        case Month.May:
            season = Season.Spring;
            break;

        case Month.June:
        case Month.July:
        case Month.August:
            season = Season.Summer;
            break;

        default:
            season = Season.Autumn;
            break;
    }

    return season;
}

15
more than one block ... unwanted in most casesEu não estou de acordo com você.
Satish Pandey

2
@DanielB Depende do idioma; As instruções de opção C # não permitem essa possibilidade.
288 Andy

5
@ Andy Estamos falando de queda ainda? As instruções de opção C # definitivamente permitem isso (verifique o terceiro exemplo), daí a necessidade break. Mas sim, tanto quanto sei, o dispositivo de Duff não funciona em c #.
28512 Daniel B

8
@DanielB Sim, somos, mas o compilador não permitirá uma condição, código e outra condição sem interrupção. Você pode ter várias condições empilhadas sem código entre ou precisa de uma pausa. Do seu link C# does not support an implicit fall through from one case label to another. Você pode fracassar, mas não há chance de esquecer acidentalmente uma pausa.
218 Andy

3
@ Matsemann .. Agora podemos fazer todas as coisas usando, if..elsemas em algumas situações switch..caseseria preferível. O exemplo acima seria um pouco complexo se usarmos if-else.
Satish Pandey

15

Isso foi perguntado no Stack Overflow no contexto de C: Por que a instrução switch foi projetada para precisar de uma interrupção?

Para resumir a resposta aceita, provavelmente foi um erro. A maioria das outras linguagens provavelmente apenas seguiu o C. No entanto, algumas linguagens como o C # parecem ter corrigido isso ao permitir falhas - mas somente quando o programador o explicita (fonte: link acima, eu não falo C #) .


O Google Go faz o mesmo com o IIRC (permitindo o avanço se especificado explicitamente).
28312 Leo

PHP também. E quanto mais você olha o código resultante, mais desconfortável ele se sente. Gosto do /* FALLTHRU */comentário na resposta vinculada pelo @Tapio acima, para provar que você quis dizer isso.
DaveP

1
Dizer que C # tem goto case, como o link faz, não ilustra por que isso funciona tão bem. Você ainda pode empilhar vários casos, como acima, mas assim que inserir o código, você precisará ter um explícito goto case whateverpara passar para o próximo caso ou obter um erro no compilador. Se você me perguntar, uma das duas, a capacidade de empilhar casos sem se preocupar com falhas inadvertidas por negligência é de longe um recurso melhor do que permitir uma falha explícita goto case.
Jesper

9

Eu responderei com um exemplo. Se você deseja listar o número de dias para cada mês do ano, é óbvio que alguns meses têm 31, 30 e 1 28/29. Ficaria assim,

switch(month) {
    case 4:
    case 6:
    case 9:
    case 11;
        days = 30;
        break;
    case 2:
        //Find out if is leap year( divisible by 4 and all that other stuff)
        days = 28 or 29;
        break;
    default:
        days = 31;
}

Este é um exemplo em que vários casos têm o mesmo efeito e são todos agrupados. Obviamente, havia uma razão para a escolha da palavra-chave break e não a construção if ... else if .

O principal a ser observado aqui é que uma declaração switch com muitos casos semelhantes não é um if ... else se ... else if ... else para cada um dos casos, mas sim se (1, 2, 3) ... senão se (4,5,6) senão ...


2
Seu exemplo parece mais detalhado do que um ifsem nenhum benefício. Talvez se seu exemplo atribuísse um nome ou fizesse um trabalho específico para o mês além do fall-through, a utilidade do fall-through seria mais aparente? (sem comentar sobre se se deve, em primeiro lugar)
horatio

1
Isso é verdade. No entanto, eu estava apenas tentando mostrar casos em que a queda poderia ser usada. Obviamente, seria muito melhor se cada um dos meses precisasse de algo feito individualmente.
Awemo 29/08/2012

8

Há duas situações em que pode ocorrer a ocorrência de um caso para outro - o caso vazio:

switch ( ... )
{
  case 1:
  case 2:
    do_something_for_1_and_2();
    break;
  ...
}

e o caso não vazio

switch ( ... )
{
  case 1:
    do_something_for_1();
    /* Deliberately fall through */
  case 2:
    do_something_for_1_and_2();
    break;
...
}

Não obstante a referência ao dispositivo de Duff , as instâncias legítimas para o segundo caso são poucas e distantes entre si, e geralmente proibidas pelos padrões de codificação e sinalizadas durante a análise estática. E onde é encontrado, é mais frequentemente do que não devido à omissão de a break.

O primeiro é perfeitamente sensato e comum.

Para ser sincero, não vejo razão para precisar do breakanalisador e do idioma, sabendo que um corpo de caso vazio é uma falha, enquanto um caso não vazio é autônomo.

É uma pena que o painel ISO C pareça mais preocupado em adicionar recursos novos (indesejados) e mal definidos ao idioma, em vez de corrigir os recursos indefinidos, não especificados ou definidos pela implementação, sem mencionar os ilógicos.


2
Graças a Deus, a ISO C não é para introduzir mudanças no idioma!
Jack Aidley 13/03

4

Não forçar o breakpermite uma série de coisas que poderiam ser difíceis de fazer. Outros observaram casos de agrupamento, para os quais existem vários casos não triviais.

Um caso em que é imperativo que o breaknão seja usado é o dispositivo de Duff . Isso é usado para " desenrolar " loops, onde pode acelerar as operações, limitando o número de comparações necessárias. Acredito que o uso inicial permitia funcionalidades que antes eram muito lentas com um loop totalmente enrolado. Ele troca o tamanho do código pela velocidade em alguns casos.

É uma boa prática substituir o breakcomentário por um apropriado, se o caso tiver algum código. Caso contrário, alguém irá corrigir os desaparecidos breake introduzir um erro.


Obrigado pelo exemplo de dispositivo da Duff (+1). Eu não estava ciente disso.
dez-

3

Em C, onde a origem parece estar, o bloco de código da switchinstrução não é uma construção especial. É um bloco de código normal, assim como um bloco em uma ifinstrução.

switch ()
{
}

if ()
{
}

casee defaultsão etiquetas de salto dentro deste bloco, especificamente relacionadas a switch. Eles são tratados da mesma maneira que os rótulos de salto normais goto. Há uma regra específica importante aqui: os rótulos de salto podem estar em quase toda parte do código, sem interromper o fluxo do código.

Como um bloco de código normal, ele não precisa ser uma instrução composta. Os rótulos também são opcionais. Estas são switchinstruções válidas em C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

O próprio padrão C fornece isso como um exemplo (6.8.4.2):

switch (expr) 
{ 
    int i = 4; 
    f(i); 
  case 0: 
    i=17; 
    /*falls through into default code */ 
  default: 
    printf("%d\n", i); 
} 

No fragmento de programa artificial, o objeto cujo identificador é i existe com duração automática de armazenamento (dentro do bloco), mas nunca é inicializado; portanto, se a expressão de controle tiver um valor diferente de zero, a chamada para a função printf acessará um valor indeterminado. Da mesma forma, a chamada para a função f não pode ser alcançada.

Além disso, também defaulté um rótulo de salto e, portanto, pode estar em qualquer lugar, sem a necessidade de ser o último caso.

Isso também explica o dispositivo de Duff:

switch (count % 8) {
    case 0: do {  *to = *from++;
    case 7:       *to = *from++;
    case 6:       *to = *from++;
    case 5:       *to = *from++;
    case 4:       *to = *from++;
    case 3:       *to = *from++;
    case 2:       *to = *from++;
    case 1:       *to = *from++;
            } while(--n > 0);
}

Por que a queda? Como no fluxo de código normal em um bloco de código normal, é esperado o fall-through para a próxima instrução , assim como você esperaria em um ifbloco de código.

if (a == b)
{
    Foo();
    /* "Fall-through" to Bar expected here. */
    Bar();
}

switch (a)
{
    case 1: 
        Foo();
        /* Automatic break would violate expected code execution semantics. */
    case 2:
        Bar();
}

Meu palpite é que o motivo disso foi a facilidade de implementação. Você não precisa de código especial para analisar e compilar um switchbloco, cuidando de regras especiais. Você apenas o analisa como qualquer outro código e precisa cuidar apenas dos rótulos e da seleção de salto.

Uma questão interessante de acompanhamento disso tudo é se as seguintes declarações aninhadas imprimirem "Concluído". ou não.

int a = 10;

switch (a)
{
    switch (a)
    {
        case 10: printf("Done.\n");
    }
}

O padrão C cuida disso (6.8.4.2.4):

Um rótulo de caixa ou padrão é acessível apenas na declaração da chave anexa mais próxima.


1

Várias pessoas já mencionaram a noção de combinar várias condições, o que é muito valioso de tempos em tempos. No entanto, a capacidade de corresponder a várias condições não requer necessariamente que você faça exatamente a mesma coisa com cada condição que corresponda. Considere o seguinte:

switch (someCase)
{
    case 1:
    case 2:
        doSomething1And2();
        break;

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

    default:
        throw new Error("Invalid Case");
}

Existem duas maneiras diferentes pelas quais conjuntos de várias condições são correspondidos aqui. Nas condições 1 e 2, eles simplesmente caem no mesmo lote de código e fazem exatamente a mesma coisa. Com as condições 3 e 4, no entanto, embora ambas terminem chamando doSomething3And4(), apenas 3 chamam doSomething3().


Atrasado para a festa, mas isso absolutamente não é "1 e 2", como o nome da função sugere: existem três estados diferentes que podem levar ao código no case 2acionamento, portanto, se queremos estar corretos (e fazemos) o real o nome da função teria que ser doSomethingBecause_1_OR_2_OR_1AND2()ou algo assim (embora o código legítimo no qual um imutável corresponda a dois casos diferentes enquanto ainda esteja sendo bem escrito seja praticamente inexistente, portanto, pelo menos isso deve ser doSomethingBecause1or2())
Mike 'Pomax' Kamermans

Em outras palavras doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(),. Não se refere a lógica e / ou.
Panzercrisis 12/12

Eu sei que não, mas dado o motivo pelo qual as pessoas ficam confusas com esse tipo de código, deve explicar absolutamente o que é isso "e", porque confiar em alguém para adivinhar "final natural e" vs. "final lógico" em uma resposta que só funciona quando explicado com precisão, precisa de mais explicações na resposta em si, ou uma reescrita do nome da função para remover essa ambiguidade =)
Mike 'Pomax' Kamermans

1

Para responder a duas de suas perguntas.

Por que C precisa de pausas?

Tudo se resume às raízes do Cs como um "montador portátil". Onde o código psudo como este era comum: -

    targets=(addr1,addr2,addr3);
    opt = 1  ## or 0 or 2
switch:
    br targets[opt]  ## go to addr2 
addr1:
    do 1stuff
    br switchend
addr2:
    do 2stuff
    br switchend
addr3
    do 3stuff
switchend:
    ......

a instrução switch foi projetada para fornecer funcionalidade semelhante em um nível superior.

Nós sempre temos interruptores sem interrupções?

Sim, isso é bastante comum e existem alguns casos de uso;

Primeiramente, você pode querer executar a mesma ação em vários casos. Fazemos isso empilhando os casos uns sobre os outros:

case 6:
case 9:
    // six or nine code

Outro caso de uso comum em máquinas de estado é que, após o processamento de um estado, queremos entrar e processar imediatamente outro estado:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;

-2

A declaração de interrupção é uma declaração de salto que permite ao usuário sair do comutador anexo mais próximo (para o seu caso), enquanto, do, para ou foreach. é tão fácil quanto isso.


-3

Eu acho que, com tudo o que foi escrito acima - o principal resultado desse segmento é que, se você deseja criar um novo idioma - o padrão deve ser que você não precise adicionar uma breakdeclaração e o compilador a tratará como se você o fizesse.

Se você deseja aquele caso raro em que deseja continuar para o próximo caso - basta declarar com uma continuedeclaração.

Isso pode ser aprimorado para que somente se você usar colchetes dentro do gabinete, ele não continuará, para que o exemplo com os meses acima de vários casos executando o mesmo código exato - funcione sempre como esperado sem a necessidade de continue.


2
Não sei se entendi sua sugestão em relação à declaração continue. Continue tem um significado muito diferente em C e C ++ e, se um novo idioma fosse como C, mas usasse a palavra-chave algumas vezes como em C e outras desta maneira alternativa, acho que reinaria a confusão. Embora possa haver alguns problemas com a queda de um bloco rotulado para outro, eu gosto do uso de vários casos com o mesmo tratamento com um bloco de código compartilhado.
DeveloperDon

C tem uma longa tradição de usar as mesmas palavras-chave para vários propósitos. Pense *, &, staticetc.
idrougge
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.