Declaração de mudança - Switch statement

Em linguagens de programação de computador , uma instrução switch é um tipo de mecanismo de controle de seleção usado para permitir que o valor de uma variável ou expressão mude o fluxo de controle da execução do programa por meio de pesquisa e mapa.

As instruções switch funcionam de forma semelhante à ifinstrução usada em linguagens de programação como C / C ++ , C # , Visual Basic .NET , Java e existe na maioria das linguagens de programação imperativas de alto nível , como Pascal , Ada , C / C ++ , C # , Visual Basic. NET , Java , e em muitos outros tipos de linguagem, usando essas palavras-chave como switch, case, selectou inspect.

As instruções switch vêm em duas variantes principais: uma opção estruturada, como em Pascal, que usa exatamente um ramo, e uma opção não estruturada, como em C, que funciona como um tipo de goto . Os principais motivos para usar um switch incluem melhorar a clareza, reduzindo a codificação repetitiva e (se a heurística permitir) também oferecer o potencial de execução mais rápida por meio da otimização do compilador mais fácil em muitos casos.

Declaração de mudança em C
switch (age) {
  case 1:  printf("You're one.");            break;
  case 2:  printf("You're two.");            break;
  case 3:  printf("You're three.");
  case 4:  printf("You're three or four.");  break;
  default: printf("You're not 1,2,3 or 4!");
}

História

Em seu texto de 1952, Introdução à Metamatemática , Stephen Kleene provou formalmente que a função CASE (a função IF-THEN-ELSE sendo sua forma mais simples) é uma função recursiva primitiva , onde ele define a noção definition by casesda seguinte maneira:

"#F. A função φ assim definida
φ (x 1 , ..., x n ) =
  • φ 1 (x 1 , ..., x n ) se Q 1 (x 1 , ..., x n ),
  • . . . . . . . . . . . .
  • φ m (x 1 , ..., x n ) se Q m (x 1 , ..., x n ),
  • φ m + 1 (x 1 , ..., x n ) caso contrário,
onde Q 1 , ..., Q m são predicados mutuamente exclusivos (ou φ (x 1 , ..., x n ) deve ter o valor dado pela primeira cláusula que se aplica) é recursivo primitivo em φ 1 , ... , φ m + 1 , Q 1 , ..., Q m + 1 .

Kleene fornece uma prova disso em termos das funções recursivas do tipo booleano "sinal de" sg () e "não sinal de" ~ sg () (Kleene 1952: 222-223); o primeiro retorna 1 se sua entrada for positiva e -1 se sua entrada for negativa.

Boolos-Burgess-Jeffrey faz a observação adicional de que a "definição por casos" deve ser mutuamente exclusiva e coletivamente exaustiva. Eles também oferecem uma prova da recursividade primitiva dessa função (Boolos-Burgess-Jeffrey 2002: 74-75).

O IF-THEN-ELSE é a base do formalismo McCarthy : seu uso substitui a recursão primitiva e o operador mu .

Sintaxe típica

Na maioria das linguagens, os programadores escrevem uma instrução switch em muitas linhas individuais usando uma ou duas palavras-chave. Uma sintaxe típica envolve:

  • o primeiro select, seguido por uma expressão que é frequentemente referida como a expressão de controle ou variável de controle da instrução switch
  • linhas subsequentes definindo os casos reais (os valores), com sequências correspondentes de instruções para execução quando ocorre uma correspondência
  • Em linguagens com comportamento de fuga, uma breakdeclaração normalmente segue uma casedeclaração para encerrar essa declaração. [Poços]

Cada alternativa começa com o valor específico, ou lista de valores (veja abaixo), que a variável de controle pode corresponder e que fará com que o controle para a sequência de instruções correspondente. O valor (ou lista / intervalo de valores) é geralmente separado da sequência de instrução correspondente por dois pontos ou por uma seta de implicação. Em muitos idiomas, cada caso também deve ser precedido por uma palavra-chave como caseou when.

Um caso padrão opcional normalmente é também permitido, especificado por uma default, otherwiseou elsepalavra-chave. Isso é executado quando nenhum dos outros casos corresponde à expressão de controle. Em algumas linguagens, como C, se não houver correspondência de maiúsculas e minúsculas e defaultfor omitido, a switchinstrução simplesmente será encerrada. Em outros, como PL / I, é gerado um erro.

Semântica

Semanticamente, existem duas formas principais de instruções switch.

A primeira forma são switches estruturados, como em Pascal, onde exatamente uma ramificação é obtida e os casos são tratados como blocos exclusivos separados. Isso funciona como uma condicional if – then – else generalizada , aqui com qualquer número de ramificações, não apenas duas.

A segunda forma são switches não estruturados, como em C, onde os casos são tratados como rótulos dentro de um único bloco, e o switch funciona como um goto generalizado. Essa distinção é conhecida como o tratamento da queda, que é elaborada a seguir.

Cair em

Em muitas linguagens, apenas o bloco correspondente é executado e, em seguida, a execução continua no final da instrução switch. Estes incluem a família Pascal (Object Pascal, Modula, Oberon, Ada, etc.), bem como PL / I , formas modernas de Fortran e dialetos BASIC influenciados por Pascal, a maioria das linguagens funcionais e muitos outros. Para permitir que vários valores executem o mesmo código (e evitar a necessidade de duplicar o código ), as linguagens do tipo Pascal permitem qualquer número de valores por caso, fornecidos como uma lista separada por vírgulas, como um intervalo ou como uma combinação.

Linguagens derivadas da linguagem C, e mais geralmente aquelas influenciadas pelo GOTO calculado de Fortran , em vez disso apresentam fallthrough, onde o controle se move para o caso correspondente e, em seguida, a execução continua ("falha") para as instruções associadas ao próximo caso no texto de origem . Isso também permite que vários valores correspondam ao mesmo ponto sem qualquer sintaxe especial: eles são apenas listados com corpos vazios. Os valores podem ser condicionados especialmente com código no corpo do caso. Na prática, o fallthrough é geralmente evitado com uma breakpalavra-chave no final do corpo correspondente, que fecha a execução do bloco switch, mas isso pode causar erros devido ao fallthrough não intencional se o programador se esquecer de inserir a breakinstrução. Isso é, portanto, visto por muitos como uma verruga de linguagem e advertido em algumas ferramentas de fiapos. Sintaticamente, os casos são interpretados como rótulos, não blocos, e as instruções switch e break alteram explicitamente o fluxo de controle. Algumas linguagens influenciadas por C, como JavaScript , mantêm o fallthrough padrão, enquanto outras removem o fallthrough ou apenas o permitem em circunstâncias especiais. Variações notáveis ​​sobre isso na família C incluem C # , em que todos os blocos devem ser encerrados com um breakou a returnmenos que o bloco esteja vazio (ou seja, o fallthrough é usado como uma forma de especificar vários valores).

Em alguns casos, os idiomas fornecem um fallthrough opcional. Por exemplo, Perl não falha por padrão, mas um caso pode explicitamente fazer isso usando uma continuepalavra - chave. Isso evita falhas não intencionais, mas permite quando desejado. Da mesma forma, o padrão do Bash não ;;falha quando termina com , mas permite falhas com ;&ou em ;;&vez disso.

Um exemplo de uma instrução switch que depende de fallthrough é o dispositivo de Duff .

Compilação

A otimização de compiladores como GCC ou Clang pode compilar uma instrução switch em uma tabela de ramificação ou em uma pesquisa binária por meio dos valores nos casos. Uma tabela de ramificação permite que a instrução switch determine com um número pequeno e constante de instruções qual ramificação executar sem ter que passar por uma lista de comparações, enquanto uma pesquisa binária leva apenas um número logarítmico de comparações, medido no número de casos em a instrução switch.

Normalmente, o único método de descobrir se essa otimização ocorreu é realmente olhando para a montagem resultante ou saída de código de máquina que foi gerada pelo compilador.

Vantagens e desvantagens

Em algumas linguagens e ambientes de programação, o uso de um caseou switchdeclaração é considerada superior a uma série equivalentes de se else if declarações porque ele é:

  • Mais fácil de depurar (por exemplo, definir pontos de interrupção no código versus uma tabela de chamadas, se o depurador não tiver capacidade de ponto de interrupção condicional)
  • Mais fácil para uma pessoa ler
  • Mais fácil de entender e, conseqüentemente, mais fácil de manter
  • Profundidade fixa: uma sequência de instruções "if else if" pode gerar aninhamento profundo, tornando a compilação mais difícil (especialmente em código gerado automaticamente)
  • Mais fácil de verificar se todos os valores foram tratados. Os compiladores podem emitir um aviso se alguns valores de enum não forem tratados.

Além disso, uma implementação otimizada pode ser executada muito mais rápido do que a alternativa, porque geralmente é implementada usando uma tabela de ramificação indexada . Por exemplo, decidir o fluxo do programa com base no valor de um único caractere, se implementado corretamente, é muito mais eficiente do que a alternativa, reduzindo consideravelmente o comprimento do caminho de instrução . Quando implementado como tal, uma instrução switch torna-se essencialmente um hash perfeito .

Em termos de gráfico de fluxo de controle , uma instrução switch consiste em dois nós (entrada e saída), mais uma aresta entre eles para cada opção. Em contraste, uma sequência de declarações "if ... else if ... else if" possui um nó adicional para cada caso, exceto o primeiro e o último, junto com uma aresta correspondente. O gráfico de fluxo de controle resultante para as sequências de "se" s, portanto, tem muito mais nós e quase o dobro de arestas, com estes não adicionando nenhuma informação útil. No entanto, as ramificações simples nas instruções if são individualmente conceitualmente mais fáceis do que as ramificações complexas de uma instrução switch. Em termos de complexidade ciclomática , ambas as opções aumentam em k -1 se dados k casos.

Mudar de expressão

Expressões de switch são introduzidas em Java SE 12 , 19 de março de 2019, como um recurso de visualização. Aqui, uma expressão de switch inteira pode ser usada para retornar um valor. Também há uma nova forma de rótulo de caso, em case L->que o lado direito é uma expressão única. Isso também evita quedas e exige que os casos sejam exaustivos. No Java SE 13, a yieldinstrução é introduzida e, no Java SE 14, as expressões de switch tornam-se um recurso de linguagem padrão. Por exemplo:

int ndays = switch(month) {
    case JAN, MAR, MAY, JUL, AUG, OCT, DEC -> 31;
    case APR, JUN, SEP, NOV -> 30;
    case FEB -> {
        if(year % 400 ==0) yield 29;
        else if(year % 100 == 0) yield 28;
        else if(year % 4 ==0) yield 29;
        else yield 28; }
};

Usos alternativos

Muitas linguagens avaliam expressões dentro de switchblocos em tempo de execução, permitindo uma série de usos menos óbvios para a construção. Isso proíbe certas otimizações do compilador, portanto, é mais comum em linguagens dinâmicas e de script em que a flexibilidade aprimorada é mais importante do que a sobrecarga de desempenho.

PHP

Por exemplo, em PHP , uma constante pode ser usada como a "variável" a ser verificada, e a primeira instrução case avaliada para essa constante será executada:

switch (true) {
    case ($x == 'hello'):
        foo();
        break;
    case ($z == 'howdy'): break;
}
switch (5) {
    case $x: break;
    case $y: break;
}

Esse recurso também é útil para verificar várias variáveis ​​em relação a um valor, em vez de uma variável em relação a muitos valores. COBOL também suporta este formulário (e outros formulários) na EVALUATEdeclaração. PL / I tem uma forma alternativa de SELECTinstrução em que a expressão de controle é totalmente omitida e a primeira WHENque é avaliada como verdadeira é executada.

Rubi

Em Ruby , devido ao seu tratamento de ===igualdade, a instrução pode ser usada para testar a classe da variável:

case input
when Array then puts 'input is an Array!'
when Hash then puts 'input is a Hash!'
end

Ruby também retorna um valor que pode ser atribuído a uma variável e, na verdade, não exige que o casetenha nenhum parâmetro (agindo um pouco como uma else ifinstrução):

catfood =
  case
  when cat.age <= 1
    junior
  when cat.age > 10
    senior
  else
    normal
  end

Montador

Uma instrução switch em linguagem assembly :

switch:
  cmp ah, 00h
  je a
  cmp ah, 01h
  je b
  jmp swtend   ; No cases match or "default" code here
a:
  push ah
  mov al, 'a'
  mov ah, 0Eh
  mov bh, 00h
  int 10h
  pop ah
  jmp swtend   ; Equivalent to "break"
b:
  push ah
  mov al, 'b'
  mov ah, 0Eh
  mov bh, 00h
  int 10h
  pop ah
  jmp swtend   ; Equivalent to "break"
  ...
swtend:

Manipulação de exceção

Várias linguagens implementam uma forma de instrução switch no tratamento de exceções , onde se uma exceção for gerada em um bloco, um branch separado é escolhido, dependendo da exceção. Em alguns casos, um branch padrão, se nenhuma exceção for levantada, também está presente. Um exemplo inicial é o Modula-3 , que usa a sintaxe TRY... EXCEPT, onde cada EXCEPTum define um caso. Isso também é encontrado em Delphi , Scala e Visual Basic.NET .

Alternativas

Algumas alternativas para alternar declarações podem ser:

  • Uma série de condicionais if-else que examinam o valor de destino um de cada vez. O comportamento de queda pode ser alcançado com uma sequência de condicionais if , cada uma sem a cláusula else .
  • Uma tabela de pesquisa , que contém, como chaves, os casevalores e, como valores, a parte sob a caseinstrução.
(Em algumas linguagens, apenas os tipos de dados reais são permitidos como valores na tabela de pesquisa. Em outras linguagens, também é possível atribuir funções como valores da tabela de pesquisa, ganhando a mesma flexibilidade de uma switchinstrução real . Consulte o artigo da tabela de controle para obter mais detalhes nisto).
Lua não oferece suporte a instruções case / switch: http://lua-users.org/wiki/SwitchStatement . Essa técnica de pesquisa é uma forma de implementar switchinstruções na linguagem Lua, que não possui embutidos switch.
Em alguns casos, as tabelas de pesquisa são mais eficientes do que as instruções não otimizadas switch , pois muitas linguagens podem otimizar as pesquisas na tabela, enquanto as instruções switch não são otimizadas, a menos que o intervalo de valores seja pequeno com poucas lacunas. Uma pesquisa de pesquisa não otimizada e não binária , entretanto, quase certamente será mais lenta do que uma chave não otimizada ou as declarações if-else múltiplas equivalentes .
  • Uma tabela de controle (que pode ser implementada como uma tabela de pesquisa simples) também pode ser personalizada para acomodar várias condições em várias entradas, se necessário, e geralmente exibe maior 'compactação visual' do que uma chave equivalente (que pode ocupar muitas instruções).
  • Correspondência de padrões , que é usada para implementar a funcionalidade do tipo switch em muitas linguagens funcionais .

Veja também

Referências

Leitura adicional