Ramificación (programación)

La bifurcación en programación es una operación utilizada en los casos en que la ejecución o no ejecución de un determinado conjunto de comandos debe depender del cumplimiento o incumplimiento de una determinada condición. La ramificación es una de las tres (junto con la ejecución secuencial de comandos y el bucle ) estructuras básicas de la programación estructurada .

Las principales formas de implementar la bifurcación en los lenguajes de programación imperativos son el operador condicional (operador if) y el operador de elección de valores múltiples (interruptor, case, switch). En los primeros lenguajes de bajo nivel, la bifurcación se implementa a través del operador de bifurcación condicional .

Operador condicional

El operador condicional implementa la ejecución de ciertos comandos, siempre que alguna expresión lógica (condición) tome el valor "verdadero" true. En la mayoría de los lenguajes de programación, una declaración condicional comienza con una palabra clave if(traducida del  inglés  -  "si"). Existen las siguientes formas del operador condicional: con una rama y dos.

Al ejecutar una declaración condicional con una rama if <условие> then <команды> end, se evalúa la condición y, si es verdadera, se ejecutan los comandos hasta la palabra clave end; de ​​lo contrario, la ejecución del programa continúa con el comando que sigue a la declaración condicional. En lenguajes de bajo nivel ( ensambladores ), esta es la única forma disponible del operador condicional. En algunos idiomas, se usa una palabra clave especial para un operador condicional con una rama (generalmente esto then, traducido del  inglés  -  "eso").

Al ejecutar un operador condicional con dos ramas if <условие> then <команды1> else <команды2> end , si la condición es verdadera, se ejecutan los comandos posteriores a la palabra clave ; si la condición es thenfalsa, se ejecutan los comandos posteriores a la palabra clave else. Si es necesario verificar varias condiciones secuencialmente, es posible hacer una cascada de sentencias condicionales:

si la condición 1 entonces ordena 1 otra cosa si la condición 2 entonces ordena 2 otra cosa si la condición 3 entonces ordena 3 ... si no la condición N + 1 entonces ordena N + 1 otra cosa ordena fin ;

En este caso, las condiciones se verificarán secuencialmente y, tan pronto como se cumpla verdadero, se ejecutará el conjunto de comandos correspondiente y la ejecución irá al comando que sigue a la declaración condicional. Si ninguna de las condiciones es verdadera, se ejecutan los comandos de la rama else.

Varios lenguajes de programación contienen una construcción especial para sentencias condicionales en cascada, lo que le permite escribir ramificaciones múltiples de manera algo más compacta y menos propensa a escribir errores:

if condition1 then ordena1 elsif condition2 then ordena2 elsif condition3 then ordena3 ... else ordena fin ; el orden de ejecución de esta declaración corresponde exactamente a la cascada anterior de declaraciones simples if-then-else, y la diferencia es puramente formal: en lugar de varias declaraciones condicionales anidadas, esta construcción es un todo único y contiene una palabra clave adicional elsifque requiere otra condición después de sí mismo.

Implementaciones

Pascal heredó la sintaxis de Algol -60, según la cual solo se puede colocar un comando en las ramas de un operador condicional. Por lo tanto, para acomodar más comandos allí, se agrupan en una declaración compuesta usando el par de palabras clave beginy end. La rama else es opcional. beginy endson necesarios solo si hay varios operadores (por ejemplo, por razones de uniformidad del código ). En el ejemplo, el operador de elección en Pascal:

Si la condición comienza las declaraciones ; end else begin sentencias ; fin ;

La necesidad de un operador condicional en Algol y Pascal ha sido criticada desde sus inicios. Los críticos dijeron que numerosas declaraciones compuestas saturan el programa, interfieren con la sangría normal y provocan errores (si olvida la declaración compuesta donde se necesita en la última rama de la declaración if, el compilador no notará nada, pero al ejecutar un programa de un grupo de declaraciones que deben ejecutarse en esta rama, según la condición, solo se ejecutará la primera, todo el resto, siempre). Las próximas generaciones de idiomas, descendientes de Algol, intentaron deshacerse de esta deficiencia. Entre ellos se encuentran tres lenguajes ampliamente conocidos: Algol-68 , Modula-2 y Ada . La construcción de la declaración if en ellos es casi la misma, excepto por palabras clave individuales:

  • Algol-68:
si la condición ... fi ;
  • Módulo-2
SI condición 1 ENTONCES comando 1 DE LO CONTRARIO SI condición 2 ENTONCES comando 2 ... DE LO CONTRARIO comando N FIN ;
  • ada
si condición1 entonces ordena1 else si condición2 entonces ordena2 ... else ordenaN end if ;

En todos los casos, "commandX" es cualquier número de sentencias separadas por punto y coma. En todos los casos, todas las ramas de la declaración condicional, excepto la primera (ramas "entonces"), son opcionales y se pueden omitir. Si no hay una rama "else" y no se cumple ninguna de las condiciones, el control se transfiere al comando que sigue a la palabra clave de finalización condicional (END, FI o END IF).

C y C++ (y después de ellos Java , C# , PHP y muchos otros lenguajes) tienen un operador condicional que es estructuralmente similar a Pascal. beginLa diferencia es que la condición debe escribirse entre paréntesis y endse utilizan corchetes en lugar de palabras clave {}:

si ( < condición > ) { < operadores > } más { < operadores > }

En Nemerle , a diferencia de la mayoría de los lenguajes, donde un operador ifpuede tener una o dos ramas, un operador if(la sintaxis es completamente similar al lenguaje C) debe tener dos ramas. Un operador condicional con una rama comienza con la palabra clave when, además, el lenguaje tiene otro operador condicional - unless, que es un "cuando inverso" - en él se ejecutan los comandos de la rama condicional si la condición es falsa.

cuando ( condición ){ declaraciones } a menos que ( condición ) { declaraciones }

En Forth , el operador condicional tiene una forma diferente que en otros idiomas, debido a la notación de sufijo y la organización de la pila. El operador condicional consta de tres palabras IF ELSE THEN[1] .

< condición > SI < expresión _1_ si _ condición _ es verdadera > DE LO CONTRARIO < expresión _2_ si _ condición _ es falsa > ENTONCES

Aquí <условие>simplemente coloca el valor en la parte superior de la pila, IFanaliza la bandera y si:

  • no es igual a cero, entonces se ejecutan las expresiones hasta ELSEo THEN;
  • si es igual a cero, entonces se ejecutan las expresiones entre ELSEy THEN.

Si está ausente ELSE, se obtiene un selector con una rama: las expresiones entre IFy THENsolo se ejecutan si la bandera es distinta de cero.

Fortran inicialmente solo tenía IF aritmético , en el que, dependiendo del signo de la expresión, se hacía una transición a una de las tres etiquetas. Por ejemplo, parte del código de la rutina de resolución de ecuaciones cuadráticas:

DN = B * B - 4 * A * C IF ( DN ) 90 , 10 , 10 10 D = SQRT ( DN ) X1 = ( - B + D ) / ( 2 * A ) X2 = ( - B - D ) / ( 2 * A )

Luego se agregaron expresiones lógicas (booleanas) y un IF lógico con una declaración, evaluada por GOTO, más tarde, un IF estructural (con varias condiciones), por ejemplo:

DN = B * B - 4 * A * C _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 2 * A ) ¡ SINO ! (sin código para discriminante negativo) END IF

Perl admite la estructura if multicondicional, así como los modificadores de instrucciones, que se escriben después de la parte de la instrucción que se ejecuta. Por ejemplo, los siguientes dos ejemplos son idénticos en funcionalidad:

if ( $a == 0 ) { ++ $cero_cuenta ; } ++ $recuento_cero si $a == 0 ;

En lugar de si, puede escribir a menos que, lo que hace que el valor de la expresión condicional se invierta antes de verificar. La misma acción a menos que:

++ $recuento_cero a menos que $a != 0 ;

Para una declaración compuesta (bloque), solo se permite la forma estructural, no el modificador. Por ejemplo:

si ( $a == 0 ) { ... } más si ( $a > 0 ) { ... } más { ... }

La palabra clave final no es necesaria, debido al requisito de que las declaraciones bajo las condiciones estén formateadas en bloques {…}.

No hay equivalente de a menos que para las ramas de elsif.

Erlang usa dos declaraciones condicionales: si y caso. Ambos tienen un valor de resultado que es igual al valor de la última declaración en la rama ejecutada y se puede usar (asignar a un nombre, pasar a una función...), por lo que no hay una declaración condicional ternaria separada en él . En la sentencia case, se realiza Pattern Matching , con la posibilidad de condiciones adicionales sobre los valores en el comparado, y en la sentencia if, solo prueba de condiciones. Las pruebas de protección permiten un conjunto limitado de operaciones y funciones integradas.

Ejemplo de caso (borrar una entrada de evento del árbol de tiempos):

{ NewEBW , NewEBN } = caso dict : encontrar ( EBN , Nodo ) de error -> { EBW , EBN }; { ok , Expiry } -> { gb_trees : delete_any ({ Expiry , Node }, EBW ), dict : erase ( Node , EBN )} end ,

Ejemplos de si:

if NeedLater -> erlang : send_after ( trunc ( 1000 * ( 1 + Elapsed )), self (), i_apply_nodes_portion ); verdadero -> final nop , After2 = if %% Si fue hace demasiado tiempo, programe el tiempo de espera inmediatamente. Después de 1 =< ? CADUCIDAD_PASO_MIN -> ? CADUCIDAD_PASO_MIN ; Después de 1 >= ? CADUCIDAD_PASO_MAX -> ? CADUCIDAD_PASO_MAX ; verdadero -> After1 final ,

Operador de opción múltiple

El diseño del interruptor tiene varias (dos o más) ramas. El conmutador ejecuta una rama determinada según el valor de la expresión clave evaluada. La diferencia fundamental entre esta instrucción y un operador condicional es que la expresión que determina la elección de la rama ejecutable no devuelve un valor lógico, sino un valor entero, o un valor cuyo tipo puede convertirse en un número entero. Algunos idiomas permiten el uso de algunas expresiones de tipo no entero (por ejemplo, cadenas de texto) en un conmutador.

El prototipo de la construcción sintáctica moderna fue la instrucción de salto utilizada en los antiguos lenguajes de programación. Este comando especificaba una expresión de selector que devolvía un valor entero y un conjunto de etiquetas. Cuando se ejecutó el comando, se evaluó la expresión y se usó su valor como el número de la etiqueta (en la lista de comandos) a la que se realizó la transición. Tales construcciones estaban, por ejemplo, en los lenguajes de programación Fortran ("computed GOTO") y BASIC . El lado atractivo del diseño es su eficiencia bastante alta: para determinar la rama deseada (marcador de salto), no es necesario comparar secuencialmente el resultado de la expresión del selector con muchos valores, es suficiente almacenar una matriz de instrucciones de salto incondicionales con las direcciones necesarias en memoria para que cuando se ejecute el comando se calcule directamente el elemento deseado a partir de valores de expresión. En este caso, la velocidad de ejecución del comando no depende del número de etiquetas. En los lenguajes modernos, la implementación de una declaración de cambio también se implementa a menudo como una tabla de salto, que consta de comandos de salto incondicionales a los fragmentos de código correspondientes. La expresión que se va a evaluar se convierte en un valor de desplazamiento de la tabla de saltos que especifica el comando que se va a ejecutar. En los lenguajes donde la expresión del selector puede tener un valor no entero, no siempre es posible evaluar directamente la rama deseada de la construcción del interruptor, por lo que utilizan otros métodos para optimizar la ejecución.

En los lenguajes de programación modernos de alto nivel, un comando de cambio generalmente tiene un nombre switch(traducido del  inglés  -  "interruptor") o case(traducido del  inglés  -  "caso"). Sin embargo, la selección de etiqueta calculada se puede conservar en los lenguajes de programación modernos de bajo nivel, como la instrucción JL del lenguaje de programación STL para los controladores lógicos programables S7-300 y S7-400 fabricados por Siemens .

Por ejemplo, en C, la sintaxis del comando es:

cambiar ( yo ) { caso 0 : case 1 : // secuencia de sentencias break ; case 2 : // secuencia de sentencias break ; predeterminado : ; }

Aquí i es una expresión selectora que debe ser de tipo entero convertible, cada rama de ejecución comienza con la palabra clave case, seguida del valor de la expresión bajo la cual se ejecutará esta rama. Una característica interesante del lenguaje C es que en él el interruptor se interpreta exactamente como un comando para saltar sobre una etiqueta calculada, y los encabezados de rama ( case значение :) juegan el papel de etiquetas. Para salir de la declaración de cambio después de completar el código de rama, se usa un comando especial break. Si no existe tal comando en la rama, después de la ejecución del código de la rama seleccionada, comenzará la ejecución del código siguiente. Esta característica se puede usar para la optimización, aunque puede causar errores difíciles de encontrar (si el programador pierde un descanso accidentalmente, el compilador no arrojará un error, pero el programa se ejecutará incorrectamente). La rama predeterminada se ejecuta cuando ninguna de las otras ramas es adecuada.

Muchos lenguajes heredan la sintaxis del comando de cambio C, pero su semántica no siempre es completamente similar a C. Por ejemplo, C# le permite usar una expresión de selector de tipo de cadena y etiquetas apropiadas.

Características del cálculo de expresiones lógicas

El orden de ejecución de un programa con sentencias condicionales puede verse significativamente afectado por la lógica de evaluación de las expresiones condicionales adoptada en el lenguaje. Cuando la condición es una expresión booleana compleja, como "f(x) > 0 AND g(y) > 0", hay dos estrategias para evaluar su resultado:

  • Cálculo completo: calcule f(x), g(y), compare los resultados con cero y luego realice una operación AND en los resultados. También lo hace, por ejemplo, Visual Basic.
  • Cálculo incompleto: calcular f(x), comparar valor con cero. Si el resultado de la comparación es "verdadero", evalúe el resto de la expresión. Si la primera condición es falsa, omita la segunda condición, incluido el cálculo de g(y) incluido en ella, ya que para la operación "Y", si uno de los operandos es falso, la expresión completa es obviamente falsa.

La segunda opción es la más común para lenguajes industriales (en particular, para Algol, Fortran, C++, C, Java, JavaScript, ECMAScript, JScript, C#, Python). Estos lenguajes tienen una regla estricta: "Una expresión lógica siempre se evalúa de izquierda a derecha y su evaluación se detiene tan pronto como se define el resultado de toda la expresión". Esto significa que si una expresión consta de varias subcondiciones combinadas con el operador AND (AND), la evaluación de la expresión se detendrá tan pronto como una de las subcondiciones resulte ser falsa (ya que "falso AND cualquier valor" siempre da como resultado “falso”), y viceversa, si se combinan varias subcondiciones con el operador OR (OR), la evaluación se detendrá después de la primera subcondición verdadera, ya que en este caso toda la expresión es verdadera, independientemente de la evaluación posterior. Pero una expresión que contiene el operador OR exclusivo (XOR) no se puede evaluar completamente, ya que uno de los valores que contiene no puede determinar el resultado del cálculo de la expresión completa.

Los idiomas Ada y Erlang usan diferentes palabras clave para estas variantes: las palabras y y o significan evaluación completa, y luego, o bien (Ada), y también orelse (Erlang) — incompleta. En Erlang, andalso y orelse tienen menor precedencia que los operadores de comparación, lo que evita los paréntesis alrededor de las condiciones elementales. El lenguaje Visual Basic .NET tiene palabras clave similares: AndAlso y OrElse.

El orden fijo de evaluación de las subcondiciones (la expresión lógica siempre se evalúa de izquierda a derecha) se introduce para poder controlar el orden de evaluación de la expresión y poner en ella primero aquellas condiciones que deben evaluarse primero. Por cierto, así es como las expresiones lógicas se diferencian de las aritméticas, por lo que, en la mayoría de los lenguajes, el orden de evaluación de las subexpresiones (a menos que esté determinado por la prioridad y la asociatividad de las operaciones) no está especificado por el lenguaje y puede ser diferente en diferentes casos

La elección de esta particular lógica de ejecución se debe a que permite simplificar las expresiones lógicas que utilizan elementos dependientes. El ejemplo clásico es una búsqueda lineal en una matriz:

// Buscar en una matriz de enteros en lenguaje Pascal // Parámetros: el valor deseado y una matriz abierta de enteros // Resultado: el índice del elemento encontrado o -1 si el elemento no se encuentra function Find ( e : integer ; var a : matriz de enteros ) : entero ; var i : entero ; comienzo i := 0 ; while ( i <= High ( a )) AND ( a [ i ] <> e ) do inc ( i ) ; // !!! si i <= Alto ( a ) entonces Encuentra := i sino Encuentra := - 1 ; fin ;

El algoritmo implementado por el programa es bastante obvio, pero hay una sutileza en la implementación (vea la línea marcada con signos de exclamación): la condición del bucle consta de dos partes conectadas por el operador AND. La primera subcondición verifica si el índice i ha ido más allá de la matriz, la segunda verifica si el elemento actual de la matriz no es igual al valor deseado. Si la matriz no contiene el valor deseado, luego de verificar el último elemento, el valor de la variable i aumentará en uno; en la siguiente iteración, la primera subcondición será falsa y el ciclo finalizará sin comprobar la segunda subcondición. Si las expresiones lógicas se evaluaran por completo, si no hubiera ningún elemento en la matriz después de la última iteración, se produciría un error: un intento de determinar a[i] provocaría un acceso incorrecto a la memoria.

Cabe señalar que, además de la evaluación incompleta del valor de la expresión, el orden fijo de evaluación de las subcondiciones también juega un papel importante aquí: dado que la verificación de matriz fuera de los límites se escribe primero, siempre será realizado antes de la verificación para alcanzar el valor deseado. Si el orden en que se evaluaron las subexpresiones no estuviera definido, sería imposible garantizar la corrección del fragmento de programa dado.

Con el cálculo completo de las expresiones lógicas, el algoritmo anterior tendría que escribirse aproximadamente de la siguiente forma:

// Buscar en una matriz de enteros en lenguaje Pascal // Variante hipotética con evaluación completa de expresiones lógicas // Parámetros: el valor deseado y una matriz abierta de enteros // Resultado: el índice del elemento encontrado o -1 si el elemento no se encontró la función Find ( e : integer var a : array of integer ) : integer ; _ var i : entero ; f : booleano ; // bandera adicional de terminación de bucle variable begin i := 0 ; f := falso ; while not f do si i > Alto ( a ) entonces f := verdadero sino si a [ i ] = e entonces f := verdadero sino inc ( i ) ; si i <= Alto ( a ) entonces Encuentra := i sino Encuentra := - 1 ; fin ;

Como puede ver, tuvimos que establecer artificialmente el orden en que se calcularon las condiciones de salida e introducir una variable adicional. Es para evitar tales trucos que se introduce la evaluación incompleta de las expresiones lógicas.

Nota: El código anterior es un ejemplo del uso de la declaración IF, pero no más. Este código no puede usarse como regla para escribir algoritmos en Pascal.

El algoritmo óptimo para encontrar un número en una matriz es:

función Buscar ( e : entero ; var a : matriz de enteros ) : entero ; var i : entero ; comenzar Resultado := - 1 ; for i := Bajo ( a ) a Alto ( a ) empieza si a [ i ] = e luego empieza Resultado : = i ; romper ; fin ; fin ; fin ;

Notas

  1. Forth tiene un operador <условие> ?DUP <выражение>que duplica una expresión si una condición es verdadera