En informática , el flujo de control (o flujo de control ) es el orden en el que se ejecutan o evalúan declaraciones individuales , instrucciones 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 .
Dentro de un lenguaje de programación imperativo , una declaración de flujo de control es una declaración que da como resultado una elección sobre cuál de dos o más caminos seguir. Para los lenguajes funcionales no estrictos , existen funciones y construcciones de lenguaje para lograr el mismo resultado, pero generalmente no se denominan declaraciones 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 un proceso en línea. declaración de flujo de control.
A nivel de lenguaje de máquina o lenguaje ensamblador , las instrucciones de flujo de control generalmente funcionan alterando el contador del programa . Para algunas unidades centrales de procesamiento (CPU), las únicas instrucciones de flujo de control disponibles son instrucciones de bifurcación condicionales o incondicionales , también denominadas saltos.
Los tipos de declaraciones de flujo de control admitidas 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 se puede hacer referencia mediante declaraciones 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 una etiqueta con nombre utilizada en algunos idiomas (como BASIC ). Son números enteros colocados al comienzo de cada línea de texto en el código fuente. Los idiomas que los utilizan a menudo imponen la restricción de que los números de línea deben aumentar de valor en cada línea siguiente, pero es posible que no requieran que sean consecutivos. Por ejemplo, en BÁSICO:
10 DEJAR X = 3 20 IMPRIMIR X
En otros lenguajes como C y Ada , una etiqueta es un identificador , que generalmente aparece al comienzo de una línea e 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 unidos por dos puntos a la siguiente declaración), pero pocas o ninguna otra variante de ALGOL permitía números enteros. Los primeros compiladores de Fortran solo permitían números enteros como etiquetas. A partir de Fortran-90, también se permiten etiquetas alfanuméricas.
La declaración goto (una combinación de las palabras en inglés go y to y pronunciadas en consecuencia) 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 como:
ir a la etiqueta
El efecto de una declaración goto es hacer que la siguiente declaración que se ejecute sea la declaración que aparece en (o inmediatamente después) de la etiqueta indicada.
Muchos científicos informáticos, en particular Dijkstra , han considerado dañinas las declaraciones Goto .
La terminología para las subrutinas varía; Alternativamente, pueden conocerse 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 según los estándares actuales, por lo que se usaban subrutinas principalmente [ cita necesaria ] para reducir el tamaño del programa. Un fragmento de código se escribió una vez y luego se usó muchas veces desde otros lugares de un programa.
Hoy en día, las subrutinas se utilizan con mayor frecuencia para ayudar a que un programa esté 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 secuencia ordenada de comandos sucesivos se considera una de las estructuras de control básicas, que se utiliza como componente básico de los programas junto con la iteración, la recursividad 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 s podía transformarse en una forma libre de goto que implicaba únicamente elección (IF THEN ELSE) y bucles (WHILE condition 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 ser reemplazada por bucles (y aún más variables booleanas).
Que tal minimalismo sea posible no significa que sea necesariamente deseable; después de todo, en teoría las computadoras sólo necesitan una instrucción de máquina (resta 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 mostró el artículo de Böhm y Jacopini fue que todos los programas podían ser libres de acceso. Otra investigación demostró que las estructuras de control con una entrada y una salida eran mucho más fáciles de entender que cualquier otra forma, [ cita necesaria ] principalmente porque podían usarse en cualquier lugar como una declaración sin interrumpir el flujo de control. En otras palabras, eran componibles . (Los desarrollos posteriores, como los lenguajes de programación no estrictos y, más recientemente, las transacciones de software componibles , han continuado con esta estrategia, haciendo que los componentes de los programas sean aún más 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, abogaron por que todos los bucles deberían tener un único punto de salida. Este enfoque purista está plasmado en el lenguaje Pascal (diseñado en 1968-1969), que hasta mediados de la década de 1990 era la herramienta preferida para enseñar introducción a la programación en el mundo académico. [2] La aplicación directa del teorema de Böhm-Jacopini puede dar lugar a la introducción de variables locales adicionales en el gráfico estructurado y también puede dar lugar a 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 tuvieron 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 de Henry Shapiro citado por Roberts encontró que usando sólo las estructuras de control proporcionadas por Pascal, la solución correcta fue dada por sólo el 20% de los sujetos, mientras que ningún sujeto escribió código incorrecto para este problema si se le permitía escribir una respuesta del 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. [ se necesita aclaración ] Los idiomas 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, por ejemplo, :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: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
. Un formulario que se encuentra en lenguajes no estructurados, que imita una instrucción típica de código de máquina, saltaría a (IR A) 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 arriba, 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. Algunos requieren una terminal ENDIF
, otros no. C y los lenguajes relacionados no requieren una palabra clave de terminal o un "entonces", pero sí requieren paréntesis alrededor de la condición.ELSE
que y IF
se combinen en ELSEIF
, evitando la necesidad de tener una serie de ENDIF
u otras declaraciones finales al final de una declaración 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
y ifFalse
envía mensajes para implementar condicionales, en lugar de cualquier construcción de lenguaje fundamental.Las declaraciones de cambio (o declaraciones de caso , o ramas multidireccionales ) comparan un valor dado con constantes específicas y toman medidas de acuerdo con la primera constante que coincida. Por lo general, existe una disposición para que se tome una acción predeterminada ("else", "de lo contrario") si ninguna coincidencia tiene éxito. Las declaraciones de cambio pueden permitir optimizaciones del compilador, como tablas de búsqueda . En los lenguajes dinámicos , los casos pueden no limitarse a expresiones constantes y pueden extenderse a la coincidencia de patrones , como en el ejemplo del script de shell de la derecha, donde *)
implementa el caso predeterminado como un globo que coincide con cualquier cadena. La lógica de casos también se puede implementar en forma funcional, como en la declaración de SQLdecode
.
Un bucle es una secuencia de declaraciones que se especifica una vez pero que se puede ejecutar varias veces seguidas. El código "dentro" del bucle (el cuerpo del bucle, que se muestra a continuación como xxx ) se obedece un número específico de veces, o una vez para cada uno de una colección de elementos, o hasta que se cumpla alguna condición, o indefinidamente .
En lenguajes de programación funcionales , como Haskell y Scheme , tanto los procesos recursivos como los iterativos se expresan con procedimientos recursivos de cola en lugar de construcciones en bucle que son sintácticas.
La mayoría de los lenguajes de programación tienen construcciones para repetir un bucle un número determinado de veces. En la mayoría de los casos, el conteo puede ir 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 (donde I tiene el valor 1) o no ejecutarse en absoluto, según el lenguaje de programación.
En muchos lenguajes de programación, sólo se pueden utilizar de forma fiable números enteros en un bucle controlado por conteo. Los números de coma flotante se representan de manera imprecisa debido a restricciones de 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 mediante sumas repetidas, los errores de redondeo acumulados pueden significar que el valor de X en cada iteración puede diferir de manera bastante significativa 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 inicio del ciclo; otros lo prueban al final. Si la prueba es al principio, el cuerpo puede omitirse por completo; si está al final, el cuerpo siempre se ejecuta al menos una vez.
Una interrupción de control es un método de detección de cambio de valor que se utiliza dentro de bucles ordinarios para activar el procesamiento de grupos de valores. Los valores se monitorean 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) IF nuevo código postal <> código postal actual display_tally(código postal actual, recuento postal) código postal actual = nuevo código postal recuento zip = 0 TERMINARA SI cuenta 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 implícitamente recorrer todos los elementos de una matriz, o todos los miembros de un conjunto o colección.
algunaColección hacer : [:cadaElemento |xxx]. para el artículo de la colección , comience xxx y finalice ; foreach (artículo; miColección) { xxx } foreach alguna matriz { xxx } foreach ($algunarray como $k => $v) { xxx } Colección<String> coll; para (Cadena s: coll) {} foreach ( cadena s en myStringCollection) { xxx } algunaColección | Para cada objeto { $_ } forall (í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 proporcionan 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 recorrer un número determinado 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 asegurar que un segmento de programa se repita para siempre o hasta que surja una condición excepcional, como un error. Por ejemplo, un programa controlado por eventos (como un servidor ) debe repetirse indefinidamente, manejando los eventos a medida que ocurren y deteniéndose únicamente cuando un operador finaliza el proceso.
Se pueden implementar bucles infinitos utilizando otras construcciones de flujo de control. Lo más común es que en programación no estructurada se trate de un salto hacia atrás (ir a), mientras que en programación estructurada se trata de un bucle indefinido (bucle while) configurado para no terminar nunca, ya sea omitiendo la condición o estableciéndola explícitamente en verdadero, como while (true) ...
. Algunos lenguajes tienen construcciones especiales para bucles infinitos, normalmente omitiendo la condición de un bucle indefinido. Los ejemplos incluyen Ada ( loop ... end loop
), [4] 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 condición, en el que 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
[ 5] cycle
(Fortran) o next
(Perl y Ruby), que hará esto. El efecto es terminar prematuramente el cuerpo del bucle más interno y luego reanudarlo normalmente con la siguiente iteración. Si la iteración es la última del ciclo, el efecto es terminar todo el ciclo antes de tiempo.
Algunos lenguajes, como Perl [6] y Ruby, [7] tienen una redo
declaración que reinicia la iteración actual desde el principio.
Ruby tiene una retry
declaración que reinicia todo el ciclo desde la iteración inicial. [8]
Cuando se utiliza 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 declaració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 declaración inmediatamente después de ese bucle. Otro término para los bucles de salida anticipada es bucle y medio .
El siguiente ejemplo se realiza en Ada , que admite tanto la salida temprana de los bucles como los bucles con prueba en el medio . Ambas características son muy similares y la comparación de ambos fragmentos de código mostrará la diferencia: la salida anticipada 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 Texto IO ;procedimiento Print_Squares es X : Entero ; comenzar Read_Data : bucle Ada . Texto entero IO . Obtener ( X ); salir de Read_Data cuando X = 0 ; Ada . Texto IO . Poner ( X * X ); Ada . Texto IO . Nueva línea ; finalizar el ciclo Read_Data ; finalizar Imprimir_Cuadrados ;
Python admite la ejecución condicional de código dependiendo de si se salió de un bucle antes (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 else : imprimir ( "El conjunto no contiene ningún número primo" )
La else
cláusula del ejemplo anterior está vinculada a la for
declaración y no a la if
declaración interna. for
Tanto los bucles como los de Python while
admiten dicha cláusula else, que se ejecuta sólo si no se ha producido una salida anticipada del bucle.
Algunos lenguajes admiten la ruptura de bucles anidados; En los círculos teóricos, esto se llama rupturas de varios niveles. 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 [9] y PHP, [10] o mediante saltos etiquetados (salir y continuar en una etiqueta determinada), como en Java y Perl. [11] Las alternativas a las rupturas multinivel incluyen rupturas individuales, junto con una variable de estado que se prueba para romper otro nivel; excepciones, que quedan atrapadas en el nivel al que se está desglosando; colocar los bucles anidados en una función y utilizar el retorno para efectuar la terminación de todo el bucle anidado; o usando una etiqueta y una declaración goto. C no incluye una ruptura multinivel y la alternativa habitual es utilizar un goto para implementar una ruptura etiquetada. [12] Python no tiene una interrupción o continuación multinivel; esto se propuso en PEP 3136 y se rechazó basándose en que la complejidad añadida no valía la pena el raro uso legítimo. [13]
La noción de rupturas de múltiples niveles tiene cierto interés en la informática teórica , porque da lugar a lo que hoy se llama jerarquía de Kosaraju . [14] En 1973, S. Rao Kosaraju refinó el teorema del programa estructurado demostrando que es posible evitar agregar variables adicionales en la programación estructurada, siempre que se permitan rupturas de bucles de varios niveles y profundidad arbitraria. [15] Además, Kosaraju demostró que existe una jerarquía estricta de programas: para cada número entero n , existe un programa que contiene una ruptura de varios niveles de profundidad n que no puede reescribirse como un programa con rupturas de múltiples niveles de profundidad menor que n . sin introducir variables añadidas. [14]
También se puede return
salir de una subrutina ejecutando las declaraciones en bucle, rompiendo tanto el bucle anidado como la subrutina. Hay otras estructuras de control propuestas para pausas 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 las pausas de varios niveles y las declaraciones de retorno. Watt señala que una clase de secuenciadores conocidos como secuenciadores de escape , definidos como "secuenciador que finaliza la ejecución de un comando o procedimiento que incluye texto", abarca tanto las interrupciones de los bucles (incluidas las interrupciones 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 interrupción implementado en los lenguajes contemporáneos generalmente no puede. [dieciséis]
Las variantes de bucle y las invariantes de bucle se utilizan para expresar la corrección de los bucles. [17]
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 seguir siendo verdadera después de cada iteración. Esto implica que cuando un bucle termina correctamente, se satisfacen tanto la condición de salida como el invariante del 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 soporte nativo para variantes e invariantes de bucle. En otros casos, el soporte es un complemento, como la especificación del lenguaje de modelado Java para declaraciones de bucle en Java .
Algunos dialectos Lisp proporcionan un sublenguaje extenso para describir bucles. Un ejemplo temprano se puede encontrar en Conversional Lisp de Interlisp . Common Lisp [18] 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 en contenedores STL y llamar a una función unaria para cada elemento. [19] La funcionalidad también se puede construir como macro en estos contenedores. [20]retry
, sin embargo, se usa en el manejo de excepciones , no en el control de bucle.GO TO
y.Muchos lenguajes de programación, especialmente aquellos que favorecen estilos de programación más dinámicos, ofrecen construcciones para flujo de control no local . Esto hace que el flujo de ejecución salte de un contexto determinado y se reanude en algún punto predeterminado. Condiciones , excepciones y continuaciones son tres tipos comunes de construcciones de control no local; También existen otros más exóticos, como generadores , corrutinas y la palabra clave async .
PL/I tiene unas 22 condiciones estándar (por ejemplo, ZERODIVIDE SUBSCRIPTRANGE ENDFILE) que pueden generarse y interceptarse mediante: acción de condición ON; Los programadores también pueden definir y utilizar sus propias condiciones con nombre.
Al igual que el if no estructurado , sólo 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:
Condición ON etiqueta GOTO
Los lenguajes modernos tienen una construcción estructurada especializada para el manejo de excepciones que no depende del uso de GOTO
interrupciones o retornos (multinivel). Por ejemplo, en C++ se puede escribir:
prueba { xxx1 // En algún lugar aquí xxx2 // usa: '''throw''' someValue; xxx3 } catch ( someClass & someId ) { // capturar valor de someClass actionForSomeClass } catch ( someType & anotherId ) { // capturar valor de someType actionForSomeType } catch (...) { // capturar cualquier cosa que no haya sido capturada actionForAnythingElse }
catch
Se puede utilizar cualquier número y variedad de cláusulas anteriores. Si no hay ninguna catch
coincidencia en particular throw
, el control se filtra a través de llamadas a subrutinas y/o bloques anidados hasta que catch
se encuentra una coincidencia o hasta que se alcanza el final del programa principal, momento en el cual el programa se detiene por la fuerza con un mensaje de error adecuado.
A través de la influencia de C++, catch
es la palabra clave reservada para declarar un controlador 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 controlador 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 controlador de excepciones para extraer automáticamente varios datos 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 si ( e = "No se puede dividir por cero" ) y luego muestre el cuadro de diálogo "No debe hacer eso" finalice el intento
El libro de texto de David Watt de 2004 también analiza el manejo de excepciones en el marco de secuenciadores (presentado 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 la cual] se ubica de manera más natural un controlador". 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, no se puede crear una rutina de manejo para esta situación anormal. ubicado en el código del sistema de bajo nivel. Watts señala además que la introducción de pruebas de indicadores de estado en la persona que llama, como implicaría la programación estructurada de salida única o incluso secuenciadores de retorno (salidas múltiples), da como resultado una situación en la que "el código de la aplicación tiende a saturarse con pruebas de indicadores de estado" y que "el programador podría, por olvido o por pereza, omitir probar un indicador de estado. De hecho, las situaciones anormales representadas por indicadores de estado se ignoran de forma predeterminada". Watt señala que, a diferencia de las pruebas de indicadores de estado, las excepciones tienen el comportamiento predeterminado opuesto , lo que hace que el programa finalice a menos que 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 excepción dedicado con la semántica analizada anteriormente. [21]
En Object Pascal, D, Java, C# y Python finally
se puede agregar una cláusula a la try
construcción. No importa cómo salga el control, se garantiza que try
el código dentro de la cláusula se ejecutará. finally
Esto es útil cuando se escribe código que debe renunciar a un recurso costoso (como un archivo abierto o una conexión de base de datos) cuando finaliza el procesamiento:
FileStream stm = nulo ; // Ejemplo de C# try { stm = new FileStream ( "logfile.txt" , FileMode . Create ); devolver ProcessStuff ( stm ); // puede generar una excepción } finalmente { if ( stm != null ) stm . Cerca (); }
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 libera, vinculando efectivamente la variable al flujo del archivo mientras se abstrae de los efectos secundarios de inicializar y liberar el archivo. La declaración de Python with
y el argumento de bloque de Ruby File.open
se utilizan con un efecto similar.
Todos los lenguajes mencionados anteriormente definen excepciones estándar y las circunstancias bajo las cuales se producen. Los usuarios pueden lanzar sus propias excepciones; C++ permite a los usuarios lanzar y capturar casi cualquier tipo, incluidos 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 asíncronas en un "estilo directo".
Los generadores , también conocidos como semicorutinas, permiten ceder el control temporalmente a un método de consumidor, normalmente utilizando una yield
palabra clave (descripción de rendimiento). Al igual que la palabra clave async, esto admite la programación en un "estilo directo".
Las corrutinas son funciones que pueden ceder 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 falso de Datamation [28] en 1973, R. Lawrence Clark sugirió que la declaración GOTO podría reemplazarse por la declaración COMEFROM y proporciona 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 declaraciones go to", [29] identifica dos situaciones que no estaban cubiertas por las estructuras de control enumeradas anteriormente y dio ejemplos de estructuras de control que podrían manejar estas situaciones. A pesar de su utilidad, estas construcciones aún no han llegado a los lenguajes de programación convencionales.
Dahl propuso lo siguiente en 1972: [30]
bucle bucle xxx1 leer(char); mientras prueba; mientras no esté en EndOfFile; 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 idiomas. Si se omite while , obtenemos un bucle infinito. La construcción aquí se puede considerar como un bucle do con el control while en el medio. Por 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 usando un modismo equivalente de bucle infinito con interrupción:
mientras (verdadero) { xxx1 si ( no prueba) se rompe xxx2}
Una posible variante 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 - repetir ) se puede representar usando un bucle infinito estándar ( loop - end loop ) que tiene una cláusula exit when en el medio (no debe confundirse con la declaración exitwhen en la siguiente sección ).
con Ada.Text_IO ; con Ada.Integer_Text_IO ;procedimiento Print_Squares es X : Entero ; comenzar Read_Data : bucle Ada . Entero_Texto_IO . Obtener ( X ); salir de Read_Data cuando X = 0 ; Ada . Texto IO . Poner ( X * X ); Ada . Texto IO . Nueva línea ; finalizar el ciclo Read_Data ; finalizar Imprimir_Cuadrados ;
Nombrar un bucle (como Read_Data en este ejemplo) es opcional pero permite dejar el bucle externo de varios bucles anidados.
Esta construcción fue propuesta por Zahn en 1974. [31] Aquí se presenta una versión modificada.
salir cuando EventA o EventB o EventC; xxx salidas EventoA: acciónA EventoB: acciónB EventoC: acciónC endexitar ;
exitwhen se usa para especificar los eventos que pueden ocurrir dentro de xxx , su ocurrencia se indica usando el nombre del evento como 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 determinar que se aplica alguna situación y la acción que se debe tomar para esa situación.
exitwhen es conceptualmente similar al manejo de excepciones , y en muchos lenguajes se utilizan excepciones o construcciones similares para este propósito.
El siguiente ejemplo simple implica buscar en una tabla bidimensional un elemento en particular.
salir cuando se encuentre o desaparezca; para I := 1 a N hacer para J := 1 a M hacer si tabla[I,J] = objetivo entonces encontrado; desaparecido; salidas encontrado: imprimir ("el elemento está en la tabla"); falta: imprimir ("el elemento no está en la tabla"); endexitar ;
Una forma de atacar una pieza de software es redirigir el flujo de ejecución de un programa. Para defenderse contra estos ataques se utiliza una variedad de técnicas de integridad del flujo de control , incluidos los valores controlados de pila , la protección contra desbordamiento del búfer , las pilas ocultas y la verificación del puntero vtable . [32] [33] [34]