Articulo de referencia

Sentencia switch

En programación informática , una instrucción switch es un mecanismo de control de flujo de selección que cambia el control de ejecución en función del valor de una expresión (e...

En programación informática , una instrucción switch es un mecanismo de control de flujo de selección que cambia el control de ejecución en función del valor de una expresión (es decir, la evaluación de una variable ). Una instrucción switch es similar a una instrucción if , pero en lugar de ramificarse solo en verdadero o falso, se ramifica en cualquier número de valores. Aunque la sintaxis varía según el lenguaje de programación , la mayoría de los lenguajes imperativos proporcionan una instrucción con la semántica descrita aquí como la instrucción switch. A menudo denotada con la palabra claveswitch , algunos lenguajes usan variaciones como case, select, o inspect.

Valor

En ocasiones, el uso de una instrucción switch se considera superior a una serie equivalente de instrucciones if-then-else porque:

Más fácil de entender
Y, por consiguiente, es más fácil de mantener: al menos en parte, ya que tiene una profundidad fija.
Más fácil de depurar
Por ejemplo, establecer puntos de interrupción en el código en lugar de en una tabla de llamadas, si el depurador no tiene capacidad para establecer puntos de interrupción condicionales.
Más fácil de verificar
que todos los valores se procesen correctamente, ya que un compilador puede advertir si un valor no se procesa adecuadamente.
Puede ejecutarse más rápido
Una implementación optimizada puede ejecutarse mucho más rápido porque a menudo se implementa como una tabla de ramificación . [ 1 ] Cuando se implementa de esta manera, una instrucción switch incorpora un hash perfecto .
Un compilador optimizador como GCC o Clang puede compilar una instrucción switch en una tabla de ramificación o en una búsqueda binaria . [ 2 ] Una tabla de ramificación permite que el programa determine qué rama ejecutar con un solo cálculo en lugar de comparar valores en una secuencia. Una búsqueda binaria solo requiere un número logarítmico de comparaciones, medido en el número de casos en la instrucción switch. Generalmente, la única forma de determinar si el código se optimizó de esta manera es analizando la salida del compilador, como el código ensamblador o el código máquina .
Menos complejo
En términos de un grafo de flujo de control , una instrucción switch consta de dos nodos (entrada y salida), más una arista entre ellos para cada opción. Por el contrario, una secuencia de instrucciones if-then-else tiene un nodo adicional para cada caso que no sea el primero ni el último, junto con una arista correspondiente. El grafo de flujo de control resultante para las secuencias de "if" tiene, por lo tanto, muchos más nodos y casi el doble de aristas, sin añadir información útil.

Elementos

Normalmente, una instrucción switch implica:

Verbo
Comienza con un verbo de control como selectque va seguido de una expresión que a menudo es un nombre de variable; la expresión de control o variable de control .
Casos
Las secciones alternativas de ramificación subsiguientes comienzan cada una con una palabra clave (es decir, case) más un valor (o varios valores) junto con el código que se ejecutará para el/los valor/es. En algunos lenguajes, como PL/I y Rexx , si se omite la expresión de control, cada alternativa comienza con una whencláusula que contiene una expresión booleana y se produce una coincidencia para el primer caso en el que dicha expresión se evalúa como verdadera; similar a una estructura if-then-else.
En un lenguaje con comportamiento de continuación secuencial, como C, cada sección termina con una palabra clave (como break) si esa sección no debe continuar secuencialmente.
Por defecto
Normalmente se permite un caso predeterminado opcional, a menudo mediante una palabra clave como default, otherwise, o else. El control salta a esta sección cuando ninguno de los otros casos coincide con la expresión de control. En algunos lenguajes, como C, si ningún caso coincide y se omite la sección predeterminada, la instrucción no hace nada, pero en otros, como PL/I, se produce un error.

Fracasar

Existen dos variantes principales de la instrucción switch: la no estructurada , que admite la ejecución secuencial, y la estructurada , que no.

En una estructura switch, como en lenguajes tipo Pascal , el control salta del inicio de la instrucción switch al caso seleccionado y, al final del caso, salta al final de la instrucción switch. Esto se comporta como una condicional if-then-else , pero admite ramificaciones con valores distintos a verdadero y falso. Para permitir que varios valores ejecuten el mismo código (evitando código duplicado ), la sintaxis permite varios valores por caso.

Una instrucción switch no estructurada, como en C (y, en general, en lenguajes influenciados por el goto calculado de Fortran ), funciona como goto . El control se ramifica desde el inicio de la instrucción switch hacia una sección case y luego continúa hasta una instrucción de salida de bloque o el final de la instrucción switch. Cuando el control se ramifica a un case, pero continúa en la siguiente rama, el flujo de control se denomina caída libre y permite ramificar al mismo código para múltiples valores.

El fallo continuo se evita terminando un caso con una palabra clave (es decir, break), pero un error común es omitir accidentalmente la palabra clave, lo que provoca un fallo continuo no intencional y a menudo un error . Por lo tanto, muchos consideran que esta característica del lenguaje es peligrosa, [ 3 ] y a menudo el código con fallo continuo produce una advertencia de una herramienta de calidad de código como lint .

Algunos lenguajes, como JavaScript , conservan la semántica de ejecución secuencial, mientras que otros la excluyen o restringen. En particular, en C# todos los bloques deben terminar con break`or` returna menos que el bloque esté vacío, lo que limita la ejecución secuencial solo para ramificaciones desde múltiples valores.

En algunos casos, los lenguajes proporcionan una opción de ejecución continua. Por ejemplo, Perl no ejecuta la ejecución continua por defecto, pero un caso puede hacerlo explícitamente usando una continuepalabra clave, evitando así la ejecución continua no intencionada. De manera similar, Bash por defecto no ejecuta la ejecución continua cuando termina con ;;, pero permite la ejecución continua [ 4 ] con ;&o ;;&en su lugar.

Un ejemplo de una sentencia switch que se basa en la ejecución secuencial es el dispositivo de Duff .

Evaluación de la expresión del caso

Algunos lenguajes permiten expresiones condicionales complejas (no solo valores estáticos), lo que posibilita un comportamiento de ramificación más dinámico. Esto limita ciertas optimizaciones del compilador, por lo que es más común en lenguajes dinámicos donde se prioriza la flexibilidad sobre el rendimiento.

Por ejemplo, en PHP y Ruby , se puede usar una constante como expresión de control, y se ejecuta la primera sentencia case que coincide con esa constante. En el siguiente código PHP, la expresión switch es simplemente el valor verdadero, por lo que se selecciona la primera expresión case que es verdadera.

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

Esta función también resulta útil para comparar varias variables con un solo valor, en lugar de comparar una variable con muchos valores.

switch ( 5 ) { case $x : break ; case $y : break ; }

COBOL también admite este formato mediante su EVALUATEinstrucción. PL/I admite un comportamiento similar omitiendo la expresión de control, y WHENse ejecuta la primera expresión que se evalúa como verdadera.

En Ruby, debido a su manejo de la ===igualdad, la expresión case se puede usar para probar la clase de una variable. Por ejemplo:

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

Valor del resultado

Algunos lenguajes permiten evaluar una instrucción switch para obtener un valor.

Expresión de caso

La expresión de caso es compatible con lenguajes que se remontan al menos a ALGOL-W . [ 5 ] En ALGOL-W, se evaluaba una expresión entera, que luego evaluaba la expresión deseada de una lista de expresiones:

J := caso I de ( 3.14 , 2.78 , 44 8.9 ) ; A := caso DECODE ( C ) - 128 de ( " A " , " B " , " C " , " D " , " E " , " F " ) ;

Otros lenguajes que admiten la expresión case incluyen SQL , Standard ML , Haskell , Common LISP y Oxygene .

Expresión de interruptor

La expresión switch (introducida en Java SE 12 ) se evalúa a un valor. También hay una nueva forma de etiqueta case, case L->donde el lado derecho es una sola expresión. Esto también evita la ejecución secuencial y requiere que los casos sean exhaustivos. En Java SE 13 yieldse introduce la instrucción y en Java SE 14 las expresiones switch se convierten en una característica estándar del lenguaje. [ 6 ] [ 7 ] [ 8 ] Por ejemplo:

int dayCount = switch ( month ) { case ENE , MAR , MAY , JUL , AGO , OCT , DIC -> 31 ; case ABR , 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 ; } } };

Ruby también admite esta semántica. Por ejemplo:

comida para gatos = caso cuando gato.edad < = 1 junior cuando gato.edad > 10 senior sino normal fin

Manejo de excepciones

Varios lenguajes implementan una forma de instrucción switch para el manejo de excepciones , donde si se produce una excepción en un bloque, se elige una rama independiente, dependiendo de la excepción. En algunos casos, también existe una rama predeterminada si no se produce ninguna excepción. Un ejemplo temprano es Modula-3 , que utiliza la sintaxis TRY... , donde cada uno define un caso. Esto también se encuentra en Delphi , Scala y Visual Basic .NET .EXCEPTEXCEPT

Ejemplos

do

El siguiente código es una instrucción switch en C. Si agees 1, imprime "Eres uno". Si agees 3, imprime "Eres tres. Eres tres o cuatro". Las instrucciones switch en C++ se comportan igual que en C.

#include <stdio.h>void printAge ( unsigned int age ) { switch ( age ) { case 0 : printf ( "¡Eres un recién nacido!" ); break ; case 1 : printf ( "Tienes un año." ); break ; case 2 : printf ( "Tienes dos años." ); break ; case 3 : printf ( "Tienes tres años." ); case 4 : printf ( "Tienes tres o cuatro años." ); break ; default : printf ( "¡Tienes más de 4 años!" ); } }

A partir de C2Y , las sentencias switch permiten especificar un rango de casos a considerar. Esta característica apareció por primera vez como una extensión de GCC para C.

#include <stddef.h> #include <stdio.h> #include <uchar.h>void writeUnicode ( char32_t c ) { switch ( c ) { // coincide con cualquier valor entre [0, 0x7F] inclusive case 0 ... 0x7F : putchar ( c ); break ; // coincide con cualquier valor entre [0x80, 0x7FF] inclusive case 0x80 ... 0x7FF : putchar ( 0xC0 + c >> 6 ); putchar ( 0x80 + c & 0x3f ); break ; // coincide con cualquier valor entre [0x800, 0xFFFF] inclusive case 0x800 ... 0xFFFF : putchar ( 0xE0 + c >> 12 ); putchar ( 0x80 + ( c >> 6 ) & 0x3f ); putchar ( 0x80 + ( c >> 12 )); break ; default : unreachable (); } }

Pitón

Python (a partir de la versión 3.10.6) admite las palabras clave matchy case. [ 9 ] [ 10 ] [ 11 ] [ 12 ] No permite la ejecución secuencial. A diferencia de las condiciones de las sentencias if, la orpalabra clave no se puede utilizar para diferenciar entre casos. case _es equivalente a defaulten C.

letra : str = input ( "Ingrese una letra: " ) .strip ( )[ 0 ] .casefold ( ) match letra : case "a" | "e" | "i" | "o" | "u" : print ( f "La letra ' { letra } ' es una vocal!" ) case "y" : print ( f "La letra ' { letra } ' puede ser una vocal." ) case _ : print ( f "La letra ' { letra } ' no es una vocal!" )

Pascal

El siguiente es un ejemplo en Pascal :

caso algúnChar de 'a' : acciónEnA ; 'x' : acciónEnX ; 'y' , 'z' : acciónEnYyandZ ; de lo contrario acciónEnNoCoincidir ; fin ;

En el dialecto Oxygene de Pascal, una instrucción switch se puede usar como una expresión:

var i : Integer := case someChar of 'a' : 10 ; 'x' : 20 ; 'y' : 30 ; else - 1 ; end ;

Script de shell

El siguiente es un ejemplo en script de shell :

caso $someChar en a ) acciónEnA ; ;; x ) acciónEnX ; ;; [ yz ]) acciónEnYandZ ; ;; * ) acciónEnNoCoincidir ;; esac

Ensamblador

Una instrucción switch en lenguaje ensamblador :

switch: cmp ah , 00h je a cmp ah , 01h je b jmp swtend ; No hay coincidencias de casos o código "predeterminado" aquí a: push ah mov al , 'a' mov ah , 0Eh mov bh , 00h int 10h pop ah jmp swtend ; Equivalente a "break" b: push ah mov al , 'b' mov ah , 0Eh mov bh , 00h int 10h pop ah jmp swtend ; Equivalente a "break" ... swtend:

Alternativas

Algunas alternativas al uso de una sentencia switch incluyen:

Si-entonces-si no
Una serie de condicionales if-then-else pueden comprobar cada valor de caso, uno a la vez. Se puede lograr la ejecución secuencial con una secuencia de condicionales if, cada uno sin la cláusula else.
Tabla de control
La lógica de una instrucción switch se puede codificar como una tabla de control (una forma de tabla de búsqueda ) que se indexa mediante los valores de cada caso, y cada valor codifica lo que de otro modo habría en la sección del caso , como un puntero a función , una función anónima o un mecanismo similar.
En un lenguaje que no proporciona una instrucción switch, como Lua , [ 13 ] una tabla de control proporciona una forma de implementar la semántica de la instrucción switch al tiempo que permite una eficiencia en tiempo de ejecución que if-then-else no proporciona.
Coincidencia de patrones
La coincidencia de patrones es una funcionalidad similar a un interruptor que se utiliza en muchos lenguajes de programación funcional .

Historia

En su texto de 1952, Introducción a la metamatemática , Stephen Kleene demuestra formalmente que la función de caso (siendo la función if-then-else su forma más simple) es una función recursiva primitiva , donde define la noción de "definición por casos" de la siguiente manera:

"#F. La función φ definida así

φ(x 1 , ... , x n ) =
  • φ 1 (x 1 , ... , x n ) si Q 1 (x 1 , ... , x n ),
  • . . . . . . . . . . . .
  • φ m (x 1 , ... , x n ) si Q m (x 1 , ... , x n ),
  • φ m+1 (x 1 , ... , x n ) en caso contrario,

donde Q 1 , ... , Q m son predicados mutuamente excluyentes (o φ(x 1 , ... , x n ) tendrá el valor dado por la primera cláusula que se aplique) es recursivo primitivo en φ 1 , ..., φ m+1 , Q 1 , ..., Q m+1 .

Stephen Kleene, [ 14 ]

Kleene proporciona una prueba de esto en términos de las funciones recursivas de tipo booleano "sign-of" sg( ) y "not sign of" ~sg( ) (Kleene 1952:222-223); la primera devuelve 1 si su entrada es positiva y −1 si su entrada es negativa.

Boolos, Burgess y Jeffrey hacen la observación adicional de que la "definición por casos" debe ser mutuamente excluyente y colectivamente exhaustiva . También ofrecen una prueba de la recursividad primitiva de esta función (Boolos, Burgess y Jeffrey 2002:74-75).

La estructura if-then-else es la base del formalismo de McCarthy : su uso reemplaza tanto la recursión primitiva como el operador mu .

Los primeros compiladores de Fortran admitían la instrucción goto calculada para bifurcaciones múltiples. Los primeros compiladores de ALGOL admitían un tipo de datos SWITCH que contenía una lista de "expresiones de designación". Una instrucción goto podía hacer referencia a una variable switch y, proporcionando un índice, bifurcarse al destino deseado. Con la experiencia, se comprendió que se necesitaba una construcción de bifurcaciones múltiples más formal, con un único punto de entrada y salida. Lenguajes como BCPL , ALGOL-W y ALGOL-68 introdujeron formas de esta construcción que han perdurado en los lenguajes modernos.

Véase también

Referencias

  1. Guntheroth, Kurt (27 de abril de 2016). Optimized C++ . O'Reilly Media. pág.  182. ISBN 9781491922033.
  2. Vlad Lazarenko. De la sentencia switch al código máquina.
  3. van der Linden, Peter (1994). Expert C Programming: Deep C Secrets , p. 38. Prentice Hall, Eaglewood Cliffs. ISBN 0131774298.
  4. desde la versión 4.0 , lanzada en 2009.
  5. Wirth, Niklaus ; Hoare, CAR (junio de 1966). "Una contribución al desarrollo de ALGOL" . Communications of the ACM . 9 (6): 413– 432. doi : 10.1145/365696.365702 . S2CID 11901135. Recuperado el 7 de octubre de 2020 a través de Association for Computing Machinery . 
  6. "JEP 325: Expresiones Switch (Vista previa)" . openjdk.java.net . Consultado el 28 de abril de 2021 .
  7. "JEP 354: Expresiones Switch (Segunda vista previa)" . openjdk.java.net . Consultado el 28 de abril de 2021 .
  8. "JEP 361: Expresiones Switch" . openjdk.java.net . Consultado el 28 de abril de 2021 .
  9. Galindo Salgado, Pablo. "Novedades de Python 3.10" . Documentación de Python 3.10.6 . Consultado el 19 de agosto de 2022 .
  10. Bucher, Brandt; van Rossum, Guido (12 de septiembre de 2020). "PEP 634 – Coincidencia de patrones estructurales: especificación" . Propuestas de mejora de Python . Recuperado el 19 de agosto de 2022 .
  11. Kohn, Tobias ; van Rossum, Guido (12 de septiembre de 2020). "PEP 635 – Coincidencia de patrones estructurales: motivación y fundamentos" . Python Enhancement Proposals . Recuperado el 19 de agosto de 2022 .
  12. Moisset, Daniel F. "PEP 636 – Coincidencia de patrones estructurales: Tutorial" . Propuestas de mejora de Python . Consultado el 19 de agosto de 2022 .
  13. Sentencia switch en Lua
  14. "Definición por casos", Kleene 1952:229

Lecturas adicionales

  • Stephen Kleene , 1952 (10.ª reimpresión, 1991), Introducción a la metamatemática , North-Holland Publishing Company, Ámsterdam, Países Bajos, ISBN 0-7204-2103-9
  • George Boolos , John Burgess y Richard Jeffrey , 2002, Computabilidad y lógica: cuarta edición , Cambridge University Press, Cambridge, Reino Unido, ISBN 0-521-00758-5Edición de bolsillo. Véase páginas 74-75.