5.4    Control de secuencia entre comandos.

Aquí veremos los mecanismos básicos para controlar el orden de ejecución de los comandos dentro de un programa.

Los resultados de cualquier programa están determinados por los comandos básicos, que aplican operaciones sobre los datos. Ejemplos de estos comandos son la asignación, llamadas a subprogramas, comandos de entrada y salida. Dentro de un comando b´`asico, se puede invocar operaciones usando expresiones, pero cada comando de éstos puede ser considerado como una unidad que representa un sólo paso del cómputo.

Comando de asignación: El propósito primordial de una asignación es atribuir al valor l (una localidad de memoria) un valor r (el valor de un objeto de datos) de cierta expresión.

La sintaxis de un comando de asignación varía de un lenguaje a otro:

Ej:

A:=B Pascal, Ada
A=B C, FORTRAN, Prolog, ML, SNOBOL4
MOVE B TO A COBOL
A<-B APL
SETQ A B LISP

En C la asignación es un operador y por lo tanto se puede escribir c=b=1, que significa c=(b=1). Pero lo más frecuente es que sea considerado como un comando por separado, como en Pascal:

X:=B+2*C
Y:=A+X

Casi todos los lenguajes tiene un solo operador de asignación, pero C tiene varios:

A=B : Asignar valor r de B a valor l de A, devolver valor r.
A+ =B(-=) : Incrementar (disminuir) A en B (A=A+B o A=A-B), devolver el nuevo valor.
++A (--A) : Incrementar (disminuir) el valor de A en 1, devolver el valor nuevo (A=A+1, devolver el valor r de A).
A++ (A--) : Devolver valor de A, luego incrementarlo (disminuirlo) en 1(devolver valor r de A, luego A=A+1).

En C el operador unario * es un operador de "indirección" que hace que el valor r de una variable se comporte como si fuera l, y el operador unario & es un operador de "dirección" que convierte el valor l en un valor r. Por ejemplo en:

int i, *p;
p=&i;
*p=7

tenemos que:

- i se declara como entero.

- p se declara como puntero a entero

- p se inicializa de modo que apunte a i, es decir, el valor l de i se convierte en valor r (&i) que se guarda como el valor r de p.

- el valor r de p se convierte en valor l (*p); como es el valor l de i, el valor r de i es fijado en 7.

Casi todos los lenguajes de programación incluyen comandos para leer datos del usuario desde un archivo o directamente. Estos comandos también cambian los valores de variables a través de asignaciones. Típicamente, la sintaxis es la de la forma: leer (archivo,datos). En C, una llamada de la función printf causa asignación a la variable "buffer" de un archivo.

La transferencia de parámetros se suele definir como una asignación del valor del argumento al parámetro formal. También se encuentran diversas formas de asignación implícita; por ejemplo, en SNOBOL4, cada referencia a la variable INPUT hace que se le asigne un nuevo valor a ella, el pattern matching en Prolog ocasiona la asignación implícita de variables. Se suele poder asignar un valor inicial a una variable como parte de su declaración.

Por lo general se distinguen 3 formas principales de control de secuencia a nivel de comandos:

secuencia
selección
repetición

Al construir programas, uno se ocupa de armar los comandos básicos que llevan a cabo el cómputo en los órdenes apropiados usando en forma repetida la secuencia, selección y repetición, para conseguir el efecto deseado. Dentro de cada una de éstas categorías generales de formas de control, se suelen usar variantes apropiadas para fines particulares.

Por ejemplo, en lugar de una selección con dos alternativas, puede haber una con varias alternativas como es el caso del "case" en Pascal.

Control explícito de secuencia.

Los primeros LP's tenían como modelo la máquina real subyacente encargada de ejecutar el programa. Por eso, FORTRAN, ALGOL, modelaban tipos de datos traducibles directamente a objetos de máquina, por ejemplo los reales y enteros eran traducidos a punto flotante y enteros de hardware respectivamente; y los comandos eran simples compuestos de rótulos y bifurcaciones. La transferencia de control se indica a menudo con el uso de un comando goto a un comando específico indicado con un rótulo.

Comando goto

En muchos lenguajes están presentes el goto incondicional como:

goto PROXIMO

donde el control es transferido al comando que tiene el rótulo (label) PROXIMO; y el goto condicional como:

if A=0 then goto PROXIMO

transfiere el control al comando que tiene el rótulo PROXIMO sólo si se cumple la condición especificada.

Aunque tiene una forma de comando simple, el goto ha sido blanco de muchas críticas en los últimos 25 años debido a que su uso indiscriminado conduce a programas poco estructurados.

Algo más importante es que se ha demostrado que el goto es un comando superfluo.

Los programas se pueden escribir con la misma facilidad sin él, en tanto tengan estructuras de control anidables como if o while, cosa que no tenían los primeros LP's.

Comando break

Ciertos lenguajes como C, incluyen un comando break, que hace que el control salga de un comando while, for o switch que lo encierra directamente y pase al comando siguiente.

Control de secuencia estructurado.

Secuencia

Es una serie de comandos que se pueden tratar como uno solo en la construcción de comandos más grandes. Suelen tener la siguiente sintaxis:

begin
·
·        } serie de comandos
·
end;

En C se escribe simplemente como {...}.

Se implementa colocando los bloques de código ejecutable en la memoria, luego el orden en que allí aparecen determina el orden en que se ejecutan.

Comandos condicionales

Tenemos diferentes alternativas:

if condición then comando endif      

constituye una bifurcación;

if condición then comando1 else comando2 endif      

un condicional de dos bifurcaciones pero sólo se ejecutará comando1 o comando2, pero no ambos;

if condición then comando1 elsif condición2 then comando2
·
·
·
elsif condiciónn then comandon else comandon+1      

expresa ramificaciones múltiples.

En un if de ramificaciones múltiples las condiciones suelen adoptar la forma de consulta repetida del valor de una variable, por ejemplo:

if Marca=0 then comando0
elsif Marca=1 then comando1 
elsif Marca=2 then comando2
else comando3
endif  

Esta estructura se expresa de manera más concisa en un comando case, como en Ada:

case
when 0 => begin comando0 end;
when 1 => begin comando1 end;
when 2 => begin comando2 end;
when others => begin comando3 end;
endcase

Los comandos if se implementan con facilidad usando las instrucciones usuales de bifurcación y salto manejadas por hardware. Los comandos case se implementan, por lo general, usando una tabla de saltos para evitar consultar repetidamente por el valor de la misma variable.

Una tabla de saltos es un vector con instrucciones salto no condicional, guardado en forma secuencial en la memoria. Se evalúa la expresión que forma la condición del comando case, y el resultado se transforma en un entero que representa el desplazamiento al interior de la tabla desde su base. cuando se ejecuta la instrucción de salto en ese desplazamiento, el control pasa al comienzo del código de la alternativa correspondiente. La estructura de implementación del case se muestra en la siguiente figura:

Instrucciones que preceden al case
Valor t de la variable MARCA Evaluación condición del case
Saltar a L0+t Tabla de saltos
L0: Saltar a L1
Saltar a L2
Saltar a L3
Saltar a L4
L1: Instrucciones para comando 0  Alternativas del case
Saltar a L5
L2: Instrucciones para comando 1
Saltar a L5
L3: Instrucciones para comando 2
Saltar a L5
L4: Instrucciones para comando 3
L5: Instrucciones que siguen al case

Comandos de iteración

La iteración es el mecanismo básico para los cálculos repetidos (el otro es la recursividad). La estructura básica de un comando de iteración consiste en una cabeza y un cuerpo. La primera controla el número de veces que se repetirá el enunciado que constituye el cuerpo.

Algunos comandos de iteración son:

Repetición simple:
perform cuerpo k times

El perform de COBOL es representativo de esta construcción. se evalúa k y se repite el cuerpo ese número de veces. Pero surgen algunas cuestiones útiles: ¿se puede volver a evaluar k en el cuerpo y cambiar el número de iteraciones?, ¿que ocurre si k es cero o negativo?

Repetición mientras se cumple una condición:
while condición do cuerpo

La condición se reevalúa después de cada vez que se ejecuta el cuerpo. En este caso es necesario que cambien algunos valores que participan en la condición, sino, una vez iniciada, nunca termina.

Repetición mientras se incrementa un contador:

En la cabeza se especifica un valor inicial, un valor final y un incremento para una variable índice. El cuerpo se ejecuta primero con el valor inicial, luego con el valor inicial más el incremento, luego el valor inicial más dos veces el incremento, y así sucesivamente hasta que alcanza el valor final. En FORTRAN77 es la única forma de iteración disponible. La forma general está dada por el comando for de ALGOL:

for i:=1 step 2 until 30 do cuerpo

cualquiera de los valores, inicial, incremento y final, pueden ser expresiones. Surge una vez más la pregunta respecto a cuándo se hace el test de terminación y cuando y con que frecuencia se evalúan las diversas expresiones.

Repetición indefinida:

Tenemos construcciones de este tipo en Ada:

loop
...
exit when condición;
...
end loop;

o en Pascal, usando un while con una condición que siempre es cierta:

while cierta do begin ... end;

El comando for de C permite todos los conceptos anteriores en una sola construcción:

for (expresión1; expresión2; expresión3) { cuerpo }

expresión1 es el valor inicial, expresión2 la condición de repetición y expresión3, el incremento. Todas las expresiones son opcionales, lo que permite mucha flexibilidad. Algunas iteraciones en C:

contador de 1 a 10:  
for (i=1;i<=10;i++) { cuerpo }          
Iteración infinita:  
for(;;) { cuerpo }          
contador con condición de salida: 
for (i=1;i<=100 && nofinArchivo; i++) { cuerpo }          

La implementación de los comandos de iteración es simple, usando la instrucción bifurcar /saltar de hardware. En la implementación del for las expresiones de la cabeza se deben evaluar a la entrada de la iteración y guardarse en áreas temporales de almacenamiento para ser recuperadas al inicio de cada iteración y ser evaluadas nuevamente.