Flujo de control

Orden de ejecución de los comandos de la computadora

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 manera similar a una subrutina , pero generalmente ocurren como respuesta a algún estímulo o evento externo (que puede ocurrir de manera 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 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 bifurcación condicional o incondicional , también denominadas saltos.

Categorías

Diagrama de estados de un proceso de búsqueda de mapeo de masas de iones peptídicos.

Los tipos de declaraciones de flujo de control admitidos por diferentes lenguajes varían, pero se pueden clasificar según su efecto:

  • Continuación en una instrucción diferente ( ramificación o salto incondicional)
  • Ejecutar un conjunto de instrucciones solo si se cumple alguna condición (elección, es decir, rama condicional )
  • Ejecutar un conjunto de instrucciones cero o más veces, hasta que se cumpla alguna condición (es decir, bucle, lo mismo que rama condicional )
  • Ejecutar un conjunto de instrucciones distantes, después de lo cual el flujo de control generalmente regresa ( subrutinas , corrutinas y continuaciones )
  • Detener el programa, impidiendo cualquier ejecución posterior (detención incondicional)

Primitivos

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 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 que va seguido inmediatamente 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.

Ir a

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 .

Subrutinas

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 en comparación con los estándares actuales, por lo que las subrutinas se usaban 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 lugares diferentes 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.

Secuencia

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 básico para los programas junto con la iteración, la recursión y la elección.

Flujo de control estructurado mínimo

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 libre de 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 breaky returndesde 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]

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. [ aclaración necesaria ] Los lenguajes luego se dividen en función de si las estructuras de control tienen o no una palabra clave final.

  • No hay palabras clave finales: ALGOL 60 , C , C++ , Go , Haskell , Java , Pascal , Perl , PHP , PL/I , Python , PowerShell . Estos lenguajes necesitan alguna forma de agrupar las sentencias:
    • ALGOL 60 y Pascal: begin...end
    • C, C++, Go, Java, Perl, PHP y PowerShell: llaves { ...}
    • PL/Yo: DO...END
    • Python: utiliza el nivel de sangría (ver regla de fuera de juego )
    • Haskell: se pueden utilizar tanto el nivel de sangría como las llaves, y se pueden combinar libremente
    • Lua: utiliza do...end
  • Palabra clave final: Ada , APL , 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
    • APL: la palabra clave final es :Endopcionalmente + palabra clave inicial, p. ej., :If... :Endo :If... :EndIf, Select... :Endo :Select... :EndSelect, sin embargo, si se agrega una condición final, la palabra clave final se convierte en:Until
    • ALGOL 68, Mitrilo: palabra clave inicial escrita al revé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 ENDpara todo
    • Visual Basic: cada estructura de control tiene su propia palabra clave. If... End If; For... Next; Do... Loop; While...Wend

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..GOTOUna 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.
  • Las sentencias condicionales pueden estar anidadas dentro de otras sentencias condicionales y, a menudo, lo están. Algunos lenguajes permiten que ELSEy IFse combinen en ELSEIF, lo que evita la necesidad de tener una serie de ENDIFu otras sentencias finales al final de una sentencia compuesta.
Pascual :Ada :
si a > 0 entonces writeln ( " " ) de lo contrario writeln ( " no " ) ;      
si  a  >  0  entonces  Put_Line ( "sí" ); de lo contrario  Put_Line ( "no" ); fin  si ;
C :Script de shell :
si ( a > 0 ) { pone ( "sí" ); } de lo contrario { pone ( "no" ); }        
si [ $a -gt 0 ] ; entonces echo "sí" de lo contrario echo "no" fi          
Pitón :Ceceo :
si  a  >  0 :  imprimir ( "sí" ) de lo contrario :  imprimir ( "no" )
( princ ( si ( plusp a ) "si" "no" ))     

Las variaciones menos comunes incluyen:

  • Algunos lenguajes, como Fortran , tienen un if de tres vías o aritmético , que prueba si un valor numérico es positivo, negativo o cero.
  • Algunos lenguajes tienen una forma funcional de una ifdeclaración, por ejemplo, Lisp cond .
  • Algunos lenguajes tienen una forma de operador de una ifdeclaración, como el operador ternario de C.
  • Perl complementa un estilo C ifcon wheny unless.
  • Smalltalk utiliza ifTruemensajes ifFalsepara implementar condicionales, en lugar de cualquier construcción fundamental del lenguaje.

Sentencias de caso y de cambio

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 .

Pascual :Ada :
caso someChar de 'a' : acciónEnA ; 'x' : acciónEnX ; 'y' , 'z' : acciónEnYyZ ; de lo contrario acciónEnNoMatch ; fin ;         
caso  someChar  es  cuando  ' a '  =>  actionOnA ;  cuando  ' x '  =>  actionOnX ;  cuando  ' y '  |  ' z '  =>  actionOnYandZ ;  cuando  otros  =>  actionOnNoMatch ; fin ;
C :Script de shell :
switch ( someChar ) { caso 'a' : acciónEnA ; romper ; caso 'x' : acciónEnX ; romper ; caso 'y' : caso 'z' : acciónEnYyZ ; romper ; predeterminado : acciónEnNoMatch ; }                  
caso $someChar en a ) acciónEnA ;; x ) acciónEnX ;; [ yz ]) acciónEnYandZ ;; * ) acciónEnNoMatch ;; esac               
Ceceo :Fortran :
( caso algún-carácter (( #\a ) acción-sobre-a ) (( #\x ) acción-sobre-x ) (( #\y #\z ) acción-sobre-y-y-z ) ( de lo contrario acción-sobre-no-coincidencia ))          
seleccionar caso ( someChar ) caso ( 'a' ) acciónEnA caso ( 'x' ) acciónEnX caso ( 'y' , 'z' ) acciónEnYyZ caso predeterminado acciónEnNoCoincidencia fin seleccionar            

Bucles

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.

Bucles controlados por conteo

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.

PARA I = 1 A N xxxSIGUIENTE I
para I : = 1 a N empezamos  xxxfin ;
HAGO I = 1,N xxxFIN HACER
para ( I=1; I<=N; ++I ) { xxx}

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.

Bucles controlados por condición

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.

HACER MIENTRAS (prueba) xxxBUCLE
repetir xxxhasta la prueba;
mientras (prueba) { xxx}
hacer xxxmientras (prueba);

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

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 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 ($someArray 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.

Iteración general

Las construcciones de iteración generales, como forla declaración de C y la forma de Common Lisp,do 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.

Bucles infinitos

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.

Continuación con la siguiente iteración

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.

Rehacer la iteración actual

Algunos lenguajes, como Perl [9] y Ruby, [10] tienen una redodeclaración que reinicia la iteración actual desde el principio.

Reiniciar bucle

Ruby tiene una retrydeclaración que reinicia todo el bucle desde la iteración inicial. [11]

Salida anticipada de los bucles

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 breakdeclaració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 elsecláusula del ejemplo anterior está vinculada a la fordeclaración, no a la ifdeclaración interna. Tanto Python forcomo whilelos 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 agregada 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 returnsalir 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]

Variantes e invariantes de bucles

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 .

Sublenguaje de bucle

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.

Tabla de referencias cruzadas del sistema de bucle

Lenguaje de programacióncondicionalbuclesalida anticipadacontinuación del buclerehacerreverinstalaciones de corrección
comenzarmediofincontarrecopilacióngeneralinfinito [1]varianteinvariante
AdamatricesNoanidado profundamenteNo
APLNoanidado profundamente [3]NoNo
doNoNo [2]NoNoanidado profundamente [3]anidado profundamente [3]No
C++NoNo [2][9]Noanidado profundamente [3]anidado profundamente [3]No
DO#NoNo [2]Noanidado profundamente [3]anidado profundamente [3]
COBOLNoNoNoanidado profundamente [15]anidado profundamente [14]No
Ceceo comúnsolo incorporado [16]anidado profundamenteNo
DNo[14]anidado profundamenteanidado profundamenteNo
Torre EiffelNoNo[10]Noun nivel [10]NoNoNo [11]solo entero [13]
F#NoNoNoNoNo [6]NoNo
FORTRAN 77NoNoNoNoNoun nivel
Fortran 90NoNoNoNoanidado profundamente
Fortran 95 y posterioresNoNomatricesNoanidado profundamente
IrNoNosolo incorporadoanidado profundamenteanidado profundamenteNo
HaskellNoNoNoNoNoNo [6]NoNo
JavaNoNo [2]Noanidado profundamenteanidado profundamenteNono nativo [12]no nativo [12]
JavaScriptNoNo [2]Noanidado profundamenteanidado profundamenteNo
NaturalNoNo
OCamlNoNomatrices, listasNoNoNo [6]NoNo
PHPNoNo [2] [5] [4]Noanidado profundamenteanidado profundamenteNo
PerlNoNo [2] [5] Noanidado profundamenteanidado profundamente
PitónNoNoNo [5]NoNoanidado profundamente [6]anidado profundamente [6]No
RebolNo [7]No [8]un nivel [6]NoNo
RubíNoNoanidado profundamente [6]anidado profundamente [6]
ML estándarNoNoNomatrices, listasNoNoNo [6]NoNo
Visual Basic .NETNoNoun nivel por tipo de bucleun nivel por tipo de bucle
Potencia ShellNoNo [2]No?
  1. a while (true) no cuenta como un bucle infinito para este propósito, porque no es una estructura de lenguaje dedicada.
  2. El bucle de C es una construcción de bucle general , no específicamente de conteo, aunque a menudo se usa para eso. for (init; test; increment)
  3. Se pueden lograr saltos profundos en APL, C, C++ y C # mediante el uso de etiquetas y gotos.
  4. Se agregó una iteración sobre objetos en PHP 5.
  5. a b c Se puede simular un bucle de conteo iterando sobre una lista incremental o un generador, por ejemplo, . de Python . range()
  6. Las interrupciones profundas se pueden lograr mediante el uso del manejo de excepciones .
  7. a No existe ninguna construcción especial, ya que la whilefunción se puede utilizar para esto.
  8. a No existe una construcción especial, pero los usuarios pueden definir funciones de bucle generales.
  9. a El estándar C++11 introdujo el for basado en rangos . En STL , hay una función 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]
  10. Se efectúa un bucle controlado por conteo mediante iteración a lo largo de un intervalo entero; salida anticipada mediante la inclusión de una condición adicional para la salida.
  11. a Eiffel admite una palabra reservada retry, sin embargo se utiliza en el manejo de excepciones , no en el control de bucles.
  12. a Requiere el lenguaje de especificación de interfaz de comportamiento Java Modeling Language (JML).
  13. a Requiere que las variantes de bucle sean números enteros; no se admiten variantes transfinitas. [1]
  14. a D admite colecciones infinitas y la capacidad de iterar sobre esas colecciones. Esto no requiere ninguna construcción especial.
  15. Se pueden lograr rupturas profundas utilizando procedimientosGO TO y .
  16. Common Lisp es anterior al 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 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 .

Condiciones

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 

Excepciones

Los lenguajes modernos tienen una estructura especializada para el manejo de excepciones que no depende del uso de GOTOsaltos 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 esté ya capturado actionForAnythingElse }                        

Se puede utilizar cualquier cantidad y variedad de catchcláusulas en la parte superior. Si no hay catchuna coincidencia con una determinada throw, el control se transmite a través de llamadas a subrutinas o bloques anidados hasta que catchse 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++, catches 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 exceptionpara introducir un manejador de excepciones y luego pueden incluso emplear una palabra clave diferente ( whenen 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 errorconstrucció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, finallyse puede agregar una cláusula a la tryconstrucción. Sin importar cómo se deje el control, se garantiza tryque el código dentro de la finallyclá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 usingbloque -, el compilador garantiza que el stmobjeto se libere, vinculando efectivamente la variable al flujo de archivo mientras se abstrae de los efectos secundarios de inicializar y liberar el archivo. withLa declaración de Python y el argumento de bloque to de Ruby File.opense 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.

Continuaciones

Asíncrono

C# 5.0 introdujo la palabra clave async para admitir E/S asincrónica en un "estilo directo".

Generadores

Los generadores , también conocidos como semicorrutinas, permiten ceder el control a un método de consumidor temporalmente, generalmente mediante una yieldpalabra clave (descripción de rendimiento). Al igual que la palabra clave async, esto permite programar en un "estilo directo".

Corrutinas

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.

Referencia cruzada de flujo de control no local

Lenguaje de programacióncondicionesexcepcionesgeneradores/corrutinasasíncrono
AdaNo??
doNoNoNoNo
C++No?
DO#No
COBOLNoNo
Ceceo comúnNo??
DNo?
Torre EiffelNo??
ErlangNo?
F#No
IrNo?
HaskellNoNo
JavaNoNoNo
JavaScript?
Objetivo-CNoNo?
PHPNo?
PL/YONoNoNo
PitónNo[25]
RebolNo?
RubíNovía extensión [26]
ÓxidoNoExperimento [27] [28][29]
EscalaNomediante extensión experimental [30]mediante extensión experimental
Tcla través de rastrosa través de bucle de eventos
Visual Basic .NETNo?
Potencia ShellNoNo?

Estructuras de control propuestas

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.

Bucle con prueba en el medio

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.

Múltiples salidas anticipadas/salidas de 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 ;

Seguridad

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]

Véase también

Referencias

  1. ^ Böhm, Jacopini. "Diagramas de flujo, máquinas de Turing y lenguajes con sólo dos reglas de formación" Comm. ACM , 9(5):366-371, mayo de 1966.
  2. ^ ab Roberts, E. [1995] "Salidas de bucle y programación estructurada: reabriendo el debate Archivado el 25 de julio de 2014 en Wayback Machine .", ACM SIGCSE Bulletin, (27)1: 268–272.
  3. ^ David Anthony Watt; William Findlay (2004). Conceptos de diseño de lenguajes de programación . John Wiley & Sons. pág. 228. ISBN 978-0-470-85320-7.
  4. ^ "Bucles anidados en C con ejemplos". GeeksforGeeks . 2019-11-25 . Consultado el 2024-03-14 .
  5. ^ "Bucles anidados en Python". www.w3schools.com . Consultado el 14 de marzo de 2024 .
  6. ^ Dean, Jenna (22 de noviembre de 2019). "Bucles anidados". The Startup . Consultado el 14 de marzo de 2024 .
  7. ^ Programación Ada: Control: Bucle sin fin
  8. ^ "¿Qué es un bucle y cómo podemos utilizarlo?". Archivado desde el original el 28 de julio de 2020. Consultado el 25 de mayo de 2020 .
  9. ^ "rehacer - perldoc.perl.org". perldoc.perl.org . Consultado el 25 de septiembre de 2020 .
  10. ^ "control_expressions - Documentación para Ruby 2.4.0". docs.ruby-lang.org . Consultado el 25 de septiembre de 2020 .
  11. ^ "control_expressions - Documentación para Ruby 2.3.0". docs.ruby-lang.org . Consultado el 25 de septiembre de 2020 .
  12. ^ Guía avanzada de programación en Bash: 11.3. Control de bucle
  13. ^ Manual de PHP: "romper"
  14. ^ perldoc: último
  15. ^ Lista de preguntas frecuentes de comp.lang.c · "Pregunta 20.20b"
  16. ^ [Python-3000] Anunciamos PEP 3136, Guido van Rossum
  17. ^ ab Kozen, Dexter (2008). "El teorema de Böhm-Jacopini es falso, proposicionalmente". Matemáticas de la construcción de programas (PDF) . Apuntes de clase en informática. Vol. 5133. págs. 177–192. CiteSeerX 10.1.1.218.9241 . doi :10.1007/978-3-540-70594-9_11. ISBN .  978-3-540-70593-2.
  18. ^ Kosaraju, S. Rao. "Análisis de programas estructurados", Proc. Fifth Annual ACM Syrup. Theory of Computing, (mayo de 1973), 240-252; también en J. Computer and System Sciences, 9, 3 (diciembre de 1974). citado por Knuth, Donald (1974). "Programación estructurada con instrucciones go to". Computing Surveys . 6 (4): 261–301. CiteSeerX 10.1.1.103.6084 . doi :10.1145/356635.356640. S2CID  207630080. 
  19. ^ David Anthony Watt; William Findlay (2004). Conceptos de diseño de lenguajes de programación . John Wiley & Sons. pp. 215–221. ISBN 978-0-470-85320-7.
  20. ^ Meyer, Bertrand (1991). Eiffel: El lenguaje . Prentice Hall. Págs. 129-131.
  21. ^ "Macro LOOP de Common Lisp".
  22. ^ for_each. Sgi.com. Recuperado el 9 de noviembre de 2010.
  23. ^ Capítulo 1. Boost.Foreach Archivado el 29 de enero de 2010 en Wayback Machine . Boost-sandbox.sourceforge.net (19 de diciembre de 2009). Consultado el 9 de noviembre de 2010.
  24. ^ David Anthony Watt; William Findlay (2004). Conceptos de diseño de lenguajes de programación . John Wiley & Sons. pp. 221–222. ISBN 978-0-470-85320-7.
  25. ^ "Asyncio: E/S asincrónica: documentación de Python 3.10.2".
  26. ^ "Socketry/Async". GitHub . 25 de febrero de 2022.
  27. ^ "Generadores - El libro de Rust Unstable".
  28. ^ "Corona - Óxido".
  29. ^ "Introducción: programación asincrónica en Rust".
  30. ^ "Encuentro con Jitsi". Storm-enroute.com . Consultado el 7 de septiembre de 2022 .
  31. ^ No sabemos a dónde IR si no sabemos de dónde VENIMOS. Esta innovación lingüística (parodia) está a la altura de todas las expectativas. Archivado el 16 de julio de 2018 en Wayback Machine Por R. Lawrence Clark* De Datamation, diciembre de 1973
  32. ^ Knuth, Donald E. "Programación estructurada con instrucciones go to" ACM Computing Surveys 6(4):261-301, diciembre de 1974.
  33. ^ Dahl & Dijkstra & Hoare, "Programación estructurada" Academic Press, 1972.
  34. ^ Zahn, CT "Una declaración de control para la programación estructurada natural de arriba hacia abajo" presentada en el Simposio sobre lenguajes de programación, París, 1974.
  35. ^ Payer, Mathias ; Kuznetsov, Volodymyr. "Sobre las diferencias entre las propiedades CFI, CPS e CPI". nebelwelt.net . Consultado el 1 de junio de 2016 .
  36. ^ "El descubrimiento de un error en Adobe Flash conduce a un nuevo método de mitigación de ataques". Dark Reading . 10 de noviembre de 2015 . Consultado el 1 de junio de 2016 .
  37. ^ Endgame. "Endgame se presentará en Black Hat USA 2016". www.prnewswire.com . Consultado el 1 de junio de 2016 .

Lectura adicional

  • Hoare, CAR "Partición: Algoritmo 63", "Ordenación rápida: Algoritmo 64" y "Encontrar: Algoritmo 65". Comm. ACM 4, 321–322, 1961.
  • Medios relacionados con Flujo de control en Wikimedia Commons
  • Ir a la declaración considerada perjudicial
  • Una contribución lingüística de la programación sin GOTO
  • "Programación estructurada con instrucciones Go To" (PDF) . Archivado desde el original (PDF) el 24 de agosto de 2009. (2,88 MB)
  • "Manual del IBM 704" (PDF) . (31,4 MB)
Obtenido de "https://es.wikipedia.org/w/index.php?title=Flujo_de_control&oldid=1251796167"