En los lenguajes de programación de computadoras , una declaración de cambio es un tipo de mecanismo de control de selección que se utiliza para permitir que el valor de una variable o expresión cambie el flujo de control de la ejecución del programa mediante búsqueda y mapa.
Las declaraciones de cambio funcionan de manera algo 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 lenguaje, utilizando palabras clave como switch
, case
o select
.inspect
Las sentencias switch vienen en dos variantes principales: un switch estructurado, como en Pascal, que toma exactamente una rama, y un switch no estructurado, como en C, que funciona como un tipo de goto . Las razones principales para usar un conmutador 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 el potencial de una ejecución más rápida a través de una optimización del compilador más sencilla en muchos casos.
En su texto de 1952 Introducción a las metamatemáticas , Stephen Kleene demostró formalmente que la función CASO (siendo la función SI-ENTONCES-ELSE su forma más simple) es una función recursiva primitiva , donde define la noción definition by cases
de la siguiente manera:
Kleene proporciona una prueba de esto en términos de las funciones recursivas de tipo booleano "signo de" sg() y "no signo de" ~sg() (Kleene 1952:222-223); el primero devuelve 1 si su entrada es positiva y −1 si su entrada es negativa.
Boolos-Burgess-Jeffrey hace 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-Jeffrey 2002:74-75).
IF-THEN-ELSE es la base del formalismo McCarthy : su uso reemplaza tanto la recursividad primitiva como el operador mu .
En la mayoría de los lenguajes, los programadores escriben una declaración de cambio en muchas líneas individuales usando una o dos palabras clave. Una sintaxis típica implica:
select
, seguido de una expresión que a menudo se denomina expresión de control o variable de control de la declaración de cambiobreak
declaración generalmente sigue a una case
declaración para finalizar dicha declaración. [Pozos]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 más abajo), que la variable de control puede coincidir y que hará que el control pase a la secuencia correspondiente de declaraciones. El valor (o lista/rango de valores) generalmente está separado de la secuencia de declaración correspondiente por dos puntos o por una flecha de implicación. En muchos idiomas, cada caso debe ir precedido de una palabra clave como case
o when
.
Por lo general, también se permite un caso predeterminado opcional, especificado mediante 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 ningún caso coincide y se default
omite, la switch
declaración simplemente no hace nada. En otros, como PL/I, se genera un error.
Semánticamente, existen dos formas principales de declaraciones de cambio.
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 un condicional if-then-else generalizado , aquí con cualquier número de ramas, no solo dos.
La segunda forma son conmutadores no estructurados, como en C, donde los casos se tratan como etiquetas dentro de un solo bloque y el conmutador funciona como un goto generalizado. Esta distinción se conoce como el tratamiento del fracaso, que se detalla a continuación.
En muchos idiomas, solo se ejecuta el bloque coincidente y luego la ejecución continúa al final de la declaración de cambio. Estos incluyen la familia Pascal (Object Pascal, Modula, Oberon, Ada, etc.), así como PL/I , formas modernas de dialectos Fortran y BASIC influenciados por Pascal, la mayoría de los lenguajes funcionales y muchos otros. Para permitir que múltiples 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 su lugar presentan falla, donde el control se mueve al caso coincidente y luego la ejecución continúa ("falla") hasta las declaraciones asociadas con el siguiente caso en el texto fuente. . Esto también permite que varios valores coincidan con el mismo punto sin ninguna sintaxis especial: simplemente se enumeran con cuerpos vacíos. Los valores pueden tener condiciones especiales con código en el cuerpo del caso. En la práctica, la falla generalmente se evita con una break
palabra clave al final del cuerpo coincidente, que sale de la ejecución del bloque de cambio, pero esto puede causar errores debido a fallas involuntarias si el programador olvida insertar la break
declaración. Por lo tanto, muchos [4] ven esto como una verruga del lenguaje y algunas herramientas de pelusa advierten contra ello. 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 la falla predeterminada, mientras que otros la eliminan o solo la permiten en circunstancias especiales. Las variaciones notables de esto en la familia C incluyen C# , en el que todos los bloques deben terminar con break
o return
a menos que el bloque esté vacío (es decir, la opción alternativa se utiliza como una forma de especificar múltiples valores).
En algunos casos, los idiomas ofrecen una alternativa opcional. Por ejemplo, Perl no falla de forma predeterminada, pero un caso puede hacerlo explícitamente usando una continue
palabra clave. Esto evita caídas involuntarias, pero lo permite cuando se desea. De manera similar, Bash por defecto no falla cuando termina con ;;
, pero permite la falla [5] con ;&
o ;;&
en su lugar.
Un ejemplo de una declaración de cambio que se basa en la falla es el dispositivo de Duff .
Los compiladores optimizadores como GCC o Clang pueden compilar una declaración de cambio en una tabla de rama o en una búsqueda binaria a través de los valores en los casos. [6] Una tabla de bifurcación permite que la instrucción de cambio determine con un número pequeño y constante de instrucciones qué bifurcación ejecutar sin tener que pasar por una lista de comparaciones, mientras que una búsqueda binaria toma solo un número logarítmico de comparaciones, medido en el número de casos en la declaración de cambio.
Normalmente, el único método para averiguar si se ha producido esta optimización es observando la salida resultante del código ensamblador o de máquina que ha generado el compilador.
En algunos lenguajes y entornos de programación, el uso de una declaración case
o 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 mediante una tabla de rama indexada . [7] Por ejemplo, decidir el flujo del programa basándose en el valor de un solo carácter, si se implementa correctamente, es mucho más eficiente que la alternativa, reduciendo considerablemente la longitud de la ruta de instrucción . Cuando se implementa como tal, una declaración de cambio se convierte esencialmente en un hash perfecto .
En términos del gráfico de flujo de control , una declaración de cambio consta de dos nodos (entrada y salida), más un borde entre ellos para cada opción. Por el contrario, una secuencia de declaraciones "if...else if...else if" tiene un nodo adicional para cada caso excepto el primero y el último, junto con un borde correspondiente. El gráfico de flujo de control resultante para las secuencias de "si" tiene, por tanto, muchos más nodos y casi el doble de aristas, que no añaden ninguna información útil. Sin embargo, las ramas simples en las declaraciones if son conceptualmente más fáciles individualmente que la rama compleja de una declaración switch. En términos de complejidad ciclomática , ambas opciones la aumentan en k −1 si se dan k casos.
Las expresiones de cambio se introducen en Java SE 12 , el 19 de marzo de 2019, como función de vista previa. Aquí se puede utilizar una expresión de cambio completa para devolver un valor. También hay una nueva forma de etiqueta de caso, case L->
donde el lado derecho es una expresión única. Sin embargo, esto también evita caídas y requiere que los casos sean exhaustivos. En Java SE 13 yield
se introduce la declaración y en Java SE 14 las expresiones de cambio se convierten en una característica del lenguaje estándar. [8] [9] [10] Por ejemplo:
int ndays = switch ( mes ) { case ENERO , MARZO , MAYO , JULIO , AGO , OCTUBRE , DICIEMBRE -> 31 ; caso ABR , JUN , SEP , NOV -> 30 ; caso FEB -> { if ( año % 400 == 0 ) rendimiento 29 ; de lo contrario, si ( año % 100 == 0 ) rinde 28 ; de lo contrario, si ( año % 4 == 0 ) rinde 29 ; de lo contrario, rinda 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 secuencias de comandos donde la flexibilidad mejorada es más importante que la sobrecarga de rendimiento.
Por ejemplo, en PHP , se puede usar una constante como "variable" para verificar, y se ejecutará la primera declaración de caso que evalúe esa constante:
cambiar ( verdadero ) { caso ( $x == 'hola' ) : foo (); romper ; caso ( $z == 'hola' ) : descanso ; } cambiar ( 5 ) { caso $x : romper ; caso $y : descanso ; }
Esta característica también es útil para comparar 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 SELECT
declaración donde la expresión de control se omite por completo 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 usar para probar la clase de la variable:
caso de entrada cuando Array luego pone '¡la entrada es una matriz!' cuando Hash luego 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 ningún parámetro (actúa un poco como una else if
declaración):
comida para gatos = caso cuando cat . edad <= 1 junior cuando gato . edad > 10 años mayor de lo contrario final normal
Una declaración de cambio en lenguaje ensamblador :
cambiar: cmp ah , 00h je a cmp ah , 01h je b jmp swtend ; Ningún caso coincide 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 ; Equivale a "romper" ... swtend:
Para Python 3.10.6, se aceptaron los PEPmatch
634-636, que agregaron palabras clave y case
. [11] [12] [13] [14] A diferencia de otros lenguajes, Python en particular no exhibe un comportamiento fallido.
letra = entrada ( "Pon una sola letra: " ) . tira ()[ 0 ] . casefold () # primer carácter que no sea un espacio en blanco de la entrada, minúscula letra de coincidencia : caso 'a' | 'mi' | 'yo' | 'o' | 'u' : # A diferencia de las condiciones en las declaraciones if, la palabra clave `o` no se puede usar aquí para diferenciar entre casos print ( f "¡La letra { letra } es una vocal!" ) caso 'y' : print ( 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 declaración de cambio 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 usa la sintaxis TRY
... EXCEPT
, donde cada uno EXCEPT
define un caso. Esto también se encuentra en Delphi , Scala y Visual Basic .NET .
Algunas alternativas a las declaraciones de cambio pueden ser:
case
valores y, como valores, la parte debajo de la case
declaración.switch
declaración real. Consulte el artículo sobre la tabla de control para obtener más detalles. en este).switch
declaraciones en el lenguaje Lua, que no tiene archivos switch
. [15]switch
, ya que muchos lenguajes pueden optimizar las búsquedas en tablas, mientras que las declaraciones de cambio no se optimizan a menos que el rango de valores sea pequeño con pocos espacios. Sin embargo, una búsqueda no binaria y no optimizada será casi con toda seguridad más lenta que un cambio no optimizado o las múltiples sentencias if-else equivalentes. [ cita necesaria ]