En informática , el flujo de control (o flujo de control ) es el orden en el que se ejecutan o evalúan las instrucciones , sentencias o llamadas a funciones de un programa imperativo . El énfasis en el flujo de control explícito distingue un lenguaje de programación imperativo de un lenguaje de programación declarativo .
En un lenguaje de programación imperativo , una sentencia de flujo de control es una sentencia que da como resultado la elección de cuál de dos o más caminos se debe seguir. En el caso de los lenguajes funcionales no estrictos , existen funciones y construcciones del lenguaje para lograr el mismo resultado, pero por lo general no se las denomina sentencias de flujo de control.
Un conjunto de enunciados se estructura a su vez generalmente como un bloque , que además de agrupar, también define un ámbito léxico .
Las interrupciones y señales son mecanismos de bajo nivel que pueden alterar el flujo de control de una manera similar a una subrutina , pero generalmente ocurren como respuesta a algún estímulo o evento externo (que puede ocurrir de forma asincrónica ), en lugar de la ejecución de una declaración de flujo de control en línea .
A nivel de lenguaje de máquina o lenguaje ensamblador , las instrucciones de flujo de control suelen funcionar alterando el contador del programa . Para algunas unidades centrales de procesamiento (CPU), las únicas instrucciones de flujo de control disponibles son las instrucciones de bifurcación condicional o incondicional , también denominadas saltos.
Los tipos de declaraciones de flujo de control admitidos por diferentes lenguajes varían, pero se pueden clasificar según su efecto:
Una etiqueta es un nombre o número explícito asignado a una posición fija dentro del código fuente y al que pueden hacer referencia las instrucciones de flujo de control que aparecen en otras partes del código fuente. Una etiqueta marca una posición dentro del código fuente y no tiene ningún otro efecto.
Los números de línea son una alternativa a las etiquetas con nombre que se utilizan en algunos lenguajes (como BASIC ). Son números enteros que se colocan al comienzo de cada línea de texto en el código fuente. Los lenguajes que los utilizan suelen imponer la restricción de que los números de línea deben aumentar de valor en cada línea siguiente, pero no pueden exigir que sean consecutivos. Por ejemplo, en BASIC:
10 DEJA X = 3 20 IMPRIMIR X
En otros lenguajes como C y Ada , una etiqueta es un identificador que suele aparecer al principio de una línea y va inmediatamente seguido de dos puntos. Por ejemplo, en C:
Éxito : printf ( "La operación fue exitosa. \n " );
El lenguaje ALGOL 60 permitía tanto números enteros como identificadores como etiquetas (ambos vinculados mediante dos puntos a la siguiente declaración), pero pocas variantes de ALGOL, si es que había alguna , permitían números enteros. Los primeros compiladores de Fortran solo permitían números enteros como etiquetas. A partir de Fortran-90, también se permitieron etiquetas alfanuméricas.
La declaración goto (una combinación de las palabras inglesas go y to , pronunciadas respectivamente) es la forma más básica de transferencia incondicional de control.
Aunque la palabra clave puede estar en mayúsculas o minúsculas según el idioma, normalmente se escribe así:
Ir a la etiqueta
El efecto de una instrucción goto es hacer que la siguiente instrucción que se ejecutará sea la instrucción que aparece en (o inmediatamente después de) la etiqueta indicada.
Muchos científicos informáticos, especialmente Dijkstra , han considerado que las declaraciones goto son dañinas .
La terminología para las subrutinas varía; pueden conocerse alternativamente como rutinas, procedimientos, funciones (especialmente si devuelven resultados) o métodos (especialmente si pertenecen a clases o clases de tipos ).
En la década de 1950, las memorias de las computadoras eran muy pequeñas para los estándares actuales, por lo que se usaban subrutinas principalmente para reducir el tamaño del programa. Un fragmento de código se escribía una vez y luego se usaba muchas veces desde varios otros lugares en un programa.
En la actualidad, las subrutinas se utilizan con más frecuencia para ayudar a que un programa sea más estructurado, por ejemplo, aislando algún algoritmo u ocultando algún método de acceso a datos. Si muchos programadores trabajan en un programa, las subrutinas son un tipo de modularidad que puede ayudar a dividir el trabajo.
En la programación estructurada, la secuenciación ordenada de comandos sucesivos se considera una de las estructuras de control básicas, que se utiliza como elemento fundamental para los programas junto con la iteración, la recursión y la elección.
En mayo de 1966, Böhm y Jacopini publicaron un artículo [1] en Communications of the ACM que mostraba que cualquier programa con goto podía transformarse en una forma sin goto que involucrara solo elección (IF THEN ELSE) y bucles (WHILE condición DO xxx), posiblemente con código duplicado y/o la adición de variables booleanas (banderas de verdadero/falso). Autores posteriores demostraron que la elección puede reemplazarse por bucles (y aún más variables booleanas).
Que tal minimalismo sea posible no significa que sea necesariamente deseable; después de todo, las computadoras teóricamente necesitan sólo una instrucción de máquina (restar un número de otro y bifurcar si el resultado es negativo), pero las computadoras prácticas tienen docenas o incluso cientos de instrucciones de máquina.
Lo que demostró el artículo de Böhm y Jacopini fue que todos los programas podían ser libres de goto. Otras investigaciones demostraron que las estructuras de control con una entrada y una salida eran mucho más fáciles de entender que cualquier otra forma, [ cita requerida ] principalmente porque podían usarse en cualquier lugar como una declaración sin interrumpir el flujo de control. En otras palabras, eran componibles . (Desarrollos posteriores, como lenguajes de programación no estrictos – y más recientemente, transacciones de software componibles – han continuado esta estrategia, haciendo que los componentes de los programas sean aún más libremente componibles.)
Algunos académicos adoptaron un enfoque purista del resultado de Böhm-Jacopini y argumentaron que incluso instrucciones como break
y return
desde el medio de los bucles son una mala práctica, ya que no son necesarias en la prueba de Böhm-Jacopini, y por lo tanto defendieron que todos los bucles deberían tener un único punto de salida. Este enfoque purista está incorporado en el lenguaje Pascal (diseñado en 1968-1969), que hasta mediados de la década de 1990 fue la herramienta preferida para la enseñanza de programación introductoria en el ámbito académico. [2] La aplicación directa del teorema de Böhm-Jacopini puede dar como resultado la introducción de variables locales adicionales en el diagrama estructurado, y también puede dar como resultado cierta duplicación de código . [3] Pascal se ve afectado por ambos problemas y, según estudios empíricos citados por Eric S. Roberts , los estudiantes de programación tenían dificultades para formular soluciones correctas en Pascal para varios problemas simples, incluida la escritura de una función para buscar un elemento en una matriz. Un estudio de 1980 realizado por Henry Shapiro citado por Roberts encontró que utilizando solo las estructuras de control proporcionadas por Pascal, la solución correcta fue dada solo por el 20% de los sujetos, mientras que ningún sujeto escribió código incorrecto para este problema si se le permitió escribir un retorno desde el medio de un bucle. [2]
La mayoría de los lenguajes de programación con estructuras de control tienen una palabra clave inicial que indica el tipo de estructura de control involucrada. [ aclaración necesaria ] Los lenguajes luego se dividen en función de si las estructuras de control tienen o no una palabra clave final.
begin
...end
{
...}
DO
...END
do
...end
end
+ espacio + palabra clave inicial, por ejemplo, if
... end if
, loop
...end loop
:End
opcionalmente + palabra clave inicial, p. ej., :If
... :End
o :If
... :EndIf
, Select
... :End
o :Select
... :EndSelect
, sin embargo, si se agrega una condición final, la palabra clave final se convierte en:Until
if
... fi
, case
...esac
END
+ palabra clave inicial, por ejemplo, IF
... ENDIF
, DO
...ENDDO
END
para todoIf
... End If
; For
... Next
; Do
... Loop
; While
...Wend
Las expresiones condicionales y las construcciones condicionales son características de un lenguaje de programación que realizan diferentes cálculos o acciones dependiendo de si una condición booleana especificada por el programador se evalúa como verdadera o falsa.
IF..GOTO
Una forma que se encuentra en lenguajes no estructurados, que imita una instrucción de código de máquina típica, saltaría a (GOTO) una etiqueta o número de línea cuando se cumpliera la condición.IF..THEN..(ENDIF)
En lugar de limitarse a un salto, cualquier declaración simple o bloque anidado podría seguir a la palabra clave THEN. Esta es una forma estructurada.IF..THEN..ELSE..(ENDIF)
. Como el anterior, pero con una segunda acción a realizar si la condición es falsa. Esta es una de las formas más comunes, con muchas variaciones. Algunas requieren una terminal ENDIF
, otras no. C y los lenguajes relacionados no requieren una palabra clave terminal, o un 'then', pero sí requieren paréntesis alrededor de la condición.ELSE
y IF
se combinen en ELSEIF
, lo que evita la necesidad de tener una serie de ENDIF
u otras sentencias finales al final de una sentencia compuesta.Las variaciones menos comunes incluyen:
if
declaración, por ejemplo , Lisp cond
.if
declaración, como el operador ternario de C.if
con when
y unless
.ifTrue
mensajes ifFalse
para implementar condicionales, en lugar de cualquier construcción fundamental del lenguaje.Las sentencias switch (o sentencias case o ramas multidireccionales ) comparan un valor dado con constantes especificadas y toman medidas de acuerdo con la primera constante que coincida. Normalmente hay una disposición para que se tome una acción predeterminada ("else", "otherwise") si no se logra ninguna coincidencia. Las sentencias switch pueden permitir optimizaciones del compilador, como tablas de búsqueda . En lenguajes dinámicos , los casos pueden no limitarse a expresiones constantes y pueden extenderse a la coincidencia de patrones , como en el ejemplo de script de shell a la derecha, donde *)
implementa el caso predeterminado como un glob que coincide con cualquier cadena. La lógica de caso también se puede implementar en forma funcional, como en la sentencia de SQLdecode
.
Un bucle es una secuencia de instrucciones que se especifica una vez pero que puede ejecutarse varias veces seguidas. El código "dentro" del bucle (el cuerpo del bucle, que se muestra a continuación como xxx ) se ejecuta una cantidad específica de veces, o una vez por cada elemento de una colección, o hasta que se cumpla alguna condición, o indefinidamente . Cuando uno de esos elementos es también un bucle, se denomina "bucle anidado". [4] [5] [6]
En los lenguajes de programación funcional , como Haskell y Scheme , los procesos recursivos e iterativos se expresan con procedimientos recursivos de cola en lugar de construcciones de bucle que son sintácticas.
La mayoría de los lenguajes de programación tienen construcciones para repetir un bucle una cierta cantidad de veces. En la mayoría de los casos, el conteo puede realizarse hacia abajo en lugar de hacia arriba y se pueden utilizar tamaños de paso distintos de 1.
En estos ejemplos, si N < 1, entonces el cuerpo del bucle puede ejecutarse una vez (siendo I el valor 1) o no ejecutarse en absoluto, dependiendo del lenguaje de programación.
En muchos lenguajes de programación, solo se pueden utilizar números enteros de manera confiable en un bucle controlado por conteo. Los números de punto flotante se representan de manera imprecisa debido a las limitaciones del hardware, por lo que un bucle como
para X := 0,1 paso 0,1 a 1,0 hacer
Puede repetirse 9 o 10 veces, dependiendo de los errores de redondeo y/o del hardware y/o de la versión del compilador. Además, si el incremento de X se produce por adición repetida, los errores de redondeo acumulados pueden significar que el valor de X en cada iteración puede diferir bastante de la secuencia esperada 0,1, 0,2, 0,3, ..., 1,0.
La mayoría de los lenguajes de programación tienen construcciones para repetir un bucle hasta que cambie alguna condición. Algunas variaciones prueban la condición al comienzo del bucle; otras la prueban al final. Si la prueba se realiza al comienzo, el cuerpo puede omitirse por completo; si se realiza al final, el cuerpo siempre se ejecuta al menos una vez.
Una interrupción de control es un método de detección de cambios de valores que se utiliza en bucles ordinarios para activar el procesamiento de grupos de valores. Los valores se controlan dentro del bucle y un cambio desvía el flujo del programa hacia el manejo del evento de grupo asociado con ellos.
HACER HASTA (Fin del archivo) SI nuevo código postal <> código postal actual display_tally(código postal actual, número de código postal) código postal actual = nuevo código postal recuento de zip = 0 FINALIZAR SI recuento de zip++ BUCLE
Varios lenguajes de programación (por ejemplo, Ada , D , C++11 , Smalltalk , PHP , Perl , Object Pascal , Java , C# , MATLAB , Visual Basic , Ruby , Python , JavaScript , Fortran 95 y posteriores) tienen construcciones especiales que permiten realizar bucles implícitos a través de todos los elementos de una matriz o de todos los miembros de un conjunto o colección.
algunaColección hace : [:eachElement |xxx]. para el artículo de la colección hacer comienza xxx y termina ; foreach (elemento; miColección) { xxx } para cada una algunaMatriz { xxx } foreach ($algunarray como $k => $v) { xxx } Colección<Cadena> coll; para (Cadena s: coll) {} foreach ( cadena s en myStringCollection) { xxx } algunaColección | ParaCadaObjeto { $_ } paratodos (índice = primero:último:paso...)
Scala tiene expresiones for , que generalizan los bucles controlados por colecciones y también admiten otros usos, como la programación asincrónica . Haskell tiene expresiones do y comprensiones, que juntas brindan una función similar a las expresiones for en Scala.
Las construcciones de iteración generales, como for
la declaración de C y la forma de Common Lispdo
, se pueden utilizar para expresar cualquiera de los tipos de bucles anteriores y otros, como la ejecución de bucles sobre una cierta cantidad de colecciones en paralelo. Cuando se puede utilizar una construcción de bucle más específica, normalmente se prefiere a la construcción de iteración general, ya que a menudo aclara el propósito de la expresión.
Los bucles infinitos se utilizan para garantizar que un segmento de programa se repita indefinidamente o hasta que surja una condición excepcional, como un error. Por ejemplo, un programa controlado por eventos (como un servidor ) debería repetirse indefinidamente, manejando los eventos a medida que ocurren y deteniéndose solo cuando un operador finaliza el proceso.
Los bucles infinitos se pueden implementar utilizando otras construcciones de flujo de control. Lo más común, en programación no estructurada, es un salto hacia arriba (goto), mientras que en programación estructurada es un bucle indefinido (bucle while) configurado para que nunca termine, ya sea omitiendo la condición o estableciéndola explícitamente como verdadera, como while (true) ...
. Algunos lenguajes tienen construcciones especiales para bucles infinitos, generalmente omitiendo la condición de un bucle indefinido. Algunos ejemplos incluyen Ada ( loop ... end loop
), [7] Fortran ( DO ... END DO
), Go ( for { ... }
) y Ruby ( loop do ... end
).
A menudo, un bucle infinito se crea involuntariamente por un error de programación en un bucle controlado por condiciones, en donde la condición del bucle utiliza variables que nunca cambian dentro del bucle.
A veces, dentro del cuerpo de un bucle existe el deseo de omitir el resto del cuerpo del bucle y continuar con la siguiente iteración del bucle. Algunos lenguajes proporcionan una declaración como continue
(la mayoría de los lenguajes), skip
[ 8] cycle
(Fortran) o next
(Perl y Ruby), que hará esto. El efecto es terminar prematuramente el cuerpo del bucle más interno y luego continuar de manera normal con la siguiente iteración. Si la iteración es la última del bucle, el efecto es terminar todo el bucle antes de tiempo.
Algunos lenguajes, como Perl [9] y Ruby, [10] tienen una redo
declaración que reinicia la iteración actual desde el principio.
Ruby tiene una retry
declaración que reinicia todo el bucle desde la iteración inicial. [11]
Al utilizar un bucle controlado por conteo para buscar en una tabla, puede ser conveniente detener la búsqueda tan pronto como se encuentre el elemento requerido. Algunos lenguajes de programación proporcionan una instrucción como break
(la mayoría de los lenguajes), Exit
(Visual Basic) o last
(Perl), cuyo efecto es terminar el bucle actual inmediatamente y transferir el control a la instrucción inmediatamente después de ese bucle. Otro término para los bucles de salida anticipada es bucle y medio .
El siguiente ejemplo se realizó en Ada , que admite tanto la salida temprana de bucles como los bucles con una prueba en el medio . Ambas funciones son muy similares y, al comparar ambos fragmentos de código, se verá la diferencia: la salida temprana debe combinarse con una declaración if , mientras que una condición en el medio es una construcción autónoma.
con Ada.Text IO ; con Ada.Integer Text IO ;procedimiento Imprimir_Cuadrados es X : Entero ; comienzo Leer_Datos : bucle Ada . Entero Texto IO . Obtener ( X ); salir Leer_Datos cuando X = 0 ; Ada . Texto IO . Poner ( X * X ); Ada . Texto IO . Nueva_Línea ; fin bucle Leer_Datos ; fin Imprimir_Cuadrados ;
Python admite la ejecución condicional de código en función de si se salió de un bucle antes de tiempo (con una break
declaración) o no mediante el uso de una cláusula else con el bucle. Por ejemplo,
para n en conjunto_de_números : si es primo ( n ): imprimir ( "El conjunto contiene un número primo" ) romper de lo contrario : imprimir ( "El conjunto no contenía ningún número primo" )
La else
cláusula del ejemplo anterior está vinculada a la for
declaración, no a la if
declaración interna. Tanto Python for
como while
los bucles admiten una cláusula else de este tipo, que se ejecuta solo si no se ha producido una salida anticipada del bucle.
Algunos lenguajes permiten salir de bucles anidados; en teoría, se denominan saltos multinivel. Un ejemplo de uso común es la búsqueda en una tabla multidimensional. Esto se puede hacer mediante saltos multinivel (salir de N niveles), como en bash [12] y PHP, [13] o mediante saltos etiquetados (salir y continuar en la etiqueta dada), como en Go, Java y Perl. [14] Las alternativas a los saltos multinivel incluyen saltos simples, junto con una variable de estado que se prueba para salir de otro nivel; excepciones, que se capturan en el nivel al que se va a salir; colocar los bucles anidados en una función y usar return para efectuar la terminación de todo el bucle anidado; o usar una etiqueta y una declaración goto. C no incluye un salto multinivel, y la alternativa habitual es usar un goto para implementar un salto etiquetado. [15] Python no tiene un salto multinivel o una continuación; esto se propuso en PEP 3136 y se rechazó sobre la base de que la complejidad añadida no valía la pena el raro uso legítimo. [16]
La noción de rupturas multinivel es de cierto interés en la informática teórica , porque da lugar a lo que hoy se llama la jerarquía de Kosaraju . [17] En 1973, S. Rao Kosaraju refinó el teorema del programa estructurado al demostrar que es posible evitar agregar variables adicionales en la programación estructurada, siempre que se permitan rupturas multinivel de profundidad arbitraria de los bucles. [18] Además, Kosaraju demostró que existe una estricta jerarquía de programas: para cada entero n , existe un programa que contiene una ruptura multinivel de profundidad n que no se puede reescribir como un programa con rupturas multinivel de profundidad menor que n sin introducir variables adicionales. [17]
También es posible return
salir de una subrutina que ejecuta las sentencias en bucle, rompiendo tanto el bucle anidado como la subrutina. Existen otras estructuras de control propuestas para interrupciones múltiples, pero generalmente se implementan como excepciones.
En su libro de texto de 2004, David Watt utiliza la noción de secuenciador de Tennent para explicar la similitud entre los saltos de varios niveles y las declaraciones de retorno. Watt señala que una clase de secuenciadores conocidos como secuenciadores de escape , definidos como "secuenciadores que terminan la ejecución de un comando o procedimiento que encierra texto", abarca tanto los saltos de bucles (incluidos los saltos de varios niveles) como las declaraciones de retorno. Sin embargo, tal como se implementan comúnmente, los secuenciadores de retorno también pueden llevar un valor (de retorno), mientras que el secuenciador de salto tal como se implementa en los lenguajes contemporáneos por lo general no puede. [19]
Las variantes de bucle y los invariantes de bucle se utilizan para expresar la corrección de los bucles. [20]
En términos prácticos, una variante de bucle es una expresión entera que tiene un valor inicial no negativo. El valor de la variante debe disminuir durante cada iteración del bucle, pero nunca debe volverse negativo durante la ejecución correcta del bucle. Las variantes de bucle se utilizan para garantizar que los bucles terminen.
Un invariante de bucle es una afirmación que debe ser verdadera antes de la primera iteración del bucle y permanecer verdadera después de cada iteración. Esto implica que cuando un bucle termina correctamente, se cumplen tanto la condición de salida como el invariante de bucle. Los invariantes de bucle se utilizan para monitorear propiedades específicas de un bucle durante iteraciones sucesivas.
Algunos lenguajes de programación, como Eiffel, contienen compatibilidad nativa con variantes e invariantes de bucles. En otros casos, la compatibilidad es un complemento, como la especificación del lenguaje de modelado Java para instrucciones de bucles en Java .
Algunos dialectos de Lisp proporcionan un sublenguaje extenso para describir bucles. Un ejemplo temprano se puede encontrar en Conversional Lisp de Interlisp . Common Lisp [21] proporciona una macro Loop que implementa dicho sublenguaje.
while (true)
no cuenta como un bucle infinito para este propósito, porque no es una estructura de lenguaje dedicada.for (init; test; increment)
range()
while
función se puede utilizar para esto.std::for_each
de plantilla que puede iterar sobre contenedores STL y llamar a una función unaria para cada elemento. [22] La funcionalidad también se puede construir como macro en estos contenedores. [23]retry
, sin embargo se utiliza en el manejo de excepciones , no en el control de bucles.GO TO
y .Muchos lenguajes de programación, especialmente aquellos que favorecen estilos de programación más dinámicos, ofrecen construcciones para el flujo de control no local . Estas hacen que el flujo de ejecución salte de un contexto determinado y se reanude en algún punto predeclarado. Las condiciones , las excepciones y las continuaciones son tres tipos comunes de construcciones de control no locales; también existen otras más exóticas, como los generadores , las corrutinas y la palabra clave async .
PL/I tiene alrededor de 22 condiciones estándar (por ejemplo, ZERODIVIDE SUBSCRIPTRANGE ENDFILE) que se pueden generar y que pueden ser interceptadas por: la acción de condición ON ; los programadores también pueden definir y usar sus propias condiciones con nombre.
Al igual que el if no estructurado , solo se puede especificar una declaración, por lo que en muchos casos se necesita un GOTO para decidir dónde se debe reanudar el flujo de control.
Desafortunadamente, algunas implementaciones tenían una sobrecarga sustancial tanto en espacio como en tiempo (especialmente SUBSCRIPTRANGE), por lo que muchos programadores intentaron evitar el uso de condiciones.
Ejemplos de sintaxis comunes:
Etiqueta GOTO de condición ON
Los lenguajes modernos tienen una estructura especializada para el manejo de excepciones que no depende del uso de GOTO
saltos o retornos (multinivel). Por ejemplo, en C++ se puede escribir:
try { xxx1 // En algún lugar aquí xxx2 // uso: '''throw''' someValue; xxx3 } catch ( someClass & someId ) { // captura el valor de someClass actionForSomeClass } catch ( someType & anotherId ) { // captura el valor de someType actionForSomeType } catch (...) { // captura todo lo que no haya sido capturado ya actionForAnythingElse }
Se puede utilizar cualquier cantidad y variedad de catch
cláusulas. Si no hay catch
una coincidencia con una determinada throw
, el control se transmite a través de llamadas a subrutinas o bloques anidados hasta que catch
se encuentra una coincidencia o hasta que se llega al final del programa principal, momento en el que el programa se detiene forzosamente con un mensaje de error adecuado.
Por influencia de C++, catch
es la palabra clave reservada para declarar un manejador de excepciones de coincidencia de patrones en otros lenguajes populares hoy en día, como Java o C#. Algunos otros lenguajes como Ada usan la palabra clave exception
para introducir un manejador de excepciones y luego pueden incluso emplear una palabra clave diferente ( when
en Ada) para la coincidencia de patrones. Algunos lenguajes como AppleScript incorporan marcadores de posición en la sintaxis del manejador de excepciones para extraer automáticamente varias piezas de información cuando ocurre la excepción. Este enfoque se ejemplifica a continuación con la on error
construcción de AppleScript:
Intente establecer myNumber en myNumber / 0 en caso de error e número n de f a t resultado parcial pr if ( e = "No se puede dividir por cero" ) entonces muestre el cuadro de diálogo "No debe hacer eso" fin del intento
El libro de texto de David Watt de 2004 también analiza el manejo de excepciones en el marco de los secuenciadores (que se presenta en este artículo en la sección sobre salidas tempranas de bucles). Watt señala que una situación anormal, generalmente ejemplificada con desbordamientos aritméticos o fallas de entrada/salida como archivo no encontrado, es un tipo de error que "se detecta en alguna unidad de programa de bajo nivel, pero [para el cual] un manejador se ubica de manera más natural en una unidad de programa de alto nivel". Por ejemplo, un programa puede contener varias llamadas para leer archivos, pero la acción a realizar cuando no se encuentra un archivo depende del significado (propósito) del archivo en cuestión para el programa y, por lo tanto, una rutina de manejo para esta situación anormal no se puede ubicar en el código de sistema de bajo nivel. Watts señala además que la introducción de pruebas de indicadores de estado en el llamador, como implicaría la programación estructurada de salida única o incluso los secuenciadores de retorno (de múltiples salidas), da como resultado una situación en la que "el código de la aplicación tiende a verse abarrotado de pruebas de indicadores de estado" y que "el programador podría omitir, por olvido o por pereza, probar un indicador de estado. De hecho, las situaciones anormales representadas por indicadores de estado se ignoran por defecto". Watt señala que, en contraste con las pruebas de indicadores de estado, las excepciones tienen el comportamiento predeterminado opuesto , lo que hace que el programa finalice a menos que el programa trate la excepción explícitamente de alguna manera, posiblemente agregando código explícito para ignorarla. Basándose en estos argumentos, Watt concluye que los secuenciadores de salto o los secuenciadores de escape son menos adecuados como secuenciador de excepciones dedicado con la semántica discutida anteriormente. [24]
En Object Pascal, D, Java, C# y Python, finally
se puede agregar una cláusula a la try
construcción. Sin importar cómo se deje el control, se garantiza que el try
código dentro de la finally
cláusula se ejecutará. Esto es útil cuando se escribe código que debe renunciar a un recurso costoso (como un archivo abierto o una conexión a una base de datos) cuando finaliza el procesamiento:
FileStream stm = null ; // Ejemplo de C# try { stm = new FileStream ( " logfile.txt " , FileMode.Create ); return ProcessStuff ( stm ); // puede generar una excepción } finally { if ( stm != null ) stm.Close ( ) ; }
Dado que este patrón es bastante común, C# tiene una sintaxis especial:
usando ( var stm = new FileStream ( "logfile.txt" , FileMode.Create ) ) { return ProcessStuff ( stm ) ; // puede generar una excepción }
Al salir del using
bloque -, el compilador garantiza que el stm
objeto se libere, vinculando efectivamente la variable al flujo de archivo mientras se abstrae de los efectos secundarios de inicializar y liberar el archivo. with
La declaración de Python y el argumento de bloque to de Ruby File.open
se utilizan con un efecto similar.
Todos los lenguajes mencionados anteriormente definen excepciones estándar y las circunstancias en las que se lanzan. Los usuarios pueden lanzar sus propias excepciones; C++ permite a los usuarios lanzar y capturar casi cualquier tipo, incluidos los tipos básicos como int
, mientras que otros lenguajes como Java son menos permisivos.
C# 5.0 introdujo la palabra clave async para admitir E/S asincrónica en un "estilo directo".
Los generadores , también conocidos como semicorrutinas, permiten ceder el control a un método de consumidor temporalmente, generalmente mediante una yield
palabra clave (descripción de rendimiento). Al igual que la palabra clave async, esto permite programar en un "estilo directo".
Las corrutinas son funciones que pueden cederse el control entre sí: una forma de multitarea cooperativa sin subprocesos.
Las corrutinas se pueden implementar como una biblioteca si el lenguaje de programación proporciona continuaciones o generadores, por lo que la distinción entre corrutinas y generadores en la práctica es un detalle técnico.
En un artículo de parodia de Datamation [31] de 1973, R. Lawrence Clark sugirió que la instrucción GOTO podría reemplazarse por la instrucción COMEFROM y brinda algunos ejemplos entretenidos. COMEFROM se implementó en un lenguaje de programación esotérico llamado INTERCAL .
El artículo de Donald Knuth de 1974 "Programación estructurada con instrucciones go to" [32] identifica dos situaciones que no estaban contempladas por las estructuras de control mencionadas anteriormente y ofrece ejemplos de estructuras de control que podrían manejar estas situaciones. A pesar de su utilidad, estas estructuras aún no han encontrado su lugar en los lenguajes de programación convencionales.
Lo siguiente fue propuesto por Dahl en 1972: [33]
bucle bucle xxx1 leer(carácter); mientras prueba; mientras no esté enFinalDelArchivo; xxx2 escribir(char); repetir ; repetir ;
Si se omite xxx1 , obtenemos un bucle con la prueba en la parte superior (un bucle while tradicional ). Si se omite xxx2 , obtenemos un bucle con la prueba en la parte inferior, equivalente a un bucle do while en muchos lenguajes. Si se omite while , obtenemos un bucle infinito. La construcción aquí puede considerarse como un bucle do con la comprobación while en el medio. Por lo tanto, esta única construcción puede reemplazar varias construcciones en la mayoría de los lenguajes de programación.
Los lenguajes que carecen de esta construcción generalmente la emulan utilizando un modismo equivalente de bucle infinito con interrupción:
mientras (verdadero) { xxx1 Si ( no prueba) se rompe xxx2}
Una variante posible es permitir más de una prueba while dentro del bucle, pero el uso de exitwhen (ver la siguiente sección) parece cubrir mejor este caso.
En Ada , la construcción de bucle anterior ( loop - while - repeat ) se puede representar utilizando un bucle infinito estándar ( loop - end loop ) que tiene una cláusula exit when en el medio (que no debe confundirse con la declaración exitwhen en la siguiente sección).
con Ada.Text_IO ; con Ada.Integer_Text_IO ;procedimiento Imprimir_Cuadrados es X : Entero ; comienzo Leer_Datos : bucle Ada . Entero_Texto_IO . Obtener ( X ); salir Leer_Datos cuando X = 0 ; Ada . Texto IO . Poner ( X * X ); Ada . Texto IO . Nueva_Línea ; fin bucle Leer_Datos ; fin Imprimir_Cuadrados ;
Nombrar un bucle (como Read_Data en este ejemplo) es opcional, pero permite salir del bucle externo de varios bucles anidados.
Este constructo fue propuesto por Zahn en 1974. [34] Aquí se presenta una versión modificada.
salircuando EventoA o EventoB o EventoC; xxx salidas EventoA: acciónA EventoB: acciónB EventoC: acciónC fin deexito ;
exitwhen se utiliza para especificar los eventos que pueden ocurrir dentro de xxx , su ocurrencia se indica utilizando el nombre del evento como una declaración. Cuando ocurre algún evento, se lleva a cabo la acción relevante y luego el control pasa justo después de endexit . Esta construcción proporciona una separación muy clara entre la determinación de que se aplica cierta situación y la acción que se debe tomar para esa situación.
exitwhen es conceptualmente similar al manejo de excepciones , y se utilizan excepciones o construcciones similares para este propósito en muchos lenguajes.
El siguiente ejemplo sencillo implica buscar un elemento particular en una tabla bidimensional.
salir cuando se encuentra o falta; para I := 1 a N hacer para J := 1 a M hacer si tabla[I,J] = objetivo entonces se encuentra; desaparecido; salidas encontrado: imprimir ("el artículo está en la tabla"); faltante: imprimir ("el elemento no está en la tabla"); fin deexito ;
Una forma de atacar un software es redirigir el flujo de ejecución de un programa. Para defenderse de estos ataques se utilizan diversas técnicas de integridad del flujo de control , como los canarios de pila , la protección contra desbordamiento de búfer , las pilas ocultas y la verificación de punteros de tablas virtuales . [35] [36] [37]