Declaración de cambio

Tipo de mecanismo de control de selección en lenguajes de programación informática

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 ifdeclaraciones 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, selecto 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.

Un ejemplo de una declaración Switch en C
switch ( edad ) { caso 1 : printf ( "Tienes uno." ); descanso ; caso 2 : printf ( "Tienes dos." ); descanso ; caso 3 : printf ( "Tienes tres." ); caso 4 : printf ( "Tienes tres o cuatro." ); descanso ; predeterminado : printf ( "¡No tienes 1, 2, 3 o 4!" ); }                   

Historia

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.

Sintaxis típica

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:

  • el primero select, seguido de una expresión que a menudo se denomina expresión de control o variable de control de la declaración switch
  • líneas subsiguientes que definen los casos reales (los valores), con las secuencias correspondientes de instrucciones para su ejecución cuando se produce una coincidencia
  • En los lenguajes con comportamiento de fallthrough, breaknormalmente una declaración sigue a otra casedeclaración para finalizar dicha declaración. [Wells]
  • En algunos lenguajes, por ejemplo, PL/I , la expresión de control es opcional; si no hay 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 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 .
  • En algunos lenguajes, por ejemplo, Rexx , no se permite ninguna expresión de control y 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 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 caseo when.

Normalmente también se permite un caso predeterminado opcional, especificado por una palabra clave default, otherwiseo 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 defaultse omite la switchinstrucción simplemente no hace nada. En otros, como PL/I, se genera un error.

Semántica

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.

Caída

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 breakpalabra 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 breakdeclaració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 breako returna 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 continuepalabra 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 .

Compilación

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.

Ventajas y desventajas

En algunos lenguajes y entornos de programación, el uso de una declaración caseor switchse considera superior a una serie equivalente de declaraciones if else if porque es:

  • 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 de puntos de interrupción condicionales)
  • Más fácil de leer para una persona
  • Más fácil de entender y, en consecuencia, más fácil de mantener.
  • Profundidad fija: una secuencia de declaraciones "if else if" puede producir una anidación profunda, lo que dificulta la compilación (especialmente en código generado automáticamente)
  • Es más fácil verificar que se manejan todos los valores. Los compiladores pueden emitir una advertencia si no se manejan algunos valores de enumeración.

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.

Expresiones de cambio

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 yieldse 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 ; } };                                                        

Usos alternativos

Muchos lenguajes evalúan expresiones dentro de switchbloques 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.

PHP

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 EVALUATEdeclaración. PL/I tiene una forma alternativa de la SELECTdeclaración donde se omite por completo la expresión de control y se ejecuta la primera WHENque se evalúa como verdadera .

Rubí

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 casetenga parámetros (actúa un poco como una else ifdeclaración):

comida para gatos = caso cuando gato . edad <= 1 menor cuando gato . edad > 10 mayor de lo contrario normal fin               

Ensamblador

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:                                                  

Pitón

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!" )

Manejo de excepciones

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 EXCEPTdefine un caso. Esto también se encuentra en Delphi , Scala y Visual Basic .NET .

Alternativas

Algunas alternativas a las sentencias switch pueden ser:

  • Una serie de condiciones if-else que examinan el valor de destino de a un valor por vez. El comportamiento de transición se puede lograr con una secuencia de condiciones if , cada una sin la cláusula else .
  • Una tabla de búsqueda , que contiene, como claves, los casevalores y, como valores, la parte bajo la casedeclaración.
(En algunos idiomas, solo se permiten tipos de datos reales como valores en la tabla de búsqueda. En otros idiomas, también es posible asignar funciones como valores de la tabla de búsqueda, obteniendo la misma flexibilidad que una switchdeclaración real. Consulte el artículo Tabla de control para obtener más detalles sobre esto).
Lua no admite declaraciones case/switch. [15] Esta técnica de búsqueda es una forma de implementar switchdeclaraciones en el lenguaje Lua, que no tiene una función switch. [15]
En algunos casos, las tablas de búsqueda son más eficientes que las sentencias no optimizadas , ya que muchos lenguajes pueden optimizar las búsquedas en tablas, mientras que las sentencias switch no están optimizadas a menos que el rango de valores sea pequeño y tenga pocos espacios vacíos. Sin embargo, una búsqueda no optimizada y no binaria será casi con certeza más lenta que una sentencia switch no optimizada o las sentencias if-else múltiples equivalentes . [ cita requerida ]switch
  • Una tabla de control (que puede implementarse como una tabla de búsqueda simple) también se puede personalizar para acomodar múltiples condiciones en múltiples entradas si es necesario y generalmente exhibe una mayor "compacidad visual" que un conmutador equivalente (que puede ocupar muchas declaraciones).
  • Coincidencia de patrones , que se utiliza para implementar una funcionalidad similar a la de un conmutador en muchos lenguajes funcionales .

Véase también

Referencias

  1. ^ Skeet, Jon (23 de marzo de 2019). C# en profundidad . Manning. ISBN 978-1617294532.
  2. ^ Bloch, Joshua (2018). "Effective Java: Programming Language Guide" (tercera edición). Addison-Wesley. ISBN 978-0134685991.
  3. ^ "Definición por casos", Kleene 1952:229
  4. ^ van der Linden, Peter (1994). Programación experta en C: secretos profundos de C , pág. 38. Prentice Hall, Eaglewood Cliffs. ISBN 0131774298 . 
  5. ^ desde la versión 4.0, lanzada en 2009.
  6. ^ Vlad Lazarenko. De la sentencia Switch al código de máquina
  7. ^ Guntheroth, Kurt (27 de abril de 2016). Optimized C++ . O'Reilly Media. pág. 182. ISBN 9781491922033.
  8. ^ "JEP 325: Cambiar expresiones (versión preliminar)". openjdk.java.net . Consultado el 28 de abril de 2021 .
  9. ^ "JEP 354: Cambiar expresiones (segunda versión preliminar)". openjdk.java.net . Consultado el 28 de abril de 2021 .
  10. ^ "JEP 361: Cambiar expresiones". openjdk.java.net . Consultado el 28 de abril de 2021 .
  11. ^ Galindo Salgado, Pablo. "Novedades de Python 3.10". Documentación de Python 3.10.6 . Consultado el 19 de agosto de 2022 .
  12. ^ Bucher, Brandt; van Rossum, Guido (12 de septiembre de 2020). «PEP 634 – Coincidencia de patrones estructurales: especificación». Propuestas de mejora de Python . Consultado el 19 de agosto de 2022 .
  13. ^ Kohn, Tobias ; van Rossum, Guido (12 de septiembre de 2020). «PEP 635 – Coincidencia de patrones estructurales: motivación y fundamento». Propuestas de mejora de Python . Consultado el 19 de agosto de 2022 .
  14. ^ Moisset, Daniel F. "PEP 636 – Coincidencia de patrones estructurales: Tutorial". Propuestas de mejora de Python . Consultado el 19 de agosto de 2022 .
  15. ^ Declaración ab Switch en Lua

Lectura adicional

Obtenido de "https://es.wikipedia.org/w/index.php?title=Declaración_Switch&oldid=1250500030"