En los lenguajes de programación informática , una declaración switch es un tipo de mecanismo de control de selección utilizado para permitir que el valor de una variable o expresión cambie el flujo de control de la ejecución del programa a través de la búsqueda y el mapa.
Las declaraciones Switch funcionan de manera similar a las if
declaraciones utilizadas en lenguajes de programación como C / C++ , C# , Visual Basic .NET , Java y existen en la mayoría de los lenguajes de programación imperativos de alto nivel como Pascal , Ada , C / C++ , C# , [1] : 374–375 Visual Basic .NET , Java , [2] : 157–167 y en muchos otros tipos de lenguajes, utilizando palabras clave como switch
, case
, select
o inspect
.
Las sentencias switch se presentan en dos variantes principales: una sentencia switch estructurada, como en Pascal, que toma exactamente una rama, y una sentencia switch no estructurada, como en C, que funciona como un tipo de goto . Las principales razones para usar una sentencia switch incluyen mejorar la claridad, al reducir la codificación que de otro modo sería repetitiva, y (si la heurística lo permite) también ofrecer la posibilidad de una ejecución más rápida a través de una optimización más sencilla del compilador en muchos casos.
En su texto de 1952 Introducción a las metamatemáticas , Stephen Kleene demostró formalmente que la función CASE (la función IF-THEN-ELSE es 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 φ se define 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 aplica) es recursivo primitivo en φ 1 , ..., φ m+1 , Q 1 , ..., Q m+1 .
— Stephen Kleene, [3]
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-Jeffrey hacen la observación adicional de que la "definición por casos" debe ser tanto mutuamente excluyente como colectivamente exhaustiva . También ellos ofrecen una prueba de la recursividad primitiva de esta función (Boolos-Burgess-Jeffrey 2002:74-75).
El IF-THEN-ELSE es la base del formalismo McCarthy : su uso reemplaza tanto a la recursión primitiva como al operador mu .
Los primeros compiladores de Fortran admitían la sentencia GOTO calculada para la ramificación multidireccional. Los primeros compiladores de ALGOL admitían un tipo de datos SWITCH que contiene una lista de "expresiones de designación". Una sentencia GOTO podía hacer referencia a una variable switch y, al proporcionar un índice, ramificarse al destino deseado. Con la experiencia, se comprendió que se necesitaba una construcción multidireccional más formal, con un único punto de entrada y salida. Los lenguajes como BCPL , ALGOL-W y ALGOL-68 introdujeron formas de esta construcción que han sobrevivido a través de los lenguajes modernos.
En la mayoría de los lenguajes, los programadores escriben una sentencia switch en varias líneas individuales utilizando una o dos palabras clave. Una sintaxis típica es la siguiente:
select
, seguido de una expresión que a menudo se denomina expresión de control o variable de control de la declaración switchbreak
normalmente una declaración sigue a otra case
declaración para finalizar dicha declaración. [Wells]WHEN
cláusula que contiene una expresión booleana y se produce una coincidencia para el primer caso en el que esa expresión se evalúa como verdadera. Este uso es similar a las estructuras if/then/elseif/else en algunos otros lenguajes, por ejemplo, Perl .WHEN
cláusula que contiene una expresión booleana y se produce una coincidencia para el primer caso en el que esa expresión se evalúa como verdadera.Cada alternativa comienza con el valor particular, o lista de valores (ver abajo), con el que la variable de control puede coincidir y que hará que el control vaya a la secuencia de instrucciones correspondiente. El valor (o lista/rango de valores) normalmente está separado de la secuencia de instrucciones correspondiente por dos puntos o por una flecha de implicación. En muchos lenguajes, cada caso también debe estar precedido por una palabra clave como case
o when
.
Normalmente también se permite un caso predeterminado opcional, especificado por una palabra clave default
, otherwise
o else
. Esto se ejecuta cuando ninguno de los otros casos coincide con la expresión de control. En algunos lenguajes, como C, si no coincide ningún caso y default
se omite la switch
instrucción simplemente no hace nada. En otros, como PL/I, se genera un error.
Semánticamente, hay dos formas principales de declaraciones switch.
La primera forma son conmutadores estructurados, como en Pascal, donde se toma exactamente una rama y los casos se tratan como bloques separados y exclusivos. Esto funciona como una condición if-then-else generalizada , aquí con cualquier número de ramas, no solo dos.
La segunda forma son los conmutadores no estructurados, como en C, donde los casos se tratan como etiquetas dentro de un único bloque y el conmutador funciona como un goto generalizado. Esta distinción se conoce como el tratamiento de fallthrough, que se explica a continuación.
En muchos lenguajes, solo se ejecuta el bloque correspondiente y luego la ejecución continúa al final de la sentencia switch. Entre ellos se incluyen la familia Pascal (Object Pascal, Modula, Oberon, Ada, etc.) así como PL/I , formas modernas de Fortran y dialectos BASIC influenciados por Pascal, la mayoría de los lenguajes funcionales y muchos otros. Para permitir que varios valores ejecuten el mismo código (y evitar la necesidad de duplicar el código ), los lenguajes de tipo Pascal permiten cualquier número de valores por caso, dados como una lista separada por comas, como un rango o como una combinación.
Los lenguajes derivados del lenguaje C, y más generalmente aquellos influenciados por el GOTO calculado de Fortran , en cambio presentan un método fallthrough, donde el control se mueve al caso coincidente, y luego la ejecución continúa ("falla") a las declaraciones asociadas con el siguiente caso en el texto fuente. Esto también permite que múltiples valores coincidan con el mismo punto sin ninguna sintaxis especial: solo se enumeran con cuerpos vacíos. Los valores pueden ser condicionados de manera especial con código en el cuerpo del caso. En la práctica, el fallthrough generalmente se evita con una break
palabra clave al final del cuerpo coincidente, que sale de la ejecución del bloque switch, pero esto puede causar errores debido a un fallthrough involuntario si el programador olvida insertar la break
declaración. Por lo tanto, muchos [4] lo ven como una falla del lenguaje y advierten contra él en algunas herramientas de lint. Sintácticamente, los casos se interpretan como etiquetas, no como bloques, y las declaraciones switch y break cambian explícitamente el flujo de control. Algunos lenguajes influenciados por C, como JavaScript , conservan el fallthrough predeterminado, mientras que otros eliminan el fallthrough o solo lo permiten en circunstancias especiales. Entre las variaciones notables de esto en la familia C se incluye C# , en el que todos los bloques deben terminar con un break
o return
a menos que el bloque esté vacío (es decir, se utiliza fallthrough como una forma de especificar múltiples valores).
En algunos casos, los lenguajes proporcionan un paso a paso opcional. Por ejemplo, Perl no lo hace de manera predeterminada, pero un caso puede hacerlo explícitamente usando una continue
palabra clave. Esto evita el paso a paso involuntario, pero lo permite cuando se desea. De manera similar, Bash no lo hace de manera predeterminada cuando se termina con ;;
, pero lo permite [5] con ;&
o ;;&
en su lugar.
Un ejemplo de una declaración de cambio que se basa en el fallo es el dispositivo de Duff .
Los compiladores optimizadores como GCC o Clang pueden compilar una declaración switch en una tabla de ramificaciones o en una búsqueda binaria a través de los valores en los casos. [6] Una tabla de ramificaciones permite que la declaración switch determine con un número pequeño y constante de instrucciones qué ramificación ejecutar sin tener que pasar por una lista de comparaciones, mientras que una búsqueda binaria solo toma un número logarítmico de comparaciones, medido en el número de casos en la declaración switch.
Normalmente, el único método para saber si se ha producido esta optimización es observar el ensamblaje resultante o el código de máquina generado por el compilador.
En algunos lenguajes y entornos de programación, el uso de una declaración case
or switch
se considera superior a una serie equivalente de declaraciones if else if porque es:
Además, una implementación optimizada puede ejecutarse mucho más rápido que la alternativa, porque a menudo se implementa utilizando una tabla de ramificación indexada . [7] Por ejemplo, decidir el flujo del programa en función del valor de un solo carácter, si se implementa correctamente, es mucho más eficiente que la alternativa, lo que reduce considerablemente las longitudes de las rutas de instrucciones . Cuando se implementa de esta manera, una declaración switch se convierte esencialmente en un hash perfecto .
En términos del gráfico de flujo de control , una sentencia switch consta de dos nodos (entrada y salida), más una arista entre ellos para cada opción. Por el contrario, una secuencia de sentencias "if...else if...else if" tiene un nodo adicional para cada caso que no sea el primero y el último, junto con una arista correspondiente. El gráfico de flujo de control resultante para las secuencias de "if" tiene, por tanto, muchos más nodos y casi el doble de aristas, y estas no añaden ninguna información útil. Sin embargo, las ramas simples en las sentencias if son conceptualmente más fáciles individualmente que la rama compleja de una sentencia switch. En términos de complejidad ciclomática , ambas opciones la aumentan en k −1 si se dan k casos.
Las expresiones switch se introdujeron en Java SE 12 , el 19 de marzo de 2019, como una característica de vista previa. Aquí se puede usar una expresión switch completa para devolver un valor. También hay una nueva forma de etiqueta de caso, case L->
donde el lado derecho es una sola expresión. Esto también evita el error de ejecución y requiere que los casos sean exhaustivos. En Java SE 13 yield
se introduce la declaración y en Java SE 14 las expresiones switch se convierten en una característica estándar del lenguaje. [8] [9] [10] Por ejemplo:
int ndays = switch ( mes ) { caso ENE , MAR , MAY , JUL , AGO , OCT , DIC -> 31 ; caso ABR , JUN , SEP , NOV -> 30 ; caso FEB -> { si ( año % 400 == 0 ) rendimiento 29 ; de lo contrario si ( año % 100 == 0 ) rendimiento 28 ; de lo contrario si ( año % 4 == 0 ) rendimiento 29 ; de lo contrario rendimiento 28 ; } };
Muchos lenguajes evalúan expresiones dentro de switch
bloques en tiempo de ejecución, lo que permite una serie de usos menos obvios para la construcción. Esto prohíbe ciertas optimizaciones del compilador, por lo que es más común en lenguajes dinámicos y de scripting donde la flexibilidad mejorada es más importante que la sobrecarga de rendimiento.
Por ejemplo, en PHP , una constante se puede utilizar como la "variable" a verificar, y se ejecutará la primera declaración de caso que evalúe esa constante:
switch ( true ) { caso ( $x == 'hola' ) : foo (); romper ; caso ( $z == 'cómo estás' ) : romper ; } switch ( 5 ) { caso $x : romper ; caso $y : romper ; }
Esta característica también es útil para comprobar múltiples variables con un valor en lugar de una variable con muchos valores. COBOL también admite esta forma (y otras formas) en la EVALUATE
declaración. PL/I tiene una forma alternativa de la SELECT
declaración donde se omite por completo la expresión de control y se ejecuta la primera WHEN
que se evalúa como verdadera .
En Ruby , debido a su manejo de ===
la igualdad, la declaración se puede utilizar para probar la clase de la variable:
caso entrada cuando Array entonces pone '¡la entrada es una matriz!' cuando Hash entonces pone '¡la entrada es un Hash!' fin
Ruby también devuelve un valor que se puede asignar a una variable y en realidad no requiere que case
tenga parámetros (actúa un poco como una else if
declaración):
comida para gatos = caso cuando gato . edad <= 1 menor cuando gato . edad > 10 mayor de lo contrario normal fin
Una declaración switch en lenguaje ensamblador :
switch: cmp ah , 00h je a cmp ah , 01h je b jmp swtend ; No hay casos que coincidan 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 "romper" b: push ah mov al , 'b' mov ah , 0Eh mov bh , 00h int 10h pop ah jmp swtend ; Equivalente a "romper" ... swtend:
Para Python 3.10.6, se aceptaron los PEPmatch
634 a 636, que agregaron palabras clave y case
. [11] [12] [13] [14] A diferencia de otros lenguajes, Python notablemente no exhibe un comportamiento de caída.
letra = entrada ( "Ponga una sola letra: " ) . strip ()[ 0 ] . casefold () # primer carácter que no sea un espacio en blanco de la entrada, en minúscula letra de coincidencia : caso 'a' | 'e' | 'i' | 'o' | 'u' : # A diferencia de las condiciones en las declaraciones if, la palabra clave 'or' no se puede usar aquí para diferenciar entre casos print ( f "¡La letra { letra } es una vocal!" ) caso 'y' : imprimir ( f "La letra { letra } puede ser una vocal." ) case _ : # `case _` es equivalente a `default` de C y otros print ( f "La letra { letra } no es una vocal!" )
Varios lenguajes implementan una forma de sentencia switch en el manejo de excepciones , donde si se genera una excepción en un bloque, se elige una rama separada, dependiendo de la excepción. En algunos casos, también está presente una rama predeterminada, si no se genera ninguna excepción. Un ejemplo temprano es Modula-3 , que utiliza la sintaxis TRY
... EXCEPT
, donde each EXCEPT
define un caso. Esto también se encuentra en Delphi , Scala y Visual Basic .NET .
Algunas alternativas a las sentencias switch pueden ser:
case
valores y, como valores, la parte bajo la case
declaración.switch
declaración real. Consulte el artículo Tabla de control para obtener más detalles sobre esto).switch
declaraciones en el lenguaje Lua, que no tiene una función switch
. [15]switch