Flujo de control
En informática, flujo de control (o flujo de control) es el orden en el que se ejecutan o evalúan sentencias, instrucciones o llamadas a funciones individuales de un programa imperativo. El énfasis en el flujo de control explícito distingue un lenguaje de programación imperativa de un lenguaje de programación declarativa.
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 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.
A su vez, un conjunto de declaraciones generalmente se estructura como un bloque, que además de agrupar, también define un ámbito léxico.
Las interrupciones y las señales son mecanismos de bajo nivel que pueden alterar el flujo de control de 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 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 generalmente funcionan alterando el contador del programa. Para algunas unidades centrales de procesamiento (CPU), las únicas instrucciones de flujo de control disponibles son las instrucciones de ramificación condicionales o incondicionales, también denominadas saltos.
Categorías
Los tipos de declaraciones de flujo de control compatibles con diferentes lenguajes varían, pero se pueden clasificar por su efecto:
- Continuación en una declaración diferente (sucursal o rama incondicional)
- Ejecución de una serie de declaraciones sólo si se cumple alguna condición (elección - es decir, rama condicional)
- Ejecución de un conjunto de declaraciones cero o más veces, hasta que se cumpla alguna condición (es decir, bucle - el mismo que rama condicional)
- Ejecución de un conjunto de declaraciones distantes, después de las cuales el flujo de control generalmente regresa (subroutinas, coroutines y continuas)
- Stopping the program, preventing any further execution (unconditional halt)
Primitivas
(feminine)Etiquetas
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 que se usa 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 usan 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 LET X = 320 PRINT X
En otros lenguajes como C y Ada, una etiqueta es un identificador, que suele aparecer al comienzo de una línea e inmediatamente seguido de dos puntos. Por ejemplo, en C:
Éxito: printf()"La operación tuvo éxito.n");
El lenguaje ALGOL 60 permitía tanto números enteros como identificadores como etiquetas (ambos vinculados por dos puntos a la siguiente declaración), pero pocas, si es que alguna, otras variantes de ALGOL 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 permiten etiquetas alfanuméricas.
Ir a
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 mando
Aunque la palabra clave puede estar en mayúsculas o minúsculas según el idioma, normalmente se escribe así:
Goto etiqueta
El efecto de una instrucción goto es hacer que la siguiente instrucción que se ejecute sea la instrucción que aparece en (o inmediatamente después) de la etiqueta indicada.
Muchos informáticos, especialmente Dijkstra, han considerado perjudiciales las declaraciones Goto.
Subrutinas
La terminología de las subrutinas varía; pueden ser conocidos 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 las subrutinas se usaban principalmente para reducir el tamaño del programa. Una pieza de código se escribió una vez y luego se usó muchas veces desde varios otros lugares en un programa.
Hoy en día, las subrutinas se usan más a menudo 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 están trabajando en un programa, las subrutinas son un tipo de modularidad que puede ayudar a dividir el trabajo.
Secuencia
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.
Flujo de control estructurado mínimo
En mayo de 1966, Böhm y Jacopini publicaron un artículo en Communications of the ACM que mostraba que cualquier programa con gotos podía transformarse en una forma sin goto que implicaba única opción (IF THEN ELSE) y bucles (WHILE condition DO xxx), posiblemente con código duplicado y/o la adición de variables booleanas (indicadores 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, las computadoras teóricamente necesitan solo una instrucción de máquina (restar un número de otro y ramificar 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 estar libres de goto. Otra investigación mostró que las estructuras de control con una entrada y una salida eran mucho más fáciles de entender que cualquier otra forma, 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 los lenguajes de programación no estrictos, y más recientemente, las transacciones de software componibles, han continuado 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 las instrucciones como break
y return
desde la mitad de los bucles son malas prácticas, ya que no lo son necesario 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 la programación introductoria en el mundo académico. La aplicación directa del teorema de Böhm-Jacopini puede resultar en la introducción de variables locales adicionales en el gráfico estructurado y también puede resultar en cierta duplicación de código. Pascal se ve afectado por estos dos 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 solo las estructuras de control proporcionadas por Pascal, solo el 20% de los sujetos dieron la solución correcta, mientras que ningún sujeto escribió un código incorrecto para este problema si se le permitía escribir un retorno del medio de un bucle.
Estructuras de control en la práctica
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. Luego, los lenguajes se dividen en cuanto a si las estructuras de control tienen o no una palabra clave final.
- No hay palabra clave final: ALGOL 60, C, C++, Haskell, Java, Pascal, Perl, PHP, PL/I, Python, Power Shell. Esos idiomas necesitan una forma de agrupar las declaraciones:
- ALGOL 60 y Pascal:
begin
...end
- C, C++, Java, Perl, PHP y PowerShell: curly brackets
{
...}
- PL/I:
DO
...END
- Python: utiliza el nivel de los indentes (véase la regla de fuera del lado)
- Haskell: se pueden utilizar soportes de nivel de entrada o curly, y pueden mezclarse libremente
- Lua: usos
do
...end
- ALGOL 60 y Pascal:
- Palabras clave final: Ada, ALGOL 68, Modula-2, Fortran 77, Mythryl, Visual Basic. Las formas de la palabra clave final varían:
- Ada: la palabra clave final es
end
+ espacio + palabra clave inicial, por ejemplo,if
...end if
,loop
...end loop
- ALGOL 68, Mythryl: palabra clave inicial deletreada hacia atrás, por ejemplo,
if
...fi
,case
...esac
- Fortran 77: la palabra clave final es
END
+ palabra clave inicial, por ejemplo,IF
...ENDIF
,DO
...ENDDO
- Modula-2: misma palabra clave final
END
para todo - Visual Basic: cada estructura de control tiene su propia palabra clave.
If
...End If
;For
...Next
;Do
...Loop
;While
...Wend
- Ada: la palabra clave final es
Elección
Declaraciones if-then-(else)
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 encontrado en idiomas no estructurados, imitando una instrucción típica de código de máquina, saltaría a (GOTO) una etiqueta o número de línea cuando se cumplió la condición.IF..THEN..(ENDIF)
. En lugar de ser restringido a un salto, cualquier simple declaración, o bloque anidado, podría seguir la palabra clave de THEN. Esta forma estructurada.IF..THEN..ELSE..(ENDIF)
. Como arriba, pero con una segunda acción para ser realizada si la condición es falsa. Esta es una de las formas más comunes, con muchas variaciones. Algunos requieren un terminalENDIF
, otros no. C y lenguajes relacionados no requieren una palabra clave terminal, o un 'entonces', pero requieren paréntesis alrededor de la condición.- Las declaraciones condicionales pueden ser y a menudo se anidan dentro de otras declaraciones condicionales. Algunos idiomas permiten
ELSE
yIF
para combinarse enELSEIF
, evitando la necesidad de tener una serie deENDIF
u otras declaraciones finales al final de una declaración compuesta.
Pascal: | Ada: | C: | Shell script: | Python: | Lisp: |
---|---|---|---|---|---|
si a ■ 0 entonces Writeln()"Sí.")más Writeln()"no"); | si a ■ 0 entonces Put_Line()Sí.);más Put_Line()"no");final si; | si ()a ■ 0) {} printf()Sí.);}más {} printf()"no");} | si [ $a -gt 0 ]; entonces eco Sí.más eco "no"fi | si a ■ 0: impresión()Sí.)más: impresión()"no") | ()princ ()si ()plusp a) Sí. "no") |
Las variaciones menos comunes incluyen:
- Algunos idiomas, como Fortran, tienen un tres vías o aritmética si, probar si un valor numérico es positivo, negativo o cero.
- Algunos idiomas tienen una forma funcional de un
if
declaración, por ejemplo Lispcond
. - Algunos idiomas tienen una forma de operador
if
declaración, como el operador ternario de C. - Perl complementa un estilo C
if
conwhen
yunless
. - Usos pequeños
ifTrue
yifFalse
mensajes para implementar condicionales, en lugar de cualquier construcción de lenguaje fundamental.
Declaraciones de caso y cambio
Sentencias de cambio (o sentencias de caso, o ramas multidireccionales) comparan un valor dado con constantes especificadas y toman medidas de acuerdo con la primera constante que coincida. Por lo general, existe una disposición para que se realice 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 lenguajes dinámicos, los casos pueden no estar limitados 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 una coincidencia global. cualquier cadena. lógica de caso
también se puede implementar en forma funcional, como en la instrucción decode
de SQL.
Pascal: | Ada: | C: | Shell script: | Lisp: |
---|---|---|---|---|
Caso algunos Char de 'a': acción OnA; 'x ': acción OnX; 'y ','z ':acción OnYandZ; más actionOnNoMatch;final; | Caso algunos Char es cuando 'a' = acción OnA; cuando 'x' = acción OnX; cuando 'Sí.' Silencio 'z' = acción OnYandZ; cuando otros = actionOnNoMatch;final; | interruptor ()algunos Char) {} Caso 'a': acción OnA; descanso; Caso 'x ': acción OnX; descanso; Caso 'y ': Caso 'z ': acción OnYandZ; descanso; por defecto: actionOnNoMatch;} | Caso $some Char dentro a) acción OnA ;x) acción OnX ; [Yz]) acción OnYandZ ;*) actionOnNoMatch ;esac | ()Caso algunos-char ()#) adopción de medidas al respecto) ()#x) action-on-x) ()#y #z) action-on-y-z) ()más action-on-no-match) |
Bucles
Un bucle es una secuencia de sentencias que se especifica una vez pero que puede llevarse a cabo varias veces seguidas. El código "dentro" el 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 los elementos de una colección, o hasta que alguna condición se cumple, o indefinidamente.
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.
Bucles controlados por conteo
La mayoría de los lenguajes de programación tienen construcciones para repetir un ciclo una cierta cantidad de veces. En la mayoría de los casos, el conteo puede ir hacia abajo en lugar de hacia arriba y se pueden usar tamaños de paso distintos de 1.
FOR I = 1 TO N TENIDO para I:= 1 a N do comenzarxxx SIGUIENTE I final; -------- DO I = 1,N ← para (I=1; I won=N; ++I) { xxx END DO FORMULADA
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, solo los números enteros se pueden usar de manera confiable en un bucle controlado por conteo. Los números de punto flotante se representan de forma imprecisa debido a limitaciones de hardware, por lo que un bucle como
para X:= 0.1 paso 0.1 a 1.0 do
puede repetirse 9 o 10 veces, según los errores de redondeo y/o el hardware y/o la versión del compilador. Además, si el incremento de X ocurre por adición repetida, los errores de redondeo acumulados pueden significar que el valor de X en cada iteración puede diferir bastante significativamente de la secuencia esperada 0.1, 0.2, 0.3,..., 1.0.
Bucles controlados por condiciones
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; otros lo prueban al final. Si la prueba es al comienzo, el cuerpo puede omitirse por completo; si es al final, el cuerpo siempre se ejecuta al menos una vez.
DO WHILE (test) ← repetición xxx LOOP FORMULADA hasta prueba; ---------- mientras (test) { ← doxxx Silencio mientras (prueba);
Una ruptura de control es un método de detección de cambio de valor que se utiliza dentro de los bucles ordinarios para activar el procesamiento de grupos de valores. Los valores se monitorean dentro del ciclo y un cambio desvía el flujo del programa al manejo del evento de grupo asociado con ellos.
DO UNTIL (End-of-File) IF new-zipcode display_tally (código-cip corriente, zipcount) actual código postal = nuevo código postal zipcount = 0 ENDIF zipcount++ LOOP
Bucles controlados por colección
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 un bucle implícito a través de todos los elementos de una matriz, o todos los miembros de un conjunto o colección.
algunosColección do: [: Cada unoElement tenciónxx].
para Tema dentro Colección do comenzar xxxx final; en adelante (Tema; miColección) { xxx} en adelante algunosArray { xxx } en adelante ($someArray como $k = prenda $v) { xxx } Recopilación realizadaString confía coll; para (String s: coll) {} en adelante ()cuerda s dentro myStringCollection) { xxx} algunosColección ← Para cada objeto { $_ }
para todos (index = first:last:step...)
Scala tiene expresiones for, que generalizan bucles controlados por colecciones y también admiten otros usos, como la programación asincrónica. Haskell tiene expresiones do y comprensiones, que en conjunto brindan una función similar a las expresiones for en Scala.
Iteración general
Las construcciones de iteración general, como la declaración for
de C y el formulario do
de Common Lisp, se pueden usar para expresar cualquiera de los tipos de bucles anteriores., y otros, como recorrer varias colecciones en paralelo. Cuando se puede usar una construcción de bucle más específica, generalmente se prefiere a la construcción de iteración general, ya que a menudo aclara el propósito de la expresión.
Bucles infinitos
Los bucles infinitos se utilizan para asegurar que un segmento de programa se repite 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, deteniéndose solo cuando un operador finaliza el proceso.
Los bucles infinitos se pueden implementar utilizando otras construcciones de flujo de control. Más comúnmente, en la programación no estructurada, esto es un salto hacia atrás (goto), mientras que en la programación estructurada, este es un bucle indefinido (bucle while) configurado para nunca terminar, ya sea omitiendo la condición o estableciéndola explícitamente como verdadera, como while (verdadero).... Algunos lenguajes tienen construcciones especiales para bucles infinitos, típicamente omitiendo la condición de un bucle indefinido. Los ejemplos incluyen Ada (
loop... end loop
), 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.
Continuación con la siguiente iteración
A veces, dentro del cuerpo de un ciclo, existe el deseo de omitir el resto del cuerpo del ciclo y continuar con la siguiente iteración del ciclo. Algunos idiomas proporcionan una declaración como continue
(la mayoría de los idiomas), skip
o next
(Perl y Ruby), que harán esto. El efecto es terminar prematuramente el cuerpo del bucle más interno y luego reanudar normalmente con la siguiente iteración. Si la iteración es la última del bucle, el efecto es terminar todo el bucle antes de tiempo.
Rehacer iteración actual
Algunos lenguajes, como Perl y Ruby, tienen una instrucción redo
que reinicia la iteración actual desde el principio.
Reiniciar bucle
Ruby tiene una instrucción retry
que reinicia todo el ciclo desde la iteración inicial.
Salida anticipada de los bucles
Cuando se usa un ciclo 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 para terminar el bucle actual inmediatamente y transferir el control a la declaración inmediatamente después de ese bucle. Otro término para bucles de salida anticipada es bucle y medio.
El siguiente ejemplo se realiza en Ada, que admite tanto salida anticipada de bucles como bucles con prueba en el medio. Ambas funciones son muy similares y la comparación de ambos fragmentos de código mostrará la diferencia: salida anticipada debe combinarse con una declaración if mientras que una condición en el medio es una construcción independiente.
con Ada.Text IO;con Ada.Integer Texto IO;procedimiento Print_Squares es X : Integer;comenzar Read_Data : bucle Ada.Integer Texto IO.Vamos.()X); Salida Read_Data cuando X = 0; Ada.Texto IO.Put ()X * X); Ada.Texto IO.New_Line; final bucle Read_Data;final Print_Squares;
Python admite la ejecución condicional de código dependiendo de si un ciclo se salió antes (con una instrucción break
) o no mediante el uso de una cláusula else con el ciclo. Por ejemplo,
para n dentro set_of_numbers: si Es alto.()n): impresión()"Set contiene un número primo") descansomás: impresión()"Set no contenía números primos")
La cláusula else
del ejemplo anterior está vinculada a la declaración for
, y no a la declaración interna if
. Los bucles for
y while
de Python admiten una cláusula else de este tipo, que se ejecuta solo si no se ha producido una salida anticipada del bucle.
Algunos idiomas admiten la ruptura de bucles anidados; en los círculos teóricos, estos se denominan descansos de varios niveles. Un ejemplo de uso común es buscar en una tabla multidimensional. Esto se puede hacer a través de rupturas multinivel (salir de N niveles), como en bash y PHP, o mediante rupturas etiquetadas (salir y continuar en una etiqueta dada), como en Java y Perl. Las alternativas a las rupturas multinivel incluyen rupturas individuales, junto con una variable de estado que se prueba para romper otro nivel; excepciones, que se capturan en el nivel al que se desglosa; colocar los bucles anidados en una función y usar return para efectuar la terminación de todo el bucle anidado; o usando una etiqueta y una instrucción goto. C no incluye una ruptura multinivel y la alternativa habitual es usar un goto para implementar una ruptura etiquetada. Python no tiene una interrupción o continuación multinivel: esto se propuso en PEP 3136 y se rechazó sobre la base de que la complejidad adicional no valía la pena para el raro uso legítimo.
La noción de rupturas multinivel tiene cierto interés en la informática teórica, porque da lugar a lo que hoy se llama la jerarquía de Kosaraju. 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 de bucles de varios niveles y profundidad arbitraria. Además, Kosaraju demostró que existe una estricta jerarquía de programas: para cada número entero n, existe un programa que contiene una ruptura de varios niveles de profundidad n que no se puede reescribir como un programa con roturas de varios niveles de profundidad inferior a n sin introducir variables añadidas.
También se puede return
fuera de una subrutina ejecutando las declaraciones en bucle, saliendo tanto del bucle anidado como de la subrutina. Hay otras estructuras de control propuestas para pausas múltiples, pero estas generalmente se implementan como excepciones.
En su libro de texto de 2004, David Watt usa la noción de secuenciador de Tennent para explicar la similitud entre las rupturas de varios niveles y las declaraciones de retorno. Watt señala que una clase de secuenciadores conocida como secuenciadores de escape, definida como "secuenciador que finaliza la ejecución de un comando o procedimiento textualmente adjunto", abarca tanto las interrupciones de los bucles (incluidos los de varios niveles descansos) y sentencias de retorno. Sin embargo, tal como se implementan comúnmente, los secuenciadores de retorno también pueden tener un valor (de retorno), mientras que el secuenciador de interrupción, tal como se implementa en los lenguajes contemporáneos, generalmente no puede hacerlo.
Variantes e invariantes de bucle
Las variantes de bucle y los invariantes de bucle se utilizan para expresar la corrección de los bucles.
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 terminarán.
Un ciclo invariante es una afirmación que debe ser verdadera antes de la primera iteración del ciclo y permanecer verdadera después de cada iteración. Esto implica que cuando un ciclo termina correctamente, tanto la condición de salida como la invariante del ciclo se cumplen. 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 de Java para declaraciones de bucle en Java.
Sublenguaje de bucle
Algunos dialectos de Lisp proporcionan un extenso sublenguaje para describir bucles. Un ejemplo temprano se puede encontrar en Conversional Lisp de Interlisp. Common Lisp proporciona una macro Loop que implementa dicho sublenguaje.
Tabla de referencias cruzadas del sistema de bucle
Lenguaje de programación | condicional | bucle | salida temprana | continuidad | redo | retry | instalaciones de corrección | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
comenzar | Medio | final | Cuenta | colección | general | infinito | variante | invariable | |||||
Ada | Sí. | Sí. | Sí. | Sí. | arrays | No | Sí. | profundo anidado | No | ||||
APL | Sí. | No | Sí. | Sí. | Sí. | Sí. | Sí. | profundo anidado | Sí. | No | No | ||
C | Sí. | No | Sí. | No | No | Sí. | No | profundo anidado | profundo anidado | No | |||
C++ | Sí. | No | Sí. | No | Sí. | Sí. | No | profundo anidado | profundo anidado | No | |||
C# | Sí. | No | Sí. | No | Sí. | Sí. | No | profundo anidado | profundo anidado | ||||
COBOL | Sí. | No | Sí. | Sí. | No | Sí. | No | profundo anidado | profundo anidado | No | |||
Lisp común | Sí. | Sí. | Sí. | Sí. | solo construida | Sí. | Sí. | profundo anidado | No | ||||
D | Sí. | No | Sí. | Sí. | Sí. | Sí. | Sí. | profundo anidado | profundo anidado | No | |||
Eiffel | Sí. | No | No | Sí. | Sí. | Sí. | No | nivel | No | No | No | entero sólo | Sí. |
F# | Sí. | No | No | Sí. | Sí. | No | No | No | No | No | |||
FORTRAN 77 | Sí. | No | No | Sí. | No | No | No | nivel | Sí. | ||||
Fortran 90 | Sí. | No | No | Sí. | No | No | Sí. | profundo anidado | Sí. | ||||
Fortran 95 y más tarde | Sí. | No | No | Sí. | arrays | No | Sí. | profundo anidado | Sí. | ||||
Haskell | No | No | No | No | Sí. | No | Sí. | No | No | No | |||
Java | Sí. | No | Sí. | No | Sí. | Sí. | No | profundo anidado | profundo anidado | No | no nativo | no nativo | |
JavaScript | Sí. | No | Sí. | No | Sí. | Sí. | No | profundo anidado | profundo anidado | No | |||
Natural | Sí. | Sí. | Sí. | Sí. | No | Sí. | Sí. | Sí. | Sí. | Sí. | No | ||
OCaml | Sí. | No | No | Sí. | arrays,lists | No | No | No | No | No | |||
PHP | Sí. | No | Sí. | No | Sí. | Sí. | No | profundo anidado | profundo anidado | No | |||
Perl | Sí. | No | Sí. | No | Sí. | Sí. | No | profundo anidado | profundo anidado | Sí. | |||
Python | Sí. | No | No | No | Sí. | No | No | profundo anidado | profundo anidado | No | |||
REBOL | No | Sí. | Sí. | Sí. | Sí. | No | Sí. | nivel | No | No | |||
Ruby | Sí. | No | Sí. | Sí. | Sí. | No | Sí. | profundo anidado | profundo anidado | Sí. | Sí. | ||
Standard ML | Sí. | No | No | No | arrays,lists | No | No | No | No | No | |||
Visual Basic.NET | Sí. | No | Sí. | Sí. | Sí. | No | Sí. | un nivel por tipo de bucle | un nivel por tipo de bucle | ||||
PowerShell | Sí. | No | Sí. | No | Sí. | Sí. | No | ? | Sí. |
- a
while (true)
no cuenta como un bucle infinito para este propósito, porque no es una estructura de lenguaje dedicada. - a b c d e f g h C's
for (init; test; increment)
el bucle es una construcción de bucle general, no específicamente una conteo, aunque a menudo se utiliza para eso. - a b c Las pausas profundas se pueden realizar en APL, C, C++ y C# a través del uso de etiquetas y gotos.
- a La iteración sobre objetos se agregó en PHP 5.
- a b c Un bucle contable puede ser simulado por la iteración sobre una lista de aumento o generador, por ejemplo, Python's
range()
. - a b c d e Las pausas profundas pueden realizarse mediante el uso de la manipulación de excepciones.
- a No hay construcción especial, ya que
while
función se puede utilizar para esto. - a No hay una construcción especial, pero los usuarios pueden definir funciones de bucle general.
- a El estándar C++11 introdujo el rango para. En el STL, hay un
std::for_each
Función de plantilla que puede iterar en contenedores STL y llamar una función no deseada para cada elemento. La funcionalidad también se puede construir como macro en estos contenedores. - a El bucle controlado por el conteo se efectúa por la iteración a través de un intervalo entero; salida temprana incluyendo una condición adicional para la salida.
- a Eiffel apoya una palabra reservada
retry
, sin embargo se utiliza en el manejo de excepción, no control de bucle. - a Requiere Java Modeling Language (JML) lenguaje de especificación de la interfaz conductual.
- a Requiere que las variantes de bucle sean enteros; no se admiten variantes transfinitas. [1]
- a D apoya colecciones infinitas, y la capacidad de iterar sobre esas colecciones. Esto no requiere ninguna construcción especial.
- a Se pueden lograr interrupciones profundas utilizando
GO TO
y procedimientos. - a Common Lisp preda el concepto de tipo de colección genérico.
Flujo de control no local estructurado
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. Estos hacen que el flujo de ejecución salte de un contexto dado y se reanude en algún punto predeclarado. Condiciones, excepciones y continuaciones son tres tipos comunes de construcciones de control no local; también existen otros más exóticos, como generadores, rutinas y la palabra clave async.
Condiciones
PL/I tiene unas 22 condiciones estándar (p. ej., ZERODIVIDE SUBSCRIPTRANGE ENDFILE) que pueden generarse y que pueden interceptarse mediante: ON condición acción; Los programadores también pueden definir y usar sus propias condiciones con nombre.
Al igual que el si 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 tuvieron una sobrecarga sustancial tanto en el espacio como en el tiempo (especialmente SUBSCRIPTRANGE), por lo que muchos programadores intentaron evitar el uso de condiciones.
Ejemplos de sintaxis comunes:
ON condición GOTO etiqueta
Excepciones
Los lenguajes modernos tienen una construcción estructurada especializada para el manejo de excepciones que no se basa en el uso de GOTO
o saltos o retornos (multinivel). Por ejemplo, en C++ se puede escribir:
Prueba {} xxx1 // En algún lugar aquí xxx2 // use: ''throw''' algunos Valor; xxx3} captura ()algunos Clase" algunos Id) {} // valor de captura de algunos Clase actionForSomeClass } captura ()algunos Tipo" otro Id) {} // valor de captura de algunos Tipo actionForSomeType} captura (...) {} // atrapar cualquier cosa que ya no haya atrapado acción Para nada Else}
Cualquier cantidad y variedad de cláusulas catch
pueden usarse arriba. Si no hay catch
que coincida con un throw
en particular, el control se filtra a través de llamadas de subrutinas y/o bloques anidados hasta que se encuentra un catch
coincidente o hasta que se alcanza el final del programa principal, momento en el que el programa se detiene a la fuerza con un mensaje de error adecuado.
Gracias a 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 varias piezas de información cuando ocurre la excepción. Este enfoque se ejemplifica a continuación mediante la construcción on error
de AppleScript:
Prueba set myNumber a myNumber / 0on error e Número n desde f a t parcial resultado pr si () e = "No se puede dividir por cero" ) entonces Cuadro de diálogo "No debes hacer eso"final Prueba
El libro de texto de 2004 de David Watt también analiza el manejo de excepciones en el marco de secuenciadores (presentado en este artículo en la sección sobre salidas anticipadas 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] se necesita un controlador". más naturalmente ubicado 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 establecer 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 lo implicaría la programación estructurada de salida única o incluso los secuenciadores de retorno (salida múltiple), da como resultado una situación en la que "el código de la aplicación tiende a saturarse de pruebas de estado". banderas" y que "el programador podría omitir por olvido o pereza probar un indicador de estado. De hecho, las situaciones anómalas representadas por indicadores de estado se ignoran de forma predeterminada." 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 programador trate explícitamente la excepción de alguna manera, posiblemente agregando código explícito para ignorarlo. Basado en estos argumentos, Watt concluye que los secuenciadores de salto o los secuenciadores de escape no son tan adecuados como un secuenciador de excepción dedicado con la semántica discutida anteriormente.
En Object Pascal, D, Java, C# y Python, se puede agregar una cláusula finally
a la construcción try
. No importa cómo el control deje el try
, se garantiza que el código dentro de la cláusula finally
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 = nulo; // Ejemplo C#Prueba{} stm = nuevo FileStream()"logfile.txt", FileMode.Crear); retorno ProcesoStuff()stm); // puede hacer una excepción} finalmente{} si ()stm ! nulo) stm.Cerca();}
Dado que este patrón es bastante común, C# tiene una sintaxis especial:
utilizando ()Var stm = nuevo FileStream()"logfile.txt", FileMode.Crear){} retorno ProcesoStuff()stm); // puede hacer una excepción}
Al salir del bloque using
, el compilador garantiza que el objeto stm
se libera, vinculando efectivamente la variable a la secuencia del archivo mientras se abstrae de los efectos secundarios de la inicialización y liberar el archivo. La instrucción with
de Python y el argumento de bloque de Ruby para File.open
se utilizan con un efecto similar.
Todos los lenguajes mencionados anteriormente definen excepciones estándar y las circunstancias bajo las cuales se lanzan.
Los usuarios pueden generar sus propias excepciones; de hecho, C++ permite a los usuarios lanzar y atrapar casi cualquier tipo, incluidos tipos básicos como int
, mientras que otros lenguajes como Java no son tan permisivos.
Continuaciones
Asíncrono
C# 5.0 introdujo la palabra clave async para admitir E/S asíncrona en un "estilo directo".
Generadores
Los generadores, también conocidos como semicorutinas, permiten ceder el control a un método de consumidor temporalmente, generalmente usando un rendimiento
palabra clave (descripción del rendimiento). Al igual que la palabra clave async, esto admite la programación en un "estilo directo".
Corrutinas
Las corrutinas son funciones que pueden ceder el control entre sí: una forma de multitarea cooperativa sin subprocesos.
Las rutinas se pueden implementar como una biblioteca si el lenguaje de programación proporciona continuaciones o generadores, por lo que la distinción entre rutinas y generadores en la práctica es un detalle técnico.
Referencia cruzada de flujo de control no local
Lenguaje de programación | condiciones | excepciones | generadores/coroutines | async |
---|---|---|---|---|
Ada | No | Sí. | ? | ? |
C | No | No | No | No |
C++ | No | Sí. | Sí. | ? |
C# | No | Sí. | Sí. | Sí. |
COBOL | Sí. | Sí. | No | No |
Lisp común | Sí. | No | ? | ? |
D | No | Sí. | Sí. | ? |
Eiffel | No | Sí. | ? | ? |
Erlang | No | Sí. | Sí. | ? |
F# | No | Sí. | Sí. | Sí. |
Vamos. | No | Sí. | Sí. | ? |
Haskell | No | Sí. | Sí. | No |
Java | No | Sí. | No | No |
JavaScript | ? | Sí. | Sí. | Sí. |
Objetivo-C | No | Sí. | No | ? |
PHP | No | Sí. | Sí. | ? |
PL/I | Sí. | No | No | No |
Python | No | Sí. | Sí. | Sí. |
REBOL | Sí. | Sí. | No | ? |
Ruby | No | Sí. | Sí. | via extension |
Rust | No | Sí. | experimental experimental | Sí. |
Scala | No | Sí. | via extensión experimental | via extensión experimental |
Tcl | via traces | Sí. | Sí. | via evento bucle |
Visual Basic.NET | Sí. | Sí. | No | ? |
PowerShell | No | Sí. | No | ? |
Estructuras de control propuestas
En un artículo falso de Datamation de 1973, R. Lawrence Clark sugirió que la declaración GOTO podría ser reemplazada 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 sentencias go to", identifica dos situaciones que no estaban cubiertas por las estructuras de control enumeradas anteriormente y proporciona 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 principales lenguajes de programación.
Bucle con prueba en el medio
Dahl propuso lo siguiente en 1972:
bucle buclexxx1 read(char); mientras prueba; mientras no en EndOfFile; xxx2 escriba(char); repetición; repetición;
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 lo tanto, esta construcción única 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 idioma equivalente de bucle infinito con interrupción:
mientras (verdad) { xxx1 si ()no prueba) descansoxxx2 }
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 (bucle-mientras-repetir) se puede representar usando un bucle infinito estándar ( loop - end loop) que tiene una cláusula exit when en el medio (que no debe confundirse con la instrucción exitwhen en el sección siguiente).
con Ada.Text_IO;con Ada.Integer_Text_IO;procedimiento Print_Squares es X : Integer;comenzar Read_Data : bucle Ada.Integer_Text_IO.Vamos.()X); Salida Read_Data cuando X = 0; Ada.Texto IO.Put ()X * X); Ada.Texto IO.New_Line; final bucle Read_Data;final Print_Squares;
Nombrar un bucle (como Read_Data en este ejemplo) es opcional pero permite dejar el bucle exterior de varios bucles anidados.
Múltiples salidas anticipadas/salidas de bucles anidados
Esto fue propuesto por Zahn en 1974. Aquí se presenta una versión modificada.
salida EventA o EventB o EventC; xxxx salidasEventA: acciónA EventB: actionB EventC: actionC endexit;
exitwhen se usa para especificar los eventos que pueden ocurrir dentro de xxx, su ocurrencia se indica usando 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 alguna situación y la acción a tomar para esa situación.
exitwhen es conceptualmente similar al manejo de excepciones, y las excepciones o construcciones similares se usan para este propósito en muchos lenguajes.
El siguiente ejemplo simple involucra la búsqueda de un elemento en particular en una tabla bidimensional.
salida encontrado o desaparecidos; para I:= 1 a N do para J:= 1 a M do si [I,J] = objetivo entonces encontrado; desaparecidos; salidasencontrado: imprimir ("el punto está en la tabla"); falta: impresión ("el punto no está en la tabla"); endexit;
Seguridad
Una forma de atacar una pieza de software es redirigir el flujo de ejecución de un programa. Se utiliza una variedad de técnicas de integridad de flujo de control, que incluyen canarios de pila, protección contra desbordamiento de búfer, pilas de sombra y verificación de puntero de vtable, para defenderse contra estos ataques.
Contenido relacionado
Modelo de cascada
Microsistemas de sol
Sombrero rojo linux