Por que temos que usar o intervalo no switch?

65

Quem decidiu, e baseando-se em quais conceitos, que switch construção (em muitas línguas) tem que ser, como é? Por que temos que usar break em cada declaração? Por que temos que escrever algo assim:

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

(Eu notei essa construção em PHP e JS, mas provavelmente há muitas outras linguagens que a utilizam)

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

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

Diz-se que break impede a execução de blocos após o atual. Mas, alguém realmente se depara com uma situação em que houve alguma necessidade de execução do bloco atual e dos seguintes? Eu não fiz. Para mim, break está sempre lá. Em todo bloco. Em todo código.

    
por trejder 28.08.2012 / 11:16
fonte

11 respostas

89

C foi uma das primeiras linguagens a ter a declaração switch nesta forma, e todas as outras principais linguagens a herdaram de lá, a maioria preferindo manter a semântica C por padrão - elas não pensavam nas vantagens de mudá-lo ou julgá-lo menos importante do que manter o comportamento ao 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 instrução switch é basicamente uma abstração de uma tabela de ramificação , e uma tabela de ramificação também tem um entrelinha implícito e requer uma instrução de salto adicional para evitá-lo.

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

    
por 28.08.2012 / 12:40
fonte
82

Porque switch não é uma alternativa de if ... else declarações nesses idiomas.

Usando switch , podemos combinar mais de uma condição por vez, o que é altamente 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;
}
    
por 28.08.2012 / 11:21
fonte
13

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

Para resumir a resposta aceita, provavelmente foi um erro. A maioria das outras linguagens provavelmente apenas seguiu C. No entanto, algumas linguagens como C # parecem ter corrigido isso, permitindo fall-through - mas apenas quando o programador explicitamente diz isso (fonte: o link acima, eu não falo C # eu mesmo) .

    
por 28.08.2012 / 11:36
fonte
9

Eu responderei com um exemplo. Se você quisesse listar o número de dias para cada mês de um ano, é óbvio que alguns meses têm 31, 30 e 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 agrupados em conjunto. Obviamente, havia uma razão para a escolha da palavra-chave break e não a if ... else if construct.

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

    
por 28.08.2012 / 11:50
fonte
8

Existem duas situações em que "passar por" de um caso para outro pode acontecer - 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 a Dispositivo de Duff , instâncias legítimas para o segundo caso são poucas e distantes entre si e geralmente proibido por padrões de codificação e sinalizado durante a análise estática. E onde é encontrado, é mais frequentemente do que não devido à omissão de um break .

O primeiro é perfeitamente sensato e comum.

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

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

    
por 28.08.2012 / 12:09
fonte
4

Não forçar o break permite várias coisas que poderiam ser difíceis de serem feitas. Outros observaram casos de agrupamento, para os quais existem vários casos não triviais.

Um caso em que é imperativo que o break não seja usado é o Dispositivo do Duff . Isso é usado para " desenrolar " loops onde ele pode acelerar as operações, limitando o número de comparações necessárias. Acredito que o uso inicial permitiu a funcionalidade que anteriormente era muito lenta com um loop totalmente acumulado. Ele troca de tamanho de código por velocidade em alguns casos.

É uma boa prática substituir o break por um comentário apropriado, se o caso tiver algum código. Caso contrário, alguém corrigirá o break ausente e apresentará um erro.

    
por 29.08.2012 / 05:20
fonte
2

Em C, onde a origem parece ser, o bloco de código da instrução switch não é uma construção especial. É um bloco de código normal, assim como um bloco sob uma instrução if .

switch ()
{
}

if ()
{
}

case e default são marcadores de salto dentro desse bloco, especificamente relacionados a switch . Eles são manipulados como rótulos de salto normais para goto . Há uma regra específica que é importante aqui: os rótulos de salto podem estar em praticamente todos os lugares no código, sem interromper o fluxo de código.

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

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

O próprio padrão C dá 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); 
} 

In the artificial program fragment, the object whose identifier is i exists with automatic storage duration (within the block) but is never initialized, and thus if the controlling expression has a nonzero value, the call to the printf function will access an indeterminate value. Similarly, the call to the function f cannot be reached.

Além disso, default é um rótulo de salto também 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 o fall-through? Como no fluxo de código normal em um bloco de código normal, o fall-through para a próxima instrução é esperado , exatamente como você esperaria em um bloco de código if .

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 a razão para isso foi a facilidade de implementação. Você não precisa de código especial para analisar e compilar um bloco switch , cuidando de regras especiais. Você apenas analisa como qualquer outro código e só precisa se preocupar com os rótulos e a seleção de pulos.

Uma interessante questão de acompanhamento disso tudo é se as seguintes instruçõ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):

A case or default label is accessible only within the closest enclosing switch statement.

    
por 29.08.2012 / 07:49
fonte
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 exige necessariamente que você faça exatamente a mesma coisa com cada condição correspondente. 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 de combinar várias condições aqui. Com as condições 1 e 2, elas 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() , somente 3 chama doSomething3() .

    
por 18.12.2014 / 16:53
fonte
1

Para responder a duas das 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 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 mais alto.

Nós temos interruptores sem intervalos?

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

Em primeiro lugar, você pode querer realizar a mesma ação em vários casos. Fazemos isso empilhando os casos uns em cima dos 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 inserir e processar imediatamente outro estado:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;
    
por 18.12.2014 / 09:51
fonte
-2

A instrução break é uma instrução saltadora que permite ao usuário sair da opção de fechamento mais próxima (no seu caso), while, do, foreach ou foreach. é tão fácil assim.

    
por 28.08.2012 / 19:39
fonte
-3

Eu acho que com tudo escrito acima - o principal resultado deste tópico é que, se você for projetar um novo idioma - o padrão deve ser que você não precisa adicionar uma instrução break eo compilador tratará disso. como se você fizesse.

Se você quiser aquele caso raro em que deseja continuar para o próximo caso, basta declará-lo com uma instrução continue .

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

    
por 05.09.2012 / 07:39
fonte

Tags