You are on page 1of 171

Tema 3.

Introducción al lenguaje de programación Modula-2 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de Facultad de Ciencias (Matemáticas)
la Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 3. Introducción al lenguaje de programación


Modula-2
3.1 Valores y tipos ................................................................ ................................ ....3
3.2 Representación de constantes ................................................................ ...........3
3.2.1 Valores numéricos enteros................................................................ ............4
3.2.2 Valores numéricos reales ................................................................ ..............4
3.2.3 Caracteres ................................................................ ................................ ....5
3.2.4 Cadenas de caracteres (Strings)................................................................ ...5
3.3 Tipos predefinidos ................................................................ .............................. 6
3.3.1 El tipo INTEGER ................................................................ ........................... 6
3.3.2 El tipo CARDINAL ................................................................ ......................... 7
3.3.3 El tipo REAL................................................................ ................................ ..7
3.3.4 El tipo LONGREAL................................................................ ........................ 8
3.3.5 El tipo CHAR................................................................ ................................ .9
3.4 Expresiones aritméticas................................................................ ...................... 9
3.5 Identificadores ................................................................ ................................ ..10
3.6 Constantes ................................................................ ................................ .......12
3.7 Variables................................................................ ................................ ...........13
3.7.1 Declaración de variables ................................................................ ............. 14
3.7.2 Sentencia de asignación ................................................................ ............. 14
3.8 Operaciones de Entrada y Salida. ................................ ................................ ....16
3.8.1 Operaciones de Salida................................................................ ................ 16
3.8.2 Operaciones de Entrada ................................................................ ............. 18
3.9 Estructura general de un programa ................................................................ ..20

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.

Informática Facultad de Ciencias (Matemáticas)


2 3.1 Valores y tipos

• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 3

Introducción
Modula-2 fue diseñado por Niklaus Wirth y es uno de los lenguajes de programación de alto
nivel más utilizado actualmente para la enseñanza de la programación de ordenadores. Existen
varias versiones de este lenguaje. Nos centraremos en el resto del curso en el estudio de una de
las versiones más populares: TopSpeed Modula-2.
Una de las características que diferencia a un lenguaje de programación de otras notaciones
para expresar algoritmos, como el pseudocódigo, es la menor flexibilidad del primero. En
efecto, un programa escrito en un lenguaje de programación de alto nivel consta de una serie de
instrucciones (o sentencias) que indican al ordenador los pasos a seguir para resolver cierto
problema. Al conjunto de reglas que dictan cuáles son las instrucciones posibles y el modo de
combinarlas se denomina sintaxis del lenguaje. En este tema comenzaremos el estudio de la
sintaxis del lenguaje Modula-2. Para definir cada una de las reglas utilizaremos la notación
BNF. Finalizaremos el tema con un ejemplo completo de un programa escrito en Modula-2 que
el alumno podrá probar en la primera práctica de la asignatura.

3.1 Valores y tipos


Todo programa de ordenador manipula una serie de datos para obtener unos resultados.
Definimos dato como un elemento de información que puede tomar un valor entre varios
posibles.
En programación, un dato puede tomar valores de una sola clase. No tiene sentido decir que el
nombre de una persona es “24”. Tampoco lo tiene decir que el número de personas en una clase
es “Pedro”. A las distintas clases de valores se denominan tipos. Así, un dato tiene asociado
un tipo que representa cual es la clase de valores que puede tomar. Algunos ejemplos de tipos
son:
• Valores numéricos enteros positivos y negativos: 100, -24, 36, ...
• Valores numéricos enteros y positivos: 0, 1, 2, 3, ...
• Valores numéricos reales: 3.1415, -16.18,...
• Valores carácter: ‘a’, ‘b’, ‘c’,...
Para cada tipo habrá una serie de operaciones que se le pueda aplicar. Así, tiene sentido sumar
dos valores numéricos, pero no dos letras. Se llama tipo abstracto de datos a un conjunto
de valores junto con las operaciones que permiten manipular éstos.
Dentro de un programa existirán dos tipos de valores:
• Valores constantes: se trata de valores cuya magnitud no cambia durante cualquier
ejecución del programa.
• Valores variables: la magnitud puede tener distintos valores en distintos puntos del
programa.

3.2 Representación de constantes


Uno de los objetivos de los lenguajes de programación es evitar las ambigüedades o
imprecisiones que existen en los lenguajes naturales. Por ejemplo, los anglosajones utilizan la
coma para separar millares, mientras que nosotros la usamos para separar la parte entera de la
decimal:

Informática Facultad de Ciencias (Matemáticas)


4 3.2 Representación de constantes

348,536 anglosajones trescientos cuarenta y ocho mil quinientos treinta y seis


España trescientos cuarenta y ocho con quinientas treinta y seis
Estudiaremos a continuación las distintas reglas que sigue el lenguaje Modula-2 para
representar valores de tipo numérico y de tipo carácter.

3.2.1 Valores numéricos enteros


Los valores numéricos enteros representan un número exacto de unidades y no pueden tener
parte fraccionaria. Un valor entero en Modula-2 se escribe con una secuencia de 1 o más dígitos
del 0 al 9 no separados entre ellos y precedido opcionalmente de los signos mas (+) o menos (-).
Ejemplos de valores válidos son:
2
+56
0
-2345
1000
Ejemplos de valores no válidos son:
123,32 NO se pueden insertar comas
22.56 NO se pueden insertar puntos
13B7 NO se pueden insertar letras
Usando la notación BNF:

Valor_Entero ::= [ + | - ] Secuencia_Digitos


Secuencia_Digitos ::= Digito { Digito }
Digito ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

3.2.2 Valores numéricos reales


Los valores numéricos reales permiten expresar cualquier cantidad, incluyendo fracciones de
unidad. En Modula-2, se pueden representar de dos modos: notación decimal habitual o
notación científica.
En la notación decimal habitual, un valor real se escribe con una parte entera terminada siempre
en un punto (.) seguida, opcionalmente, por una secuencia de dígitos que constituyen la parte
decimal. Ejemplos de valores válidos son:
-0.78
5.
+234.53
En la notación científica, un valor real se escribe como una mantisa, que es un número real en
notación decimal habitual, seguido de la letra E y un exponente entero que indica la potencia de
10 por la que se multiplica la mantisa. Ejemplos de valores válidos son:
-0.78E+2 equivale a –0.78 x 102
5.E-3 equivale a 5.0 x 10-3 = 0.005
+234.53E4 equivale a 234.53 x 104
Ejemplos de valores no válidos son:
.234 Es necesario al menos un dígito en la parte entera
José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA
Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 5

2,56 NO se pueden insertar comas


13.4B7 NO se puede insertar la letra B
Obsérvese que podemos escribir un mismo valor de distintos modos:
45.6 456.E-14.56E+1 45.60E+0 456000.00E-4
La definición usando notación BNF es:

Valor_Real ::= Valor_Real_Decimal | Valor_Real_Científica


Valor_Real_Decimal ::= Valor_Entero . [ Secuencia_Digitos ]
Valor_Real_Científica ::= Valor_Real_Decimal Factor_Escala
Factor_Escala ::= E Valor_Entero

3.2.3 Caracteres
Además de tipos numéricos, el lenguaje de programación Modula-2 permite operar valores de
tipo carácter. Se trata de las letras y signos que constituyen un texto. El valor correspondiente a
un carácter concreto se expresa en Modula-2 escribiendo dicho carácter entre comillas (“
situado en la tecla correspondiente al número 2 del teclado de un PC) o apóstrofos (‘ situado
en la tecla correspondiente al cierre de interrogación). Ejemplos de valores válidos son:
‘a’ Representa a la letra a minúscula
“A” Representa también a la letra a minúscula
“?” Representa el carácter correspondiente al cierre de interrogación
Ejemplos de valores no válidos son:
a Deben ir entre comillas o apóstrofes.
‘ab’ Solo puede ir una letra
‘a” Debe empezar y acabar con el mismo delimitador
Algunas observaciones son:
• El espacio en blanco (‘ ‘) es un carácter válido. A veces lo representaremos con ‘b’.
• Hay que distinguir entre el número entero 7 y la letra ‘7’.
• El valor correspondiente al carácter apóstrofo solo se puede representar entre comillas
(“ ’ ”) y viceversa (‘ “ ‘)
Usando notación BNF:

Valor_Caracter ::= ‘ Caracter_menos_apostrofo ‘ | “ Caracter_menos_comillas “


Caracter_menos_comillas ::= a | b | c | ... | 1 | 2 | 3 | ... símbolos
Caracter_menos_comillas ::= a | b | c | ... | 1 | 2 | 3 | ... símbolos

3.2.4 Cadenas de caracteres (Strings)


Normalmente no es suficiente trabajar con caracteres sueltos. Suele interesar trabajar con
palabras o frases. Estos valores se representan con cadenas de caracteres o Strings. Una cadena
de caracteres se escribe como una secuencia de caracteres entre apóstrofos o comillas.
Ejemplos válidos son:
“Coche” Representa la palabra Coche
‘Coche’ Idem al anterior

Informática Facultad de Ciencias (Matemáticas)


6 3.3 Tipos predefinidos

“La casa es verde” Representa una frase


Algunas observaciones son:
• Si una cadena incluye apóstrofos en su interior sólo podrá escribirse entre comillas y
viceversa. Ejemplo “Incluir entre ‘apóstrofos’ texto”.
• Es posible definir una cadena que no contenga ningún carácter. A ésta se llama cadena
vacía y se escribe como “” o ‘’.
Usando notación BNF:

Valor_Cadena ::= ‘ { Caracter_menos_apostrofo } ‘ | “ { Caracter_menos_comillas } “

3.3 Tipos predefinidos


En programación, un dato puede tomar valores de una sola clase y que a dicha clase se llama
tipo del dato. En Módula-2 existen tipos de datos predefinidos y sentencias para que el
programador defina nuevos tipos. Algunos de los tipos de datos predefinidos se designan con
los siguientes nombres: INTEGER, CARDINAL, REAL, LONGREAL, CHAR y BOOLEAN.
Estudiaremos los cinco primeros a continuación. Estudiaremos el tipo BOOLEAN y los
mecanismos para definir nuevos tipos en temas posteriores.

3.3.1 El tipo INTEGER


En TopSpeed Modula-2 los valores de este tipo son los valores numéricos enteros positivos y
negativos comprendidos en el rango [-32768 , 32767]. La representación de cualquiera de estos
valores en la memoria del ordenador es exacta (no se producen errores de precisión al
operarlos).
En caso de no recordar estos valores, podemos hacer referencia a ellos con las expresiones
MIN(INTEGER) y MAX(INTEGER).
Las operaciones definidas para valores de este tipo son las siguientes:
• a + b. Suma de los enteros a y b
• a – b. Diferencia de los enteros a y b
• a * b. Producto de los enteros a y b
• a DIV b. Cociente resultante de dividir el entero a por el entero b.
• a MOD b. Resto resultante de dividir el entero a por el entero b.
• ABS(a). Valor absoluto del entero a.

• + a. Devuelve el valor del entero a inalterado.


• - a. Devuelve el valor del entero a negado.
Como puede observarse, los signos + y – tienen un doble significado según se usen como
operadores infijos entre dos enteros o prefijos frente a un único entero.
El operador DIV devuelve la parte entera resultante de dividir un número entero por otro. MOD
devuelve el resto correspondiente. Cuando el divisor es cero se produce un error. Se cumple
siempre la siguiente regla:
a MOD b = a – b (a DIV b)
Algunos ejemplos de uso de estos operadores son los siguientes:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 7

Operación Resultado
10 DIV 3 3
10 MOD 3 1
(-20) DIV (-7) 2
(-20) MOD (-7) -6
17 DIV (-3) -5
17 MOD (-3) 2
10 DIV 0 Error
ABS (-2) 2

Cuando se están operando datos de tipo INTEGER hay que tener en cuenta el rango de valores
válidos. Si durante algún momento se produce un resultado fuera de dicho rango, el programa
acabará con un error (suponemos que la opción de chequeo de rangos del compilador está
activada). Debido a esto, la propiedad asociativa no es válida para los datos del tipo INTEGER
y ciertos operadores. Por ejemplo
Operación Resultado
(32767 + 1) –10 Error
32767 + (1-10) 32758

ya que en la primera expresión la suma se sale del rango válido.

3.3.2 El tipo CARDINAL


En TopSpeed Modula-2 los valores de este tipo son los valores numéricos enteros positivos
comprendidos en el rango [0 , 65535]. La representación de cualquiera de estos valores en la
memoria del ordenador es exacta (no se producen errores de precisión al operarlos).
En caso de no recordar el valor máximo, podemos nombrarlo con la expresión MAX(CARDINAL).
Con excepción de la operación de cambio de signo o negación las operaciones son las mismas
que para el tipo INTEGER.
Es un error intentar operar un dato de tipo INTEGER con otro de tipo CARDINAL. Sólo se
pueden operar datos del mismo tipo.

3.3.3 El tipo REAL


En TopSpeed Modula-2 los valores de este tipo son los valores numéricos reales positivos y
negativos comprendidos en el rango [-3.4x1038, +3.4x1038]. La representación de estos valores
en la memoria del ordenador no es exacta (se producen errores de precisión al operarlos). Se
manejan valores aproximados.
Las operaciones definidas para valores de este tipo son las siguientes:
• a + b. Suma de los reales a y b
• a – b. Diferencia de los reales a y b
• a * b. Producto de los reales a y b
• a / b. Cociente de los reales a y b
• ABS(a). Valor absoluto del real a.

Informática Facultad de Ciencias (Matemáticas)


8 3.3 Tipos predefinidos

• + a. Devuelve el valor del real a inalterado


• - a. Devuelve el valor del real a negado.
En el caso de los reales la división no devuelve el cociente entero, sino el valor real resultante
de dividir el cociente por el divisor. Para el tipo REAL NO existen las operaciones DIV y MOD.
Operación Resultado
10.5 / 0.2 52.5
-15.4E2 + 450.0 -1090.0

3.3.4 El tipo LONGREAL


En TopSpeed Modula-2 los valores de este tipo son los valores numéricos reales positivos y
negativos comprendidos en el rango [-1.7x10308, +1.7x10308]. La representación de estos valores
en la memoria del ordenador no es exacta (se producen errores de precisión al operarlos). Se
manejan valores aproximados.
Las operaciones definidas para valores de este tipo son las mismas que para el tipo REAL. Para
valores de este tipo están también definidas las siguientes operaciones:
• Sin(a): Devuelve el seno del valor LONGREAL a como un valor de tipo LONGREAL.
• Cos(a): Devuelve el coseno del valor LONGREAL a como un valor de tipo
LONGREAL.
• Tan(a): Devuelve la tangente del valor LONGREAL a como un valor de tipo
LONGREAL.
• ASin(a): Devuelve el arco-seno del valor LONGREAL a como un valor de tipo
LONGREAL.
• ACos(a): Devuelve el arco-coseno del valor LONGREAL a como un valor de tipo
LONGREAL.
• ATan(a): Devuelve la arco-tangente del valor LONGREAL a como un valor de tipo
LONGREAL.
• Sqrt(a): Devuelve la raíz cuadrada del valor LONGREAL a como un valor de tipo
LONGREAL.
• Exp(a): Devuelve la función exponencial del valor LONGREAL a como un valor de
tipo LONGREAL.
• Log(a): Devuelve el logaritmo en base e del valor LONGREAL a como un valor de tipo
LONGREAL.
• Log10(a): Devuelve el logaritmo en base 10 del valor LONGREAL a como un valor de
tipo LONGREAL.
En el caso de las funciones trigonométricas, todos los ángulos que se manejan están expresados
en radianes, NO en grados.
Estas operaciones, a diferencia de todas las vistas hasta el momento, no están disponibles de
modo directo. Se encuentran en un módulo de biblioteca (MATHLIB) y es necesario que
sean importadas por el programa antes de ser usadas. Veremos posteriormente como se
importan operaciones de una biblioteca.
NOTA: Estas operaciones NO están definidas para valores del tipo REAL.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 9

3.3.5 El tipo CHAR


Este tipo incluye como valores todos los caracteres disponibles en el ordenador. Además de los
caracteres alfabéticos (letras), se incluyen caracteres numéricos y de puntuación.
Existen tres operaciones definidas para valores de este tipo:
• ORD(x): Toma como argumento un carácter y devuelve su posición dentro de la tabla
ASCII.
• CHR(x): Toma como argumento un valor entre 0 y 255 devuelve el carácter que ocupa
dicha posición dentro de la tabla ASCII.
• CAP(x): Toma como argumento un carácter y si éste es una letra minúscula devuelve la
letra mayúscula correspondiente. En otro caso, devuelve el mismo carácter que toma
como argumento.
Nótese que las funciones CHR y ORD son inversas la una de la otra:
CHR(ORD (x)) = x ORD(CHR (x)) = x.
Ejemplos de uso son:
Operación Resultado
ORD(‘A’) 65
CHR(65) ‘A’
CHR(66) ‘B’
CAP(‘q’) ‘Q’
CAP(‘Q’) ‘Q’
CAP(‘?’) ‘?’

3.4 Expresiones aritméticas


Una expresión aritmética representa un cálculo a realizar con valores. Para ello se combinan
operandos y operadores.
Si no se utilizan paréntesis, el orden en que se realizan las operaciones es el siguiente:
1º Cambios de signos: + y – como prefijos
2º Operadores multiplicativos *, /, DIV, y MOD
3º Operadores aditivos: + y – como infijos
Si aparece más de un operador del mismo nivel, las operaciones se realizan de izquierda a
derecha. Si una expresión va precedida de un cambio de signo se entiende que solamente afecta
al próximo operando. Si se quiere que éste afecte a toda la expresión, ésta deberá incluirse entre
paréntesis.
Algunos ejemplos son:
Expresión Equivale a
5 * 30 + 5 (4*30)+5
334 DIV 6 MOD 4 * 5 ((334 DIV 6) MOD 4) * 5
-5 * 10 MOD 3 ((-5) * 10) MOD 3
(5 + 10) * 4 + 5 ((5 + 10) * 4) + 5
En caso de duda es mejor utilizar los paréntesis.

Informática Facultad de Ciencias (Matemáticas)


10 3.5 Identificadores

En Modula-2, es un error operar datos de tipos distintos con los operadores aritméticos. En caso
de que sea necesario operar valores de tipos distintos utilizaremos las funciones de conversión
REAL y TRUNC:

• REAL(a): toma un valor de tipo INTEGER o CARDINAL y devuelve el REAL


equivalente.
• TRUNC(a): toma un valor de tipo REAL y devuelve el INTEGER o CARDINAL
equivalente. Para ello elimina la parte decimal del número original.

Expresión Equivale a
5.5 + REAL(10) 15.5
5.0 + REAL (10) 15.0
5.0 + 10 ERROR
5 + TRUNC(10.5) 15
5 + TRUNC(10.0) 15
Obsérvese que el tipo del resultado siempre coincide con el tipo de los operandos (REAL en los
dos primeros casos e INTEGER o CARDINAL en los dos últimos).

3.5 Identificadores
En cualquier programa aparece una serie de datos que son manipulados por éste. La manera de
hacer referencia a los diferentes elementos que intervienen en un programa es darles un nombre
particular a cada uno de ellos. En programación, se llaman identificadores a los nombres
usados para identificar cada elemento del programa.
En Modula-2, un identificador es una palabra formada por caracteres alfabéticos o numéricos
seguidos (sin espacios en blanco o signos de puntuación intercalados) y que debe comenzar por
una letra. Pueden usarse las letras mayúsculas y minúsculas del alfabeto inglés y los dígitos del
0 al 9. No se puede usar ni la eñe ni vocales acentuadas en un identificador, aunque sí se
pueden usar en valores de tipo carácter y cadenas de caracteres.
Ejemplos de identificadores válidos son:
Indice DiaDelMes Ejemplo1
IdentificadorMuyLargo

Ejemplos de identificadores no válidos:


3Ejemplo No pueden comenzar por un dígito
Ejemplo$ No se pueden usar signos de puntuación
Dia Del Mes No se pueden intercalar espacios
Año No se puede usar la eñe
En Modula-2, se distinguen las letras mayúsculas y minúsculas, por lo que son identificadores
diferentes DiaSemana, Diasemana y DIASEMANA.
La definición de un identificador usando la notación BNF es:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 11

Identificador ::= Letra { Letra | Dígito }


Dígito ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Letra ::= A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
P|Q|R|S|T|U|V|W|X|Y|Z|
a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|
p|q|r|s|t|u|v|w|x|y|z|

Sin embargo, no todos los identificadores construidos usando la definición anterior son válidos.
Existen una serie de palabras que tienen un significado especial y que, por tanto, no pueden ser
usadas como identificadores. Se trata de las palabras reservadas y los identificadores
predefinidos.
La lista de palabras reservadas en TopSpeed Modula-2 es la siguiente:
AND FORWARD PROCEDURE
ARRAY FROM QUALIFIED
BEGIN GOTO RECORD
BY IF REPEAT
CASE IMPLEMENTATION RETURN
CONST IMPORT SET
DEFINITION IN THEN
DIV LABEL TO
DO LOOP TYPE
ELSE MOD UNTIL
ELSIF MODULE VAR
END NOT WHILE
EXIT OF WITH
EXPORT OR
FOR POINTER
La lista de identificadores predefinidos en TopSpeed Modula-2 es la siguiente:
ABS FLOAT ODD
ADDRESS HALT ORD
ADR HIGH PROC
BITSET INC REAL
BOOLEAN INCL SHORTADDR
BYTE INTEGER SHORTCARD
CAP LONGCARD SHORTINT
CARDINAL LONGINT SIZE
CHAR LONGREAL TRUE
CHR LONGWORD TRUNC
DEC MAX VAL
DISPOSE MIN VSIZE
EXCL NEW WORD
FALSE NULLPROC
El programador puede redefinir los identificadores predefinidos, aunque esto no es una buena
práctica de programación, ya que confundiría a otros programadores que leyesen el programa y
esperasen el significado habitual. Por otro lado, no es posible redefinir las palabras reservadas.

Informática Facultad de Ciencias (Matemáticas)


12 3.6 Constantes

3.6 Constantes
Una constante es un valor fijo que se utiliza en un programa. El valor debe ser siempre el
mismo en cualquier ejecución del programa.
Algunos ejemplos de constantes son el número de meses del año, el número de días de una
semana y la constante matemática π.
Es posible asociar un nombre a una constante mediante una declaración de constante. Así
podemos asignar al identificador PI el valor 3.141592 mediante la siguiente declaración:
CONST PI = 3.141592;

La declaración se inicia con la palabra reservada CONST, seguida del nombre de la constante
que se está definiendo, el signo igual y por último, el valor asociado. La declaración termina
con un punto y coma.
Una vez definida la constante puede utilizarse en expresiones. Las siguientes expresiones son
equivalentes:
2. * 3.141592 2. * PI

También es posible declarar más de una constante a la vez en un programa. Para ello se
comienza con la palabra reservada CONST y se prosigue con las distintas declaraciones
separadas por punto y coma:
CONST
DIASSEMANA = 7;
MESESANYO = 12;
NUMEROE = 2.718281;
LETRAPUNTO = '.';

Como puede observarse, es habitual definir una constante en cada línea de texto y situar el
comienzo de cada declaración de constante a la misma altura horizontal dentro de la línea. A
esta práctica se llama indentación. No es obligatoria pero si bastante conveniente ya que
mejora la legibilidad del programa.
Cada constante tiene asociado de modo automático un tipo:
DIASSEMANA Tipo INTEGER o CARDINAL
MESESANYO Tipo INTEGER o CARDINAL
NUMEROE Tipo REAL o LONGREAL
LETRAPUNTO Tipo CHAR
Es posible declarar el valor de una constante mediante una expresión siempre que todos los
operandos que intervengan en ésta sean valores constantes y que las operaciones sean
predefinidas del lenguaje:
CONST
RADIO = 5.3;
DIAMETRO = 2.0*RADIO;

La notación BNF para las declaraciones de constantes es la siguiente:

Declaración_de_Constantes ::= CONST { Asociación_de_Constante ; }


Asociación_de_Constante ::= Identificador = Expresión _Constante
Expresión_Constante ::= ...

Definiremos qué es una expresión constante en temas posteriores.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 13

3.7 Variables
El concepto de variable en programación es distinto al concepto de variable algebraica. Las
variables algebraicas representan valores de modo que una vez asociada la variable a un valor
determinado dicho valor no cambia.
❏ Ejemplo: Sea la siguiente ecuación:
2x = 1
el valor asociado a la variable x en este caso es x = ½.

En programación el concepto de variable es distinto y está asociado a la memoria del
ordenador. La memoria del ordenador mantiene, además de las instrucciones del programa, una
serie de datos. Una vez que el programa almacena un dato en una de las posiciones de la
memoria su valor se mantiene. Sin embargo, es posible modificar el valor cuantas veces se
desee.
Una variable representa un valor almacenado en la memoria del ordenador, el cual puede ser
usado tantas veces como se desee. El valor de la variable puede ser modificado en cualquier
momento, y será el nuevo valor el que estará almacenado en ella a partir de entonces.
Podemos ver una variable como una caja en la cual es posible almacenar un único valor en un
momento dado. Es posible en todo momento consultar cuál es el valor almacenado en la caja.
También es posible almacenar un nuevo valor en la caja de modo que el valor antiguo se pierde
y el valor de la caja pasa a ser desde ese momento el nuevo valor almacenado.
Las variables de un programa se designan mediante nombres o identificadores. El identificador
de una variable representa el valor almacenado en dicha variable. El programador puede elegir
los nombres más apropiados para las variables que utilice en su programa.

3.7.1 Declaración de variables


En un programa en Modula-2 es necesario declarar cada una de las variables que se utilice. Para
cada variable usada es necesario especificar su nombre y el tipo de valores que puede tomar.
Esto quiere decir que si una variable se declara con tipo INTEGER, por ejemplo, podrá
almacenar en un momento un único valor entero (un 3, un –20 ó 15000) pero no podrá
almacenar un valor de otro tipo (CHAR o REAL, por ejemplo).
❏ Ejemplo: Para usar una variable cuyo nombre sea Edad y que almacene un valor de tipo
entero es necesario declararla del siguiente modo:
VAR Edad : INTEGER;


Como puede observarse, una declaración de variable consta de la palabra reservada VAR seguida
del nombre de la variable, un signo de dos puntos y el nombre del tipo de valor que puede
tomar la variable. La declaración termina con un punto y coma.
Es posible declarar más de una variable usando una única palabra VAR:
VAR Dia : INTEGER;
Mes : INTEGER;
Anyo : INTEGER;
Letra : CHAR;

Informática Facultad de Ciencias (Matemáticas)


14 3.7 Variables

Si varias variables tienen el mismo tipo pueden declararse conjuntamente escribiendo sus
nombres seguidos, separados por comas. El ejemplo anterior puede escribirse de forma
abreviada:
VAR Dia, Mes, Anyo : INTEGER;
Letra : CHAR;

La descripción BNF de la declaración de variables es la siguiente:

Declaración_de_Variables ::= VAR { Lista_de_Variables ; }


Lista_de_Variables ::= Lista_de_Identificadores : Tipo
Lista_de_Identificadores ::= Identificador { , Identificador }
Tipo ::= INTEGER | CARDINAL | REAL | LONGREAL | CHAR | ...

La definición de Tipo se completará en temas posteriores.

3.7.2 Sentencia de asignación


La forma de conseguir que una variable guarde un nuevo valor es mediante la sentencia de
asignación. Mediante esta sentencia es posible:
• Inicializar una variable: una variable declarada contiene un valor indeterminado hasta que
explícitamente se le da uno. Al hecho de dar un valor inicial a una variable se le llama
inicialización de la variable.
• Modificar el valor que tenga: es posible modificar el valor que tenga una variable de modo
que desde ese momento pase a contener un nuevo valor.
La forma de la sentencia de asignación es la siguiente:

Sentencia_de_Asignación ::= Nombre_de_Variable := Expresión


Nombre_de_Variable ::= Identificador
Expresión ::= ...

Una sentencia de asignación consta del nombre de la variable que se quiere modificar, el
operador de asignación (los caracteres dos puntos e igual seguidos y sin espacios entre ellos) y
una expresión que indica el nuevo valor de la variable.
El modo en el cual se ejecuta una sentencia de asignación es el siguiente:
1) Se calcula el resultado correspondiente a la expresión que aparece a la derecha del
operador de asignación.
2) Se almacena el valor calculado en la variable cuyo nombre aparece a la izquierda del
operador de asignación.
Para que una sentencia de asignación sea correcta es imprescindible que el tipo de la expresión
a la derecha del operador de asignación sea compatible con el tipo declarado para la variable
cuyo nombre aparece a la izquierda. Suponiendo las siguientes declaraciones de variables:
VAR Num1 : INTEGER
Num2 : CARDINAL;
Num3 : REAL;
Letra1, Letra2 : CHAR;

son válidas las siguientes sentencias de asignación:


Num1 := -10;
José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA
Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 15

Num2 := 32+24;
Num3 := 1.5E2;
Letra1 := ‘1’;
Letra2 := ‘Ñ’;

NOTA: En Modula-2 es necesario separar las distintas instrucciones del programa con el
símbolo punto y coma (;). Además es habitual escribir cada instrucción en una línea. Esto
último no es obligatorio.
Sin embargo, las siguientes asignaciones no son válidas:
Num1 := 40000; El valor máximo para un dato INTEGER es 32767
Num2 := -20; Un dato CARDINAL no puede tomar valores negativos.
Num3 := 10; La constante 10 es un entero y Num3 es REAL;
Letra1 := 1; Letra1 debe almacenar un carácter, no un número
Letra2 := ‘ab’; Letra2 puede almacenar un único carácter
Si intervienen variables o constantes en la expresión a la derecha de una sentencia de
asignación, se usará el valor que tengan en dicho momento. Por ejemplo la siguiente secuencia
de asignaciones
CONST
DIASSEMANA = 7;
VAR
a, b, c : INTEGER;

a := DIASSEMANA;
b := 2 * a;
c := a + b;

dejará como valores almacenados en las variables a, b y c los valores 7, 14 y 21


respectivamente.
Un caso que requiere cierta atención es aquél en el que la variable que está siendo modificada
forma parte de la expresión. Por ejemplo:
a := DIASSEMANA;
a := a + 1;

A la hora de ejecutar la segunda sentencia de asignación se siguen los pasos que vimos al
principio de este apartado:
1) Se calcula el valor de la expresión a la derecha del operador de asignación: a + 1 = 7 +
1=8
2) Se almacena dicho valor en la variable que aparece a la izquierda: el nuevo valor de la
variable a pasa a ser 8.
Como puede observarse, la sentencia a := a + 1 tiene como resultado que la variable a vea su
valor incrementado en una unidad. También es posible decrementar el valor de una variable en
un unidad (a := a – 1), o en cualquier número de unidades (a := a + 20 hace que el valor
almacenado en la variable a aumente en veinte unidades).
Dado que es muy habitual incrementar y decrementar el valor de variables Modula-2 cuenta
con dos instrucciones para ello INC y DEC:
INC(x) ≡ x := x + 1
DEC(x) ≡ x := x + 1
INC(x, 10) ≡ x := x + 10

Informática Facultad de Ciencias (Matemáticas)


16 3.8 Operaciones de Entrada y Salida

DEC(x, 10) ≡ x := x - 10
Veamos un ejemplo de cómo van modificándose los valores de las variables durante la
ejecución de un pequeño programa:
VAR Posi : CARDINAL;
Dato : INTEGER;
EjeX, EjeY : REAL;

EjeX EJeY Dato Posi Sentencias


? ? ? ? Ejex := 34.89;
34.89 ? ? ? Dato := 67;
34.89 ? 67 ? Posi := TRUNC(EjeX) + Dato;
34.89 ? 67 101 Dato := TRUNC(EjeY) + Posi;
34.89 ? ? 101 EjeY := EjeX + REAL(Posi);
34.89 135.89 ? 101 Dato := Posi DIV 5;
34.89 135.89 20 101

3.8 Operaciones de Entrada y Salida


La mayoría de los programas necesitan una serie de datos. Estos datos suelen ser introducidos
por una persona usando alguna unidad de entrada (generalmente el teclado del ordenador). Se
necesitan instrucciones que permitan a un programa escrito en Modula-2 pedir un dato desde el
teclado (o desde otra unidad de entrada) y guardarlo en una variable. A este tipo de
instrucciones se les llama operaciones de entrada.
De un modo similar, la mayoría de los programas suelen calcular unos resultados que hay que
emitir al exterior a través de una unidad de salida (generalmente la pantalla del ordenador).
Necesitamos instrucciones que permitan a un programa escrito en Modula-2 enviar resultados
al exterior. A este tipo de instrucciones se les denomina operaciones de salida.
En Módula-2 las instrucciones para efectuar E/S se encuentran en módulos de biblioteca, es
decir, módulos preprogramados disponibles para el programador. La biblioteca donde están
implementadas estas instrucciones depende del compilador de Modula-2 que se utilice. En el
caso de Top Speed Modula-2 están contenidas en la biblioteca IO (Input-Output) y se describen
a continuación.

3.8.1 Operaciones de Salida


Las principales operaciones de salida definidas en el modulo de biblioteca IO son las
siguientes:
• WrChar(c): escribe en pantalla el valor de c que debe ser una expresión de tipo CHAR.
El siguiente trozo de programa:
WrChar(‘h’);
WrChar(‘o’);
WrChar(‘l’);
WrChar(‘a’);
WrChar(CHR(ORD(‘!’)));

producirá la siguiente salida en la pantalla del ordenador:


hola!

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 17

• WrLn: hace que el resultado correspondiente a la próxima operación de salida se muestre en


la siguiente línea de pantalla.
El siguiente trozo de programa:
WrChar(‘h’); WrLn;
WrChar(‘o’); WrLn;
WrChar(‘l’); WrLn;
WrChar(‘a’); WrLn;
WrChar(CHR(ORD(‘!’)));

producirá la siguiente salida en la pantalla del ordenador:


h
o
l
a
!

• WrStr(s): escribe la cadena de caracteres s en pantalla.


El siguiente trozo de programa:
WrStr(‘holab¿qué hay?’); WrLn;
WrStr(‘entre “comillas”’);

producirá la siguiente salida en la pantalla del ordenador:


holab¿qué hay?
entre “comillas”

• WrInt(i,a): escribe en pantalla el valor de i que debe ser una expresión de tipo
INTEGER. El valor de a indica el número de posiciones reservadas para escribir i en la
pantalla. En caso de que i requiera un número de posiciones menor del reservado, las
posiciones sobrantes se rellenarán con espacios en blanco. Si el número de posiciones
indicado por a es menor que el número de caracteres necesarios para imprimir i, se
imprimirá i ignorando lo indicado por a.
El siguiente trozo de programa:
WrInt(123,5); WrLn;
WrInt(45,1); WrLn;
WrInt(67,0); WrLn;
WrInt(-89,6); WrLn;
WrInt(1000+500, 6);

producirá la siguiente salida en la pantalla del ordenador:


bb123
45
67
bbb-89
bb1500

Es habitual utilizar un ancho cero, de este modo el valor ocupará tantas posiciones en
pantalla como necesite.

Informática Facultad de Ciencias (Matemáticas)


18 3.8 Operaciones de Entrada y Salida

• WrCard(c,a): idéntico al anterior, pero el valor c debe ser de tipo CARDINAL.


• WrReal(r,p,a): Escribe el valor de la expresión r, que debe ser de tipo REAL, en
pantalla. Los números reales siempre se muestran usando notación científica y con un único
dígito en la parte entera. El significado de a es el mismo que en WrInt. Se mostrarán p-1
cifras en la parte fraccionaria.
El siguiente trozo de programa:
WrReal(1.2345, 1, 10); WrLn;
WrReal(1.2345, 2, 10); WrLn;
WrReal(12.345, 5, 0); WrLn;
WrReal(12.345, 6, 0); WrLn;

producirá la siguiente salida en la pantalla del ordenador:


bbbbb1.E+0
bbbb1.2E+0
1.2345E+1
1.23450E+1

• WrLngReal(r,p,a): igual al anterior pero r debe ser una expresión de tipo LONGREAL.
NOTA: El símbolo b representa un espacio en blanco.

3.8.2 Operaciones de Entrada


Las principales operaciones de entrada definidas en el módulo de biblioteca IO son las
siguientes:
• RdChar(): lee un dato de tipo CHAR del teclado.
• RdInt(): lee un número de tipo INTEGER del teclado.
• RdCard(): lee un número de tipo CARDINAL del teclado.
• RdReal(): lee un número de tipo REAL del teclado.
• RdLngReal(): lee un número de tipo LONGREAL del teclado.
Estas instrucciones se usan de la misma forma que un valor dentro de programa, por lo que no
pueden aparecer como instrucciones aisladas dentro del mismo (cosa que ocurría con las de
salida vistas anteriormente) sino sólo formando parte de una expresión.

El modo usual de utilizar estas operaciones es mediante una sentencia de asignación, en cuya
parte izquierda aparece el nombre de la variable en la cual se quiere guardar el valor leído del
teclado. En la parte derecha de la sentencia de asignación aparece la operación de entrada. El
tipo de la variable en la parte izquierda debe coincidir con el tipo de dato que lee la operación.
Tras cada llamada a una de estas funciones, el programa se detiene a la espera de que el usuario
introduzca un valor. Cuando el usuario introduce el dato y pulsa la tecla <ENTER>, el
programa continúa ejecutándose y el valor es asignado a la variable en la parte izquierda de la
sentencia de asignación.
Tras cada una de estas operaciones de entrada es conveniente ejecutar la operación RdLn (que
debe importarse también del módulo de biblioteca IO) para evitar complicaciones.
El siguiente trozo de programa (suponiendo que la variable Num está declarada con tipo
INTEGER) lee un valor entero del teclado, lo almacena en la variable Num, suma 10 al valor
leído y muestra el resultado por pantalla:
José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA
Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 19

Num := RdInt();
RdLn;
Num := Num + 10;
WrInt(Num, 0);

Al ejecutar el programa anterior, el ordenador se detiene al llegar a la instrucción RdInt() y


muestra un cursor parpadeante para indicar al usuario que está a la espera de que introduzca un
dato:
_

En este punto el usuario debe introducir el dato y pulsar la tecla <ENTER>:


120_

Al pulsar la tecla <ENTER> el dato 120 es asignado a la variable Num.. Además, el cursor pasa
a la siguiente línea de pantalla de modo que la próxima operación de salida comenzará en dicha
línea:
120
_

La asignación Num := Num + 10 h ace que el valor de la variable Num pase a ser 130. Por
último, la operación WrInt(Num,0) muestra el valor de la variable Num con el ancho que sea
necesario:
120
130

Es normal preceder las operaciones de entrada con operaciones de salida que indiquen al
usuario qué dato debe introducir. El siguiente programa resulta más claro para el usuario:
WrStr(“Introduzca un número entero: “);
Num := RdInt();
RdLn;
Num := Num + 10;
WrStr(“El número introducido más diez es: “);
WrInt(Num, 0);

Al ejecutar este último programa, el ordenador muestra el mensaje “Introduzca un número


entero: “ y un cursor parpadeante, lo cual indica que está a la espera de que el usuario
introduzca un dato entero:
Introduzca un número entero: _

Una vez que el usuario introduce el número y pulsa la tecla <ENTER> el cursor pasa a la
siguiente línea:
Introduzca un número entero: 120
_

Informática Facultad de Ciencias (Matemáticas)


20 3.9 Estructura general de un programa. Ejemplo

Por último, se incrementa en diez el valor de la variable y ejecutan las dos operaciones de
salida finales:
Introduzca un número entero: 120
El número introducido más diez es: 130

3.9 Estructura general de un programa. Ejemplo


Con los elementos introducidos en este tema es posible escribir programas completos en
Modula-2. El siguiente ejemplo muestra un programa que lee el radio de un círculo desde el
teclado, calcula el área de dicho círculo y lo escribe por pantalla:
MODULE Circulo;
FROM IO IMPORT WrStr, RdReal, RdLn, WrReal;
CONST
PI = 3.1416; (* El número PI *)
PRECISION = 5; (* Nº de dígitos al escribir reales *)
ANCHO = 0; (* Calcular ancho necesario automáticamente *)
VAR
Radio, Area : REAL;
BEGIN
WrStr(‘Introduce el radio: ‘);
Radio := RdReal();
RdLn;
Area := PI*Radio*Radio;
WrStr(‘El área del círculo es ‘);
WrReal(Area,PRECISION,ANCHO)
END Circulo.

Podemos distinguir las siguientes partes dentro del programa:


• Cabecera: todo programa debe comenzar con la palabra reservada MODULE seguida de
un identificador, que indica el nombre del programa, y un signo punto y coma. En el
presente ejemplo se ha elegido como nombre del programa el identificador Circulo. El
programa debe finalizar con la palabra reservada END seguida del nombre del programa (el
mismo identificador usado en la cabecera) y un punto.
• Listas de importación: a continuación es necesario enumerar los módulos de biblioteca
usados por el programa, especificando cada una de las operaciones utilizadas. Los módulos
de bibliotecas contienen elementos que se utilizan con frecuencia en el desarrollo de
distintos programas, por lo que están disponibles en el lenguaje, y por lo tanto, no es
necesario programarlos. Para utilizarlos hay que indicarlo en la lista de importación. Esto
se puede hacer de dos formas:
1. Forma implícita:
IMPORT NombreBiblioteca;

En este caso se puede utilizar cualquier elemento definido en la biblioteca refiriéndonos a


él mediante su nombre precedido por el del módulo.
2. Forma explícita:
FROM NombreBiblioteca IMPORTListaElementos;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 3. Introducción al lenguaje de programación Modula-2 21

En este caso sólo se pueden utilizar los elementos especificados en ListaElementos


refiriéndonos a él sólo por su nombre. Este es el caso del ejemplo.
Cada compilador de Módula-2 tiene sus propias bibliotecas y define las características de
los procedimientos que contiene. Este es un aspecto a revisar siempre que, aun usando el
mismo lenguaje, cambiemos de compilador.
• Declaraciones: en esta sección se declaran las distintas constantes y variables que son
utilizadas en el programa. El programa ejemplo usa tres valores constantes (PI, PRECISION
y ANCHO) y dos variables de tipo REAL (Radio y Area).
• Parte Ejecutiva: comienza con la palabra reservada BEGIN y en ella aparecen las
instrucciones que constituyen el programa, separadas entre sí con el signo punto y coma.
Es posible incluir en cualquier punto del programa un comentario. Un comentario es una
secuencia de caracteres que es ignorada por el compilador. Se usan para documentar el
programa, de manera que aunque no contribuyan a resolver el problema, sí ayudan a mejorar la
comprensión del programa. Se insertan en el programa escribiendo un texto entre los símbolos
(* y *).
Usando la notación BNF, la estructura de un programa en Modula-2 es la siguiente:

Modulo_de_Programa ::= Cabecera_de_Programa


{ Lista_de_Importación }
{ Declaraciones }
Parte_Ejecutiva
Fin_de_Programa
Cabecera_de_Programa ::= MODULE Nombre_de_Programa ;
Lista_de_Importación ::= FROM Nombre_de_Biblioteca IMPORT Lista_de_Operaciones ; |
IMPORT Nombre_de_Biblioteca ;
Lista_de_Operaciones ::= Nombre_de_Operación { , Nombre_de_Operación }
Declaraciones ::= Declaración_de_Constante | Declaración_de_Variable
Parte_Ejecutiva ::= BEGIN Secuencia_de_Sentencias
Secuencia_de_Sentencias ::= Sentencia { ; Sentencia }
Sentencia ::= Sentencia_de_Asignación | ...
Fin_de_Programa ::= END Nombre_de_Programa .
Nombre_de_Programa ::= Identificador
Nombre_de_Biblioteca ::= Identificador
Nombre_de_Operación ::= Identificador

Podemos observar que tanto la lista de importación como las declaraciones pueden aparecer
cero, una o más veces en un programa. Completaremos la definición de sentencia a lo largo del
curso.

Informática Facultad de Ciencias (Matemáticas)


Relación de problemas. Tema 3. 1

Relación de Problemas (Tema 3)


1. Escribir un programa que lea desde el teclado dos valores de tipo INTEGER y escriba en
pantalla su suma, su diferencia, su producto, su cociente y su resto.
2. Escribir un programa que pida una letra desde el teclado, la pase a mayúscula e imprima
el resultado por pantalla.
3. Escribir un programa que lea el radio y la altura de un cilindro desde el teclado y escriba
por pantalla el área y volumen correspondiente.
NOTA: El área y volumen del cilindro de altura h y radio R es:
Area = 2πR2+2πRh
Volumen = πR2h
4. Escribir un programa que lea desde el teclado una cantidad de segundos y muestre por
pantalla el número de horas, minutos y segundos equivalentes.
5. Escribir un programa que lea un carácter del teclado y escriba por pantalla su código
dentro de la tabla ASCII.
6. a) ¿Qué ocurre al ejecutar el siguiente programa?
MODULE Prueba;
FROM IO IMPORT WrInt;
VAR
Num : INTEGER;
BEGIN
Num := MAX(INTEGER);
Num := Num + 10 – 100;
WrInt (Num, 0)
END Prueba.

b) ¿Cómo puede solucionarse el problema?


c) ¿Qué ocurre si cambiamos el programa por el siguiente?
MODULE Prueba;
FROM IO IMPORT WrCard;
VAR
Num : CARDINAL;
BEGIN
Num := MAX(INTEGER);
Num := Num + 10 – 100;
WrCard (Num, 0)
END Prueba.

7. Escribe un programa que lea del teclado la longitud de los dos catetos de un triángulo
rectángulo, calcule la longitud de la hipotenusa y la escriba por pantalla.
8. Escribe un programa que lea la longitud del lado de un cuadrado y escriba en pantalla su
área y su perímetro.
9. Escribe un programa que lea un carácter del teclado y escriba en pantalla el carácter que
le sigue en el código ASCII.

Informática Facultad de Ciencias (Matemáticas)


2 3.9 Estructura general de un programa. Ejemplo

10. Encuentra los errores en el siguiente programa:


MODULE Erroneo;
FROM IO IMPORT WrInt;
CONST
PRECISION = 2;
ANCHO = 0;
VAR
Num1 : INTEGER;
Num2 : CARDINAL;
Num3 : REAL;
BEGIN
Num1 := 10;
Num2 := -100;
Num3 := 13.;
Letra := ‘A’
Num3 := Num3 + 1;
WrStr(“Valor número 1: “);
WrInt(Num1, ANCHO);
WrLn;
WrStr(“Valor número 2: “);
WrCard(Num2, ANCHO);
WrLn;
WrStr(Valor número 3:);
WrReal(Num3, PRECISION, ANCHO);
WrLn;
WrStr(“Valor letra: “);
WrChar(Letra);
WrLn
END Erroneo.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 4. Diseño de Algoritmos. Estructuras de selección 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de Facultad de Ciencias (Matemáticas)
la Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 4. Diseño de Algoritmos. Estructuras de


selección
4.1 Composición secuencial de instrucciones........................................................... 2
4.2 Selección entre acciones alternativas ................................................................ .2
4.2.1 El tipo BOOLEAN en Modula-2 ................................................................ .....3
4.2.2 Expresiones booleanas................................................................ ................. 3
4.2.2.1 Operadores relacionales ................................................................ .........3
4.2.2.2 Operadores booleanos ................................................................ ............4
4.2.2.3 Evaluación en cortocircuito................................................................ ......6
4.2.3 Sentencias de selección en Modula-2........................................................... 6
4.2.3.1 Sentencia IF THEN ELSIF ELSE............................................................. 6
4.2.3.2 Sentencia CASE................................................................ .................... 10

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.
• Fundamentos de programación. L. Joyanes. McGraw-Hill

Informática. Facultad de Ciencias (Matemáticas)


2 4.1 Composición secuencial de instrucciones

4.1 Composición secuencial de instrucciones


La forma más simple de componer instrucciones en un lenguaje de programación es la
secuencia: se ejecutan las distintas acciones una tras otra según su orden físico dentro del
programa. Podemos representar esta forma de composición con el siguiente diagrama de flujo:

Instrucción A

Instrucción B

Figura 1. Composición secuencial de A y B

En Modula-2 la composición de dos instrucciones se consigue escribiendo una tras otra,


separándolas por un carácter punto y coma. Por ejemplo, el siguiente programa compone dos
instrucciones de escritura secuencialmente:
MODULE Secuencia;
FROM IO IMPORT WrStr;
BEGIN
WrStr (“ A “);
WrStr (“ B “)
END Secuencia.

La sintaxis BNF de la estructura de secuencia en Modula-2 es:

Secuencia_de_Sentencias ::= Sentencia { ; Sentencia }

4.2 Selección entre acciones alternativas


A la hora de describir algoritmos, puede ser necesario especificar dos o más caminos
alternativos a seguir en función de cierta condición. La estructura de selección permite ejecutar
una acción u otra, dependiendo de una determinada condición que se analiza a la entrada de la
estructura. El diagrama correspondiente a esta estructura es:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 4. Diseño de Algoritmos. Estructuras de selección 3

Sí No
Condición

Acción A Acción B

Figura 2. Estructura de selección

Para ejecutar una estructura de selección, se evalúa primero la condición. Si ésta es cierta se
ejecutará la acción A. Si la condición es falsa se ejecutará la acción B. Veremos posteriormente
que ésta es sólo una de las distintas instrucciones de selección disponibles en Modula-2.
La condición debe ser una expresión booleana: expresión que puede ser cierta o falsa.
Estudiamos las expresiones booleanas en los siguientes puntos.

4.2.1 El tipo BOOLEAN en Modula-2


El tipo BOOLEAN es el tipo de aquellas variables que sólo pueden tomar dos valores:
VERDADERO o FALSO, (TRUE y FALSE en Modula-2).
Un ejemplo de uso de datos booleanos puede verse en el siguiente programa, que asigna a la
variable EsAlto el valor VERDADERO y a la variable EsRubio FALSO:
MODULE Booleano;
VAR
EsAlto, EsRubio : BOOLEAN;
BEGIN
EsAlto := TRUE;
EsRubio := FALSE
END Booleano.

Existe una función predefinida que devuelve un valor de tipo booleano. Se trata de la función
ODD. Esta función toma como argumento un número no REAL (de tipo INTEGER o
CARDINAL) y devuelve el valor TRUE si el número es impar o FALSE si es par. No existe una
función predefinida para ver si un número es par, pero puede usarse la expresión NOT ODD.

4.2.2 Expresiones booleanas


Una de las formas de construir expresiones booleanas es mediante los operadores relacionales.
4.2.2.1 Operadores relacionales
Los operadores relacionales son operadores binarios que permiten comparar dos valores del
mismo tipo. Son binarios porque tienen dos argumentos. Los operadores relacionales en
Modula-2 son:

Informática. Facultad de Ciencias (Matemáticas)


4 4.2 Selección entre acciones alternativas

> mayor que


< menor que
= igual a
>= mayor o igual que
<= menor o igual que
<> # distinto a
Figura 1. Operadores relacionales

Los símbolos con dos caracteres deben escribirse en ese orden y sin separadores entre ellos. El
operador distinto a puede escribirse de dos formas.
El resultado de una operación relacional es VERDADERO o FALSO, o sea es de tipo
BOOLEAN. Así:
2.3 < 8.5 ≡ TRUE
5 < 3 ≡ FALSE
n >= 4 ≡ Es correcta sólo si n es de tipo INTEGER o CARDINAL, y el resultado
depende del valor de n.
‘a’ < ‘d’ ≡ TRUE
‘A’ < 25 ≡ Error, ya que ‘A’ tiene tipo CHAR y 25 tiene tipo INTEGER o
CARDINAL
n*m <= x+8 ≡ Es correcta si n,m,x tienen tipo INTEGER o CARDINAL y el resultado
depende de los valores de n, m y x.
Si EsPar es una variable de tipo BOOLEAN y N de tipo INTEGER o CARDINAL podemos
escribir sentencias como:
EsPar := (N MOD 2 = 0);

ya que (N MOD 2 = 0) es una expresión booleana, de modo que tras la ejecución de esta
sentencia EsPar contendrá TRUE si N es un número par y FALSE en otro caso.
4.2.2.2 Operadores booleanos
Podemos construir expresiones booleanas más complicadas con los operadores booleanos.
Estos son:
AND & Conjunción lógica
OR Disyunción lógica
NOT ~ Negación lógica
Figura 2. Operadores booleanos

Los dos primeros operadores son binarios, mientras que el último es unario (tiene un único
argumento). A diferencia de los operadores relacionales, los tipos de los argumentos deben ser
expresiones booleanas (no son válidas expresiones de cualquier tipo).
La expresión A AND B es cierta si tanto A como B son ciertas. La expresión A OR B es cierta si
A es cierta o B es cierta o si ambas son ciertas. Por último NOT A es cierta si y sólo si A es falsa.
Podemos expresar esto mediante la siguiente tabla de verdad:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 4. Diseño de Algoritmos. Estructuras de selección 5

A B A AND B A OR B NOT A
TRUE TRUE TRUE TRUE FALSE
TRUE FALSE FALSE TRUE
FALSE TRUE FALSE TRUE TRUE
FALSE FALSE FALSE FALSE
Figura 3. Tabla de verdad de los operadores booleanos

Podemos construir expresiones complejas combinando estos operadores con los relacionales:
(x>=5)AND(x<=10) ≡ TRUE si x ∈ [5,10] (x ha de tener tipo INTEGER o
CARDINAL)
EsAlto OR EsRubio ≡ TRUE si es alto o rubio (EsAlto y EsRubio deben tener
tipo BOOLEAN)
((x>=5)AND(x<=10)) ≡ TRUE si x ∈ [5,10] o x ∈ [20,25] (x debe tener tipo
OR INTEGER o CARDINAL)
((x>=20)AND(x<=25))
NOT EsAlto ≡ TRUE si no es alto (EsAlto debe tener tipo BOOLEAN)
Es necesario manejar correctamente estos operadores y conocer algunas equivalencias lógicas
que permiten simplificar estas expresiones. Así, por ejemplo:
NOT (A = B) ≡ A <> B
NOT (A AND B) ≡ (NOT A) OR (NOT B)
NOT (A OR B) ≡ (NOT A) AND (NOT B)

Estas igualdades se pueden demostrar mediante una tabla de verdad. En estas tablas se
evalúan las expresiones con todas las combinaciones de argumentos posibles y se comprueba
que siempre se obtiene el mismo resultado. Por ejemplo, para demostrar la segunda
equivalencia la tabla de verdad es:
Α Β Α & Β ∼(Α & Β) (∼Α) (∼Β) (∼Α)OR(∼Β)
TRUE TRUE TRUE FALSE FALSE FALSE FALSE
TRUE FALSE FALSE TRUE FALSE TRUE TRUE
FALSE TRUE FALSE TRUE TRUE FALSE TRUE
FALSE FALSE FALSE TRUE TRUE TRUE TRUE
Figura 4. Tabla de verdad, para equivalencia de expresiones

A la hora de construir una expresión booleana debemos de tener en cuenta la precedencia de los
operadores. Así la expresión NOT X AND Y, con X = FALSE e Y = FALSE, tendría valor TRUE si
primero actúa el operador AND y FALSE si primero actúa NOT.
Para evitar este tipo de ambigüedades se establece una prioridad entre los operadores booleanos
(igual que ocurre con los operadores aritméticos +, -, ...). En Modula-2, si no utilizamos
paréntesis, el orden de evaluación es el siguiente:
1º Operador de negación: NOT ∼ + - (unarios)
2º Operadores de multiplicación: * / DIV MOD AND &
3º Operadores de adición: + - OR
4º Operadores de relacionales: < > = >= <= <> #

Informática. Facultad de Ciencias (Matemáticas)


6 4.2 Selección entre acciones alternativas

Dentro del mismo nivel las expresiones se evalúan en el orden en que están escritas, es decir, de
izquierda a derecha.
El ejemplo citado anteriormente se corresponde con (NOT X) AND Y. Si hubiésemos querido
expresar la otra posibilidad deberíamos haber usado paréntesis: NOT (X AND Y). Es buena
costumbre usar paréntesis, aunque no sean necesarios, para aclarar el significado de
expresiones complejas.
Por último, veamos la sintaxis para las expresiones (booleanas y aritméticas) en Modula-2 (por
ahora):

Expresión ::= Expresión_simple [ Operador_Comparador Expresión_Simple]


Expresión_Simple ::= [+|- ] Termino {Operador_Sumador Término}
Término ::= Factor {Operador_Multiplicador Factor}

Factor ::= Identificador_de_Variable | Identificador_de_Constante |

Valor_Constante | (Expresión) | ( NOT | ~) Factor


Operador_Comparador ::= > | < | = | >= | <= | <> | #

Operador_Sumador ::= + | - | OR

Operador_Multiplicador ::= * | / | DIV | MOD | AND | &

Valor_Constante ::= Valor_Entero | Valor_Real | Valor_Carácter | Valor_Cadena | Valor_Bool

Valor_Bool ::= TRUE | FALSE

4.2.2.3 Evaluación en cortocircuito


El modo en que Modula-2 evalúa las expresiones booleanas se llama evaluación en
cortocircuito. En Modula-2 no siempre se evalúa la expresión completa, sino sólo aquella parte
que es necesaria para conocer el valor. Así, si en una expresión conjunción (AND) el primer
término es FALSE, no es necesario evaluar el resto de la expresión, ya que el resultado global
de la expresión será FALSE. Por ejemplo dada la siguiente expresión:
(b > 10) AND (a MOD b = 0)

si b es menor o igual que 10, no se evaluará la segunda parte de la expresión. Lo mismo ocurre
si la expresión global es una disyunción (OR) y la primera subexpresiones es TRUE. En este
caso, la expresión global tendrá como resultado TRUE sin que se evalúe el resto de la
expresión.
Debido a esto, la propiedad conmutativa no se cumple en general al utilizar operadores
booleanos. Por ejemplo, la expresión (10 > 7) OR (100 DIV 0 = 3) será evaluada a TRUE,
ya que, al evaluarse la primera parte de la disyunción (10 > 7) a TRUE, la segunda parte (100
DIV 0 = 3) no es evaluada. Sin embargo, la evaluación de la expresión (100 DIV 0 = 3)
OR (10 > 7) hace que se produzca un error, ya que al intentar evaluar la primera parte (100
DIV 0 = 3) se produce un error debido a la división por cero.

4.2.3 Sentencias de selección en Modula-2


Existen dos sentencias de selección en Modula-2: la binaria y la múltiple. Pasamos a estudiar
cada una de ellas.
4.2.3.1 Sentencia IF THEN ELSIF ELSE
En Modula-2 la sentencia de selección binaria puede adoptar la siguiente forma:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 4. Diseño de Algoritmos. Estructuras de selección 7

IF Condición THEN
Acción A
ELSE
Acción B
END

La ejecución de esta sentencia consiste en evaluar primero la condición. Si ésta es verdadera


(TRUE), se ejecuta la acción A. En otro caso, si la condición fuese falsa (FALSE), se ejecutaría
la acción B. Siempre se ejecuta alguna acción, o bien A o bien B, pero nunca ambas. El
diagrama de flujo correspondiente es:

Sí No
Condición

Acción A Acción B

Figura 5. Selección binaria

La condición debe ser una expresión de tipo BOOLEAN. Las palabras IF THEN ELSE END
separaran las distintas partes de la sentencia (condición, acción A y acción B). Un ejemplo de
uso de la sentencia es el siguiente:
IF ODD(N) THEN
WrStr (“El nº es impar”)
ELSE
WrStr (“El nº es par”)
END

A veces no es necesario hacer nada si la condición no se cumple. En este caso la sentencia de


selección puede carecer de la parte ELSE:
IF Condición THEN
Acción
END

El diagrama de flujo en este caso es:

Informática. Facultad de Ciencias (Matemáticas)


8 4.2 Selección entre acciones alternativas

Sí No
Condición

Acción

Figura 6. Selección simplificada

No hay restricciones sobre el tipo de sentencias que puede aparecer en una rama de una
sentencia de selección. Puede, por ejemplo, aparecer una secuencia de sentencias
IF x < 0 THEN
x := -x;
WrStr (“Signo cambiado”)
ELSE
WrStr (“Signo sin cambiar”)
END

También puede aparecer otra sentencia de selección. A esto es lo que se llama anidamiento
de sentencias de selección. Un ejemplo de anidamiento es el siguiente trozo de programa,
que escribe la calificación correspondiente a la nota contenida en la variable REAL Nota:
IF Nota < 5.0 THEN
WrStr (“Suspenso”)
ELSE
IF Nota < 7.0 THEN
WrStr (“Aprobado”)
ELSE
IF Nota < 9.0 THEN
WrStr (“Notable”)
ELSE
IF Nota < 10.0 THEN
WrStr (“Sobresaliente”)
ELSE
WrStr (“Matrícula”)
END
END
END
END

A la estructura anterior también se llama selección en cascada. El diagrama de flujo


correspondiente al ejemplo es:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 4. Diseño de Algoritmos. Estructuras de selección 9

Sí No
Nota < 5.0

Sí No
Nota < 7.0
Escribir
Suspenso

Sí No
Nota < 9.0
Escribir
Aprobado

Sí No
Nota < 10.0
Escribir Notable

Escribir Escribir
Sobresaliente Matrícula

Figura 7. Selección en cascada

Este tipo de situaciones es habitual, por lo que Modula-2 posee una sintaxis más simple para
estos casos. Se usa para ello la palabra reservada ELSIF. El ejemplo anterior se puede escribir
también del siguiente modo:
IF Nota < 5.0 THEN
WrStr (“Suspenso”)
ELSIF Nota < 7.0 THEN
WrStr (“Aprobado”)
ELSIF Nota < 9.0 THEN
WrStr (“Notable”)
ELSIF Nota < 10.0 THEN
WrStr (“Sobresaliente”)
ELSE
WrStr (“Matrícula”)
END

Obsérvese que el significado de la selección en cascada es distinto al del siguiente código (que
se corresponde con una secuencia de sentencias de selección):
IF Nota < 5.0 THEN
WrStr (“Suspenso”)
END;
IF (Nota >= 5.0) AND (Nota < 7.0) THEN
WrStr (“Aprobado”)
END;
IF (Nota >= 7.0) AND (Nota < 9.0) THEN
WrStr (“Notable”)
END;
IF (Nota >= 9.0) AND (Nota < 10.0) THEN
WrStr (“Sobresaliente”)
END;
IF Nota = 10.0 THEN
WrStr (“Matrícula”)
END;

El diagrama de flujo para éste último trozo de código es:

Informática. Facultad de Ciencias (Matemáticas)


10 4.2 Selección entre acciones alternativas

Sí No
Nota < 5.0

Escribir
Suspenso

Sí No
Nota ∈[5.0,7.0)

Escribir
Aprobado

Sí No
Nota = 10.0

Escribir
Matrícula

Figura 8. Selección NO en cascada

En la selección en cascada, una vez que una condición es cierta no se comprueba ninguna otra.
Aquí siempre se comprueban todas las condiciones. La selección en cascada es más eficiente.
La sintaxis BNF de la sentencia de selección es:

Sentencia_IF ::= IF Expresión THEN Secuencia_de_Sentencias


{ ELSIF Expresión THEN Secuencia_de_Sentencias }
[ ELSE Secuencia_de_Sentencias ]
END

donde Expresión debe ser obligatoriamente de tipo BOOLEAN.


4.2.3.2 Sentencia CASE
La sentencia de selección múltiple se utiliza para ejecutar distintas sentencias en función de los
distintos valores que pueda tomar una expresión. El esquema de esta sentencia es:
CASE Expresión OF
listas de valores : acción|
listas de valores : acción|
ELSE
acción por defecto
END

Para ejecutar esta sentencia, se evalúa primero la expresión. A continuación, se inspeccionan


las distintas listas de valores, hasta encontrar una con un valor que coincida con el resultado de

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 4. Diseño de Algoritmos. Estructuras de selección 11

la expresión. La acción correspondiente a ese valor es ejecutada. Si el resultado de la expresión


no aparece en ninguna de las listas, se ejecuta la acción por defecto.
El siguiente ejemplo muestra el código para un trozo de programa que escribe el nombre del día
de la semana almacenado en la variable Dia (de tipo CARDINAL):
CASE Dia OF
1 : WrStr (“Lunes”) |
2 : WrStr (“Martes”) |
3 : WrStr (“Miercoles”) |
4 : WrStr (“Jueves”) |
5 : WrStr (“Viernes”) |
6 : WrStr (“Sábado”) |
7 : WrStr (“Domingo”)
ELSE
WrStr (“Error: día no válido”)
END;

A cada caso se puede asociar más de un valor. Esto puede hacerse de tres formas:
• Separando distintos valores con comas. (por ejemplo 1,2,3)
• Escribiendo dos valores separados por dos puntos. En este caso se está especificando
un rango de valores. (por ejemplo 1..5)
• Combinando estas dos formas. (por ejemplo 1, 3..4)
La siguiente sentencia escribe si el número almacenado en la variable Dia corresponde a un día
laborable o no:
CASE Dia OF
1..5 : WrStr (“Laborable”) |
6 : WrStr (“Depende”) |
7 : WrStr (“Festivo”)
ELSE
WrStr (“Error: día no vál
ido”)
END;

Algunas reglas relativas a esta sentencia son:


• La alternativa ELSE puede no aparecer, pero si el resultado de la expresión no coincide
con ninguno de los casos, el programa acabará con un error (sin embargo, esto no
ocurre en TopSpeed Modula-2). Si queremos que para ciertos valores no se haga nada,
entonces estos valores se deben de asociar con una acción vacía.
• Un mismo valor nunca puede aparecer en distintas alternativas.
• El tipo de la expresión a evaluar no puede ser REAL. Ha de ser un tipo ordinal.
• Los distintos valores en las alternativas son expresiones constantes (no pueden
intervenir variables)
• El tipo de la expresión y de los distintos valores debe coincidir.
La sintaxis BNF para esta sentencia es:

Informática. Facultad de Ciencias (Matemáticas)


12 4.2 Selección entre acciones alternativas

Sentencia_CASE ::= CASE Expresión OF


Caso { | Caso }
[ ELSE Secuencia_de_sentencias ]
END
Caso ::= Lista_de_Valores : Secuencia_de_Sentencias
Lista_de_Valores ::= Valores { , Valores }
Valores ::= Expresión_Constante [ .. Expresión_Constante ]

La sentencia de selección múltiple es equivalente a la selección en cascada (esta última será la


que debamos usar cuando el tipo de expresión sea REAL):

CASE Expresión OF Valor := Expresión;


v1 : IF Valor = v1 THEN
SentenciaA | SentenciaA
v2..v4 : ELSIF (Valor >= v2) AND
SentenciaB | (Valor <= v4)
v5,v6,v7 : THEN
SentenciaC | SentenciaB

... ELSIF (Valor = v5) OR
ELSE (Valor = v6) OR
SentenciaH (Valor = v7) THEN
END SentenciaC
...
ELSE
SentenciaH
END

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 4) 1

Relación de Problemas (Tema 4)


1. Si la variable valor es de tipo BOOLEAN, ¿cómo se puede escribir de otro modo la
condición o pregunta valor = TRUE?, ¿ y valor = FALSE?.
2. Si la variable cierto es de tipo BOOLEAN, y a, b son de tipo INTEGER, ¿a qué son
equivalentes las siguientes expresiones ?
i) cierto := (a<b) AND (b<a)
ii) cierto := (a<=b) OR (b<=a)
3. Si x e y son variables de tipo BOOLEAN, ¿cuál es la relación entre las siguientes dos
expresiones?:
i) x <> y
ii) (x OR y) AND NOT (x AND Y)
Demuéstralo mediante una tabla de verdad.
4. Si p, q y r son variables booleanas, comprueba las siguientes igualdades usando tablas de
verdad:
i) (p OR r) AND (q OR r) ≡ (p AND q) OR r
ii) (p AND r) OR (q AND r) ≡ (p OR q) AND r
5. Escribe un programa que determine el mayor de tres números enteros.
6. Escribe un programa que sin utilizar sentencias de asignación escriba ordenados de menor
a mayor tres números almacenados en variables de tipo entero.
7. Escribe un programa que dado un número entero introducido por teclado compruebe si es
par o no.
8. Escribe un programa que lea tres números d, m y a que representan una fecha (día, mes y
año) y calcule el día de la semana correspondiente usando la congruencia de Zeller:
día = (700 + (26x - 2) DIV 10 + d + y + y DIV 4 + z DIV 4 - 2z) MOD 7
donde los valores de x, y, z son:
si m ≤ 2 si m ≥ 3
x m + 10 m-2
y (a - 1) MOD 100 a MOD 100
z (a - 1) DIV 100 a DIV 100
El valor de día estará entre 0 y 6, (0 significa Domingo, 1 Lunes, etc. )
NOTA: Este algoritmo es solo válido para el calendario gregoriano, que fue introducido
en distintos países en distintas fechas (el 14 de Septiembre de 1942 en Inglaterra, por
ejemplo).
9. Escribe un programa que determine el menor número de monedas y billetes de curso legal
equivalentes a cierta cantidad de pesetas leídas del teclado.
NOTA: Considera que existen billetes de 10.000, 5.000, 2.000, 1.000 y monedas de 500,
100, 25, 5 y 1 pesetas.
10. Escribe un programa que lea dos letras minúsculas y determine:

Informática. Facultad de Ciencias (Matemáticas)


2 Relación de Problemas (Tema 4)

• el número de letras que las separan


• la letra o dos letras centrales del intervalo que determinan, pero en mayúsculas
11. Escribe un programa que determine si un año es bisiesto. Un año es bisiesto si es múltiplo
de 4 (por ejemplo 1984). Sin embargo, los años múltiplos de 100 sólo son bisiestos
cuando a su vez son múltiplos de 400 (por ejemplo 1800 no es bisiesto, mientras que 2000
lo será).
12. Escribe un programa que calcule el número de días de un mes, dados los valores
numéricos del mes y año.
NOTA: considera los años bisiestos para Febrero.
13. El domingo de Pascua es el primer domingo después de la primera luna llena posterior al
equinoccio de primavera, y se determina mediante el siguiente cálculo sencillo:
A = año MOD 19
B = año MOD 4
C = año MOD 7
D = (19 * A + 24) MOD 30
E = (2 * B + 4 * C + 6 * D + 5) MOD 7
N = (22 + D + E)
Donde N indica el día del mes de Marzo si N es menor o igual que 31 o Abril si es mayor
que 31 (el 32 indicaría el 1 de Abril). Construye un programa que determine fechas de
domingos de Pascua.
14. Escribe un programa que dados los conjuntos de puntos pertenecientes a la circunferencia
cuya ecuación es x2 + y2 = 16, a la elipse x2/36 + y2 /16 = 1 y a la recta y = 2x + 1, indique
para una pareja de coordenadas x e y , leídas desde el teclado, el conjunto o conjuntos a
los que pertenece.
15. Un sistema de ecuaciones lineales:
ax + by = c
dx + ey = f
se puede resolver con las siguientes fórmulas:
x = (ce - bf) / (ae - bd)
y = (af - cd) / (ae - bd)
Diseña el programa que lea los coeficientes a, b, c, d, e, f y muestre los valores de x e y.
NOTA: Estudia el caso en que (ae - bd) = 0.
16. Desarrolla un programa que calcule las raíces correspondientes a una ecuación de
segundo grado ax2 + bx + c = 0.
NOTA: Estudia el caso de raíces complejas (valor del discriminante < 0) y el caso a = 0.
17. Escribe un programa que lea un número de tipo CARDINAL, y si tiene exactamente
cuatro cifras, escriba si es capicúa o no.
18. Escribe un programa que lea un número de tipo CARDINAL, y si tiene exactamente
cuatro cifras, escriba si el número es igual a la suma de los cuadrados de sus cifras.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de Facultad de Ciencias (Matemáticas)
la Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 5. Estructuras iterativas


5.1 Bucles................................................................ ................................ ................. 2
5.2 Estructuras iterativas en Modula-2................................................................ ......3
5.2.1 La sentencia FOR ................................................................ ......................... 3
5.2.2 La sentencia WHILE ................................................................ ..................... 7
5.2.3 La sentencia REPEAT ................................................................ ................... 8
5.2.4 Las sentencias LOOP y EXIT................................................................ ......10
5.3 Diseño de bucles ................................................................ .............................. 13
5.3.1 Anidamiento de bucles ................................................................ ................ 13
5.3.2 Bucles infinitos ................................................................ ............................ 14

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.
• Fundamentos de programación. L. Joyanes. McGraw-Hill.

Informática. Facultad de Ciencias (Matemáticas)


2 5.1 Bucles

Introducción
Las estructuras de selección, estudiadas en el tema anterior, no son suficientes para describir
cualquier algoritmo. Es habitual que ciertas partes de un algoritmo deban repetirse varias veces
con objeto de resolver un problema. La repetición es un concepto importante a la hora de
describir algoritmos. Los lenguajes de programación disponen de una serie de sentencias que
permiten repetir varias veces algunos segmentos del programa. A este tipo de sentencias se les
denomina sentencias iterativas. En este tema, estudiamos las sentencias iterativas que
posee el lenguaje de programación Modula-2.

5.1 Bucles
Un término muy utilizado en el argot informático es bucle. Un bucle es un segmento de
algoritmo que se repite varias veces. Podemos distinguir dos tipos de bucles:
• Bucles deterministas: son aquellos para los cuales el número de repeticiones es
conocido a priori.
❏ Ejemplo: Supongamos que tenemos un ordenador que sólo es capaz de sumar
una unidad a un número. Escribir un algoritmo que lea un número del teclado, le
sume diez y muestre el resultado por pantalla.
Un posible algoritmo que utiliza un bucle determinista para resolver este problema
es el siguiente:
1. Leer un número desde el teclado
2. Repetir 10 veces
2.1. Sumar uno al valor del número
3. Escribir el valor del número

• Bucles no deterministas o indeterministas: son aquellos para los cuales no
se conoce a priori el número de veces que se van a repetir. El bucle se repite hasta
que se alcanza cierta condición. La condición es conocida a priori, pero no sabemos
cuántas veces es necesario repetir el bucle para que la condición se alcance.
❏ Ejemplo: Escribir un algoritmo que lea números desde teclado hasta que se lea
un valor cero. El algoritmo debe calcular la suma total de los números leídos y
mostrar el resultado por pantalla.
Un algoritmo que utiliza un bucle no determinista para resolver este problema es el
siguiente:
1. Guardar el valor cero en la variable suma
2. Leer un número desde el teclado
3. Mientras que el valor del número leído sea distinto de cero
3.1. Sumar el valor del número leído a la variable suma
3.2. Volver a leer el número desde el teclado
4. Escribir el valor de la variable suma

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 3

En este caso, no sabemos a priori cuántas veces se repetirán los pasos 3.1 y 3.2. Sin
embargo, sabemos que dichos pasos dejarán de repetirse en el momento en que se
lea un valor cero desde el teclado. Conocemos a priori la condición pero no el
número de repeticiones.

5.2 Estructuras iterativas en Modula-2


Modula-2 dispone de cuatro sentencias iterativas distintas. Pasemos a estudiar cada una de
ellas.

5.2.1 La sentencia FOR


La sentencia FOR permite repetir, un número de veces conocido a priori, una serie de
instrucciones. La sentencia FOR es el modo más adecuado de expresar bucles definidos en
Modula-2.
La forma más simple que toma esta sentencia es la siguiente:
FOR Variable := Valor1 TO Valor2 DO
Acciones
END

Las palabras FOR, TO, DO y END son palabras reservadas del lenguaje y delimitan cada una
de las partes de la sentencia. La variable que aparece a la izquierda del signo de asignación se
llama variable de control del bucle. El valor de esta variable se modifica en cada repetición
del bucle. El conjunto de acciones que aparecen entre las palabras DO y END constituyen el
cuerpo del bucle.
El significado de la sentencia FOR es el siguiente: se ejecutan las acciones entre las palabras
DO y END tantas veces como indique la expresión (Valor2-Valor1+1). La primera vez que se
ejecuten estas acciones, el valor de la variable de control es Valor1. La segunda vez que se
ejecuten las acciones, el valor de la variable de control es Valor1+1. El valor de la variable de
control es incrementado en una unidad tras cada repetición, de modo que la última vez que se
ejecuten las acciones el valor de la variable de control es Valor2.
❏ Ejemplo: Escribir un programa en Modula-2 que lea un número Num del teclado y calcule el
sumatorio de los números naturales menores o iguales a Num ( 1+2+3+ ... + (Num-1)+Num ).
El programa debe mostrar el resultado del sumatorio por pantalla.
Supongamos que no conocemos la siguiente equivalencia:
N
N ( N − 1)
∑i =
i =1 2
Para calcular el sumatorio anterior podemos utilizar una variable Suma cuyo valor inicial sea
cero. Primero, sumaremos a esta variable el valor uno. A continuación, el valor dos.
Repetiremos este paso hasta sumar el valor Num. El algoritmo que calcula el sumatorio es:
Suma := 0;
Suma := Suma + 1;
Suma := Suma + 2;
...
Suma := Suma + (Num-1);
Suma := Suma + Num;

o de un modo más breve:

Informática. Facultad de Ciencias (Matemáticas)


4 5.2 Estructuras iterativas en Modula-2

1. Asignar a la variable Suma el valor cero.


2. Repetir Num veces (variando el valor de i desde uno hasta Num)
2.1. Sumar i al valor de la variable Suma
El modo más adecuado de expresar el paso 2 de este algoritmo en Modula-2 es usando la
sentencia FOR. Se obtiene el siguiente programa:
MODULE Suma;
FROM IO IMPORT RdCard, RdLn, WrCard, WrStr;
VAR
Num, Suma, i : CARDINAL;
BEGIN
WrStr(“Dame un número: “);
Num := RdCard(); RdLn;

Suma := 0;
FOR i := 1 TO Num DO
Suma := Suma + i
END;

WrStr(“La suma de los primeros “);


WrCard(Num,0);
WrStr(“ números naturales es “);
WrCard(Suma,0)
END Suma.


Podemos especificar que la variable de control se incremente en un valor distinto a uno tras
cada repetición de las acciones dentro del bucle. Para ello se utiliza la palabra reservada BY
seguida del valor a sumar a la variable de control.
❏ Ejemplo: Escribir un programa en Modula-2 que lea del teclado un número impar Num y
calcule el sumatorio de los números naturales impares menores o iguales a Num ( 1+3+ ... +
(Num-2)+Num).
Para calcular el sumatorio anterior podemos utilizar una variable Suma cuyo valor inicial sea
cero. Sumaremos primero a esta variable el valor uno. A continuación el valor tres.
Repetiremos este paso hasta sumar el valor Num. El algoritmo que calcula el sumatorio es:
1. Asignar a la variable Suma el valor cero.
2. Repetir (variando el valor de i desde uno hasta Num de dos en dos)
2.1. Sumar i al valor de la variable Suma
Se obtiene el siguiente programa:
MODULE Suma;
FROM IO IMPORT RdCard, RdLn, WrCard, WrStr;
VAR
Num, Suma, i : CARDINAL;
BEGIN
WrStr(“Dame un número: “);
Num := RdCard(); RdLn;

IF NOT ODD(Num) THEN


WrStr(“El número no es impar”)
ELSE
Suma := 0;
FOR i := 1 TO Num BY 2 DO
Suma := Suma + i
END;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 5

WrStr(“La suma de los primeros “);


WrCard(Num,0);
WrStr(“ números impares naturales es “);
WrCard(Suma, 0)
END
END Suma.


El valor que aparece tras la palabra reservada BY puede ser también negativo. En dicho caso, la
variable de control del bucle es decrementada en el valor indicado tras cada repetición del
bucle.
La sintaxis BNF para la sentencia FOR es la siguiente:

Sentencia_FOR ::= FOR Identificador_de_Variable :=


Expresión_Inicial TO Expresion_Final [ BY Paso ] DO
Secuencia_de_Sentencias
END
Expresión_Inicial ::= Expresión
Expresión_Final ::= Expresión
Paso ::= Expresión_Constante

Obsérvese que la expresión que aparece tras la palabra BY debe ser una expresión constante
(no puede contener ni variables ni operadores no predefinidos).
El diagrama de flujo que representa la sentencia FOR depende del signo de la expresión
constante Paso:

Inicial ← Valor1 Inicial ← Valor1


Final ← Valor2 Final ← Valor2
Variable ← Inicial Variable ← Inicial

Sí Sí
Variable > Final Variable < Final

No No

Acciones Acciones

Variable←Variable+Paso Variable←Variable+Paso

Figura 1. Bucle FOR con paso positivo. Figura 2. Bucle FOR con paso negativo.

La condición de salida del bucle es que el valor de la variable de control sea estrictamente
mayor (o menor en el caso de que el Paso sea negativo) que el valor de la expresión que
aparece tras la palabra TO. Adviértase que dicha expresión se calcula una sola vez, antes de
comprobar la condición del bucle por primera vez. En cada repetición del bucle, la condición es

Informática. Facultad de Ciencias (Matemáticas)


6 5.2 Estructuras iterativas en Modula-2

comprobada antes de ejecutar el cuerpo del bucle. La variable de control es actualizada tras
ejecutar el cuerpo del bucle, antes de volver a comprobar la condición.
❏ Ejemplo: El cuerpo del siguiente bucle se ejecuta cero veces, ya que la condición de salida es
cierta la primera vez.
FOR i := 1 TO 0 DO
WrCard(i,0); WrLn
END


❏ Ejemplo: El cuerpo del siguiente bucle se ejecuta dos veces (con valores para la variable i de
uno y tres) ya que la condición de salida es cierta la tercera vez.
FOR i := 1 TO 4 BY 2 DO
WrCard(i,0); WrLn
END


❏ Ejemplo: El cuerpo del siguiente bucle se ejecuta diez veces (con valores para la variable i
entre uno y diez) ya que el valor para la expresión que aparece tras la palabra TO es calculado
una única vez y no se vuelve a calcular tras cada repetición.
Fin := 5;
FOR i := 1 TO 2*Fin DO
WrCard(i,0); WrLn;
Fin := 15
END


Algunas consideraciones sobre la sentencia FOR son las siguientes:
• El tipo de la variable de control puede ser básico (excepto REAL y LONGREAL),
enumerado o subrango (ver tema sobre tipos definidos por el programador).
❏ Ejemplo: El siguiente bucle muestra por pantalla todas las letras mayúsculas
(excepto la letra eñe mayúscula que está fuera del rango [‘A’..’Z’] en la tabla
ASCII).
FOR Letra := ‘A’ TO ‘Z’ DO
WrChar(Letra)
END


• Los tipos de las expresiones Valor1 y Valor2 deben ser compatibles con el tipo de
la variable de control.
• El tipo de la expresión constante Paso debe ser entero.
• La variable de control no puede ser:
1) Una componente de una variable estructurada (ver tema sobre arrays).
2) Una variable anónima (ver tema sobre punteros).
3) Una variable importada de otro módulo (ver tema sobre módulos).
4) Un parámetro formal (ver tema sobre subprogramas).
• El programador no debe escribir en el cuerpo del bucle ninguna sentencia que
modifique el valor de la variable de control.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 7

• El valor de la variable de control está indefinido tras la finalización del bucle FOR.
❏ Ejemplo: No sabemos qué valor se escribe por pantalla tras del siguiente bucle.
FOR i := 1 TO 10 DO
...
END;
WrInt(i, 0);

5.2.2 La sentencia WHILE


La sentencia WHILE permite repetir, mientras que sea cierta una condición, una serie de
instrucciones. La sentencia WHILE debe utilizarse exclusivamente para expresar bucles
indefinidos.
La forma que toma esta sentencia es la siguiente:
WHILE Condición DO
Acciones
END

Las palabras WHILE, DO y END son palabras reservadas del lenguaje y delimitan cada una de
las partes de la sentencia. La condición que aparece entre las palabras WHILE y DO es
denominada condición de permanencia del bucle. La condición de permanencia debe ser
una expresión de tipo booleano. El conjunto de acciones que aparecen entre las palabras DO y
END constituyen el cuerpo del bucle.
El diagrama de flujo correspondiente a esta sentencia es el siguiente:

No
Condición

Acciones

Figura 3. La sentencia WHILE.

Obsérvese que la condición se vuelve a evaluar, para cada repetición del bucle, antes de ejecutar el
cuerpo. Se permanece dentro del bucle mientras que la condición sea cierta (es por esto que la
condición se llama de permanencia). Podría ocurrir que el cuerpo del bucle no llegara a ejecutarse,
en caso de que la condición fuese falsa la primera vez que se comprueba.
La sintaxis de la sentencia WHILE, utilizando la notación BNF, es:

Informática. Facultad de Ciencias (Matemáticas)


8 5.2 Estructuras iterativas en Modula-2

Sentencia_WHILE ::= WHILE Condición DO


Secuencia_de_Sentencias
END
Condición ::= Expresión

❏ Ejemplo: Escribir un programa que, dado un número natural Tope, calcule el mínimo valor de
N tal que 0+1+2+...+(N-1)+N ≥ Tope.
Un posible algoritmo para resolver este problema es el siguiente:
1. Leer del teclado el valor de la variable Tope
2. Guardar el valor cero en la variable Suma
3. Guardar el valor cero en la variable N
4. Mientras que el valor de la variable Suma sea menor al de la variable Tope
4.1. Sumar uno a la variable N
4.2. Sumar N a la variable Suma
5. Escribir el valor de la variable N
Este algoritmo da lugar al siguiente diagrama de flujo y su correspondiente programa en
Modula-2:
MODULE Suma; INICIO
FROM IO IMPORT RdCard, RdLn, WrCard, WrStr;
VAR
Leer
Tope, N, Suma : CARDINAL; Tope
BEGIN
WrStr(“Dame el tope a umar:
s ”);
Tope := RdCard(); RdLn; suma ← 0
N ←0
Suma := 0;
N := 0;
WHILE Suma < Tope DO
No
N := N+1; Suma < Tope
Suma := Suma + N
END; Sí

WrStr(“El mínimo N que cumple la condición es ”); N←N+1


WrCard(N,0) Suma ← Suma + N

END Suma.

Escribir
N

FIN

5.2.3 La sentencia REPEAT


La sentencia REPEAT permite repetir una serie de instrucciones hasta que cierta condición sea
cierta. La sentencia REPEAT debe utilizarse exclusivamente para expresar bucles indefinidos.
La forma que toma esta sentencia es la siguiente:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 9

REPEAT
Acciones
UNTIL Condición

Las palabras REPEAT y UNTIL son palabras reservadas del lenguaje y delimitan cada una de
las partes de la sentencia. La condición que aparece tras la palabras UNTIL es denominada
condición de salida del bucle. La condición de salida debe ser una expresión de tipo
booleano. El conjunto de acciones que aparecen entre las palabras REPEAT y UNTIL
constituyen el cuerpo del bucle.
El diagrama de flujo correspondiente a esta sentencia es el siguiente:

Acciones

No
Condición

Figura 4. La sentencia REPEAT.

Obsérvese que la condición se vuelve a evaluar, para cada repetición del bucle, después de
ejecutar el cuerpo. Se sale del bucle una vez que la condición sea cierta (es por esto que la
condición se llama de salida). El cuerpo del bucle se ejecuta al menos una vez.
La sintaxis de la sentencia REPEAT, utilizando la notación BNF, es:

Sentencia_REPEAT ::= REPEAT


Secuencia_de_Sentencias
UNTIL Condición

❏ Ejemplo: Escribir un programa que calcule una aproximación al número e utilizando la


siguiente igualdad:

1 1 1
e = ∑ = 1+ + +
i = 0 i! 1! 2!
Dado que es imposible sumar infinitos términos, el programa dejará de sumar términos una vez
que el valor del último término sumado sea menor a una diezmilésima.
Llamemos término i-ésimo al término 1/i!. Es interesante observar que, conocido el valor del
término i-ésimo, basta con multiplicar éste por 1/(i+1) para obtener el valor del término
siguiente.
En el algoritmo utilizaremos tres variables. La variable i indicará, en todo momento, el orden
del término que estemos sumando. La variable Termino almacenará siempre el valor del
término actual. Por último, la variable Suma irá acumulando la suma de términos.
El diagrama de flujo correspondiente al algoritmo y su correspondiente programa en Modula-2
son los siguientes:

Informática. Facultad de Ciencias (Matemáticas)


10 5.2 Estructuras iterativas en Modula-2

MODULE NumeroE; INICIO


FROM IO IMPORT WrLngReal, WrStr;
CONST
DIEZMILESIMA = 1.0/10000.0; i ←0
Término ← 1
PRECISION = 10; Suma ←Término
ANCHO = 0;
VAR
i, Termino, Suma :LONGREAL;
BEGIN i←i+1
Término ← Término*(1/i)
i := 0.; Suma ← Suma+Término
Termino := 1.;
Suma := Termino;
REPEAT
No
i := i+1.; Término <
Termino := Termino * (1.0/i); 1/10000

Suma := Suma + Termino



UNTIL Termino < DIEZMILESIMA;
Escribir
WrStr(“La aproximación al número e es ”); Suma
WrLngReal(Suma,PRECISION,ANCHO)
END NumeroE.
FIN

5.2.4 Las sentencias LOOP y EXIT


La sentencia LOOP permite repetir indefinidamente una serie de instrucciones.
La forma que toma esta sentencia es la siguiente:
LOOP
Acciones
END

Las palabras LOOP y END son palabras reservadas del lenguaje y delimitan el cuerpo del
bucle.
El diagrama de flujo correspondiente a esta sentencia es el siguiente:

Acciones

Figura 5. La sentencia LOOP.

Obsérvese que el cuerpo del bucle es ejecutado indefinidamente. Nunca se pasa a la instrucción
siguiente al bucle.
La sentencia EXIT puede ser utilizada para salir de un bucle LOOP. La ejecución de una
sentencia EXIT, dentro del cuerpo de un bucle LOOP, hace que el bucle finalice y que la
ejecución del programa continúe con la instrucción siguiente a la palabra reservada END.
El modo habitual de combinar las sentencias LOOP y EXIT es el siguiente:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 11

LOOP
Acción A;
IF Condición THEN
EXIT
END;
Acción B
END

El diagrama de flujo correspondiente a esta construcción es:

Acción A


Condición

No

Acción B

Figura 6. Las sentencias LOOP y EXIT.

La sintaxis de las sentencias LOOP y EXIT, utilizando la notación BNF, es:

Sentencia_LOOP ::= LOOP


Secuencia_de_Sentencias
END
Sentencia_EXIT ::= EXIT

Algunas consideraciones sobre estas sentencias son:


• La sentencia EXIT sólo puede aparecer dentro del cuerpo de un bucle LOOP. No se
puede usar la sentencia EXIT para salir de un bucle FOR, WHILE o REPEAT.
• Cuando una sentencia EXIT aparece en un bucle LOOP anidado dentro de otro bucle
LOOP, la ejecución de dicha sentencia sólo hará que abandone el bucle LOOP más
interno.
• Si la comprobación de la condición de salida se hace justo al principio del bucle LOOP,
se debe sustituir el bucle LOOP por uno WHILE equivalente:
LOOP
IF Condición THEN WHILE NOT Condición DO
EXIT ≡ Acciones
END; END
Acciones
END

• Si la comprobación de la condición de salida se hace justo al final del bucle LOOP, se


debe sustituir el bucle LOOP por uno REPEAT equivalente:

Informática. Facultad de Ciencias (Matemáticas)


12 5.2 Estructuras iterativas en Modula-2

LOOP
Acciones REPEAT
IF Condición THEN ≡ Acciones
EXIT UNTIL Condición
END;
END

• De los dos puntos anteriores, se deduce que sólo se debe utilizar las combinación de
las sentencias LOOP y EXIT cuando la condición de salida está situada en un punto
intermedio del cuerpo del bucle.
• Modula-2 no limita el número de sentencias EXIT que pueden aparecer dentro de un
bucle LOOP. El uso de esta característica del lenguaje hace que los programas
resultantes sean difíciles de entender. Además de ser un mal estilo de programación, el
uso de esta característica del lenguaje no es necesaria. Por todo esto, es muy importante
evitar que aparezca más de una sentencia EXIT dentro de cada bucle LOOP.
❏ Ejemplo: Escribir un programa que lea dos números enteros del teclado, de modo que el
segundo sea mayor que el primero.
Utilizaremos dos variables enteras (Menor y Mayor). Leeremos un valor en la variable Menor y
repetiremos el proceso de leer otro número en la variable Mayor hasta que éste sea
estrictamente mayor al primero.
El diagrama de flujo correspondiente al algoritmo y su correspondiente programa en Modula-2
son los siguientes:
INICIO
MODULE Numeros;
FROM IO IMPORT WrStr, WrInt, RdInt, RdLn;
VAR Escribir
Menor, Mayor : INTEGER; “Dame un nº”
BEGIN
WrStr(“Dame un número: “);
Menor := RdInt(); RdLn; Leer
WrStr(“Dame un número mayor: “); Menor

LOOP
Mayor := RdInt(); RdLn;
IF Mayor > Menor THEN Escribir
“Dame otro mayor”
EXIT
END;
WrStr(“Debe ser mayor. Prueba de nuevo”)
END Leer
WrStr(“Los números son: “); Mayor
WrInt(Menor,0);
WrStr(“ y “);
WrInt(Mayor,0) Sí
END Numeros. Mayor > Menor

No

Escribir
“Prueba otra vez”

Escribir
Menor y Mayor

FIN

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 13

5.3 Diseño de bucles

5.3.1 Anidamiento de bucles


Modula-2 no impone ningún tipo de restricciones sobre el tipo de sentencias que pueden
aparecer en el cuerpo de un bucle. En concreto, dentro del cuerpo de un bucle puede aparecer
otro bucle. A esto se le llama anidamiento de bucles.
❏ Ejemplo: Escribir un programa que calcule el factorial de un número leído del teclado sin
usar la operación de multiplicación.
Desarrollamos primero un algoritmo que calcula el producto de dos números enteros y positivos
i y f. Para ello, sumamos f veces el valor de i:

i× f = i +i +


 



+i


f veces

Para realizar el cálculo anterior, hay que repetir la acción sumar i un número predeterminado
de veces (f veces). El modo más adecuado de expresar esto es mediante una sentencia FOR:
MODULE Producto;
FROM IO IMPORT RdCard, RdLn, WrCard, WrStr;
VAR
i, f, Producto, j :CARDINAL;
BEGIN
WrStr(“Dame un número: ”);
i := RdCard(); RdLn;
WrStr(“Dame otro: ”);
f := RdCard(); RdLn;

Producto := 0;
FOR j := 1 TO f DO
Producto := Producto + i
END;

WrStr(“El producto es ”);


WrCard (Producto, 0)
END Producto.

Para calcular el factorial de un número n debemos calcular la siguiente expresión:


n!= n × (n − 1) × (n − 2) ×


      



2 ×1


n productos

Para realizar el cálculo anterior, hay que repetir n veces la acción multiplicar por i (variando el
valor de i desde uno hasta n). De nuevo, el modo más adecuado de expresar esto es mediante
un bucle FOR:
MODULE Fact;
FROM IO IMPORT RdCard, RdLn, WrCard, WrStr;
VAR
n, i, f : CARDINAL;
BEGIN
WrStr(“Dame un número: ”);
n := RdCard(); RdLn;

f := 1;
FOR i := 1 TO n DO
f := f * i
END;

Informática. Facultad de Ciencias (Matemáticas)


14 5.3 Diseño de bucles

WrStr(“El
WrCard (f,factorial
0) es ”);
END Fact.

Podemos utilizar el primer algoritmo para calcular el producto f * i . Combinando los dos
algoritmos, obtenemos un algoritmo que calcula el factorial de un número sin usar la operación
de multiplicación:
MODULE FactSum;
FROM IO IMPORT RdCard, RdLn, WrCard, WrStr;
VAR
n, i, j, f, Producto :CARDINAL;
BEGIN
WrStr(“Dame un número: ”);
n := RdCard(); RdLn;

f := 1;
FOR i := 1 TO n DO

Producto := 0;
FOR j = 1 TO f DO
Producto := Producto + i ≡ f := f * i
END;
f := Producto

END;

WrStr(“El factorial es ”);


WrCard (f, 0)
END FactSum.

Nótese que ha sido necesario anidar dos bucles FOR para resolver el problema.

❏ Ejercicio: Dibuja el diagrama de flujo correspondiente a este algoritmo. Sigue los pasos que
se realizarían para calcular 3! usando el algoritmo anterior.

Al diseñar un algoritmo con bucles anidados, tanto el comienzo como el final del bucle anidado
deben estar dentro del bucle exterior. Así, el siguiente anidamiento de bucles es correcto:
WHILE Condición DO
Acciones
REPEAT
Acciones
UNTIL Condición
Acciones
END

El siguiente anidamiento no es correcto, ya que el bucle REPEAT está anidado en el bucle


WHILE y debe acabar antes que éste último:
WHILE Condición DO
Acciones
REPEAT
Acciones
END
UNTIL Condición
Acciones

Otra consideración importante es que a la hora de anidar bucles FOR, es imprescindible utilizar
una variable de control distinta para cada uno de ellos. Por ejemplo,

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 5. Estructuras iterativas 15

FOR i := 1 TO 3 DO
FOR j := 1 TO 2 DO
WrChar(‘*’)
END
END

Obsérvese que este trozo de programa escribe seis asteriscos por pantalla.

5.3.2 Bucles infinitos


Un bucle infinito es un bucle que nunca acaba.
La ejecución de un programa que contiene un bucle infinito no acaba. En TopSpeed Modula-2,
el programador puede interrumpir un programa de este tipo pulsando simultáneamente las
teclas Ctrl y C.
A veces, el bucle infinito es debido a que el programador olvidó alguna sentencia en el cuerpo
del bucle.
❏ Ejemplo: El siguiente bucle no acaba ya que falta una sentencia en el cuerpo que incremente
el valor de la variable N:
Suma := 0;
N := 0;
WHILE N <= 100 DO
Suma := Suma + N
END
WrCard(Suma,0)


En otras ocasiones, el bucle infinito se debe a que es imposible alcanzar la condición de
finalización del bucle. Este tipo de situaciones se suelen deber a un error de diseño del
programa.
❏ Ejemplo: El siguiente bucle no acaba ya que la variable N nunca alcanzará un valor negativo:
Suma := 0;
N := 100;
WHILE N >= 0 DO
Suma := Suma + N;
N := N + 1
END
WrCard(Suma,0)


Con objeto de evitar la aparición de bucles infinitos, es importante comprobar que las
condiciones de finalización de un bucle serán alcanzadas.

Informática. Facultad de Ciencias (Matemáticas)


Relación de Problemas (Tema 5) 1

Relación de problemas (Tema 5)


1. Escribir un programa que lea una lista de valores reales positivos terminada por un
número negativo y calcule la media de esta lista.

2. Escribe un programa que lea un número entero positivo por teclado y determine si es
primo o compuesto.

3. Diseña un programa que escriba en pantalla la tabla de multiplicar de un numero del 1 al 9


introducido desde el teclado.

4. Escribir un programa que lea una lista de valores enteros positivos terminada por 0 y
determine los valores máximo y mínimo.

5. Escribe un programa que calcule e imprima en pantalla los N primeros números primos,
siendo N un número que se introduce por teclado.

6. Escribe un programa que calcule el valor de seno x dado por:

x3 x5 x7
Seno x = x − + − +

3! 5! 7 !

Este programa debe leer dos variables x y precisión donde x es un ángulo en radianes y
precisión debe ser positiva y menor que 1. La serie debe sumarse hasta que el valor
absoluto del último sumando sea menor que precisión, incluyendo este último sumando
en la suma.

NOTA: El término con numerador xn de la serie se puede calcular multiplicando el previo

x2
por −
n × (n − 1)

7. Un billete de segunda clase en tren de Londres a París vale normalmente 80 libras. Sin
embargo, existen tarifas especiales en los siguientes casos:

65 años o más: 52.60 libras

12-15 años: 55.00 libras

4-11 años: 40.20 libras

menos de 4 años: gratis

Supón que se quiere averiguar el coste total para una familia. Escribe un programa que lea
la edad de cada miembro de la familia y calcule el coste total de los billetes.

Informática. Facultad de Ciencias (Matemáticas)


2 Relación de Problemas (Tema 5)

8. Si x0 es una aproximación a a entonces una aproximación mejor es:

1 a
x1 =  x0 + 
2 x0 

Podemos repetir esto con x1 y obtener una nueva aproximación. Este proceso, que es una
aplicación del método de Newton-Raphson, se puede generalizar a:

1 a
x n +1 =  x n +  , n = 1,2,
2 xn 

Escribir un programa que lea los valores de a y precisión. El programa debe calcular la
raíz de a utilizando el método descrito. El programa se detiene cuando el valor absoluto
de la diferencia entre dos aproximaciones consecutivas es menor que precisión. El valor
inicial para la iteración será a/2. El programa debe mostrar en pantalla todos las
aproximaciones obtenidas.

9. Escribir tres programas para calcular el factorial de un número entero leído desde el
teclado utilizando las sentencias FOR, WHILE y REPEAT.

10. Escribir un programa que visualice en pantalla una figura similar a la siguiente:

*
**
***
****

Siendo el número de líneas un dato que se lee al principio del programa.


11. Un número es perfecto si coincide con la suma de todos sus divisores excepto el mismo.
Por ejemplo 28 es perfecto ya que sus divisores son 1, 2, 4, 7, 14 y 1+2+4+7+14 = 28.
Escribe un programa que determine si un número leído del teclado es perfecto.

12. Para encontrar el máximo común divisor (mcd) de dos números enteros se puede emplear
el algoritmo de Euclides: dados los números a y b (siendo a > b) se divide a por b
obteniendo el cociente q1 y el resto r1. Si r1 ≠ 0, se divide b por r1, obteniendo el cociente
q2, y el resto r2. Si r2 ≠ 0 se divide r1 por r2 obteniendo cocientes y restos sucesivos. El
proceso continúa hasta obtener un resto igual a 0. El resto anterior a éste es el máximo
común divisor de los números iniciales (a y b). Escribe un programa que lea dos números
enteros positivos del teclado y calcule su máximo común divisor y su mínimo común
múltiplo (mcm).

NOTA: obtener el mínimo común múltiplo a partir de la siguiente igualdad:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 5) 3

mcd (a,b) * mcm (a,b) = a * b

13. Diseñar un programa que calcule el cociente entero y el resto correspondiente para dos
números naturales dados por teclado, sin utilizar los operadores DIV y MOD.

14. Escribir un programa que lea un número N y calcule el término N-ésimo de la sucesión de
Fibonacci:

Fibonacci (1) = 0
Fibonacci (2) = 1
Fibonacci (n) = Fibonacci (n − 1) + Fibonacci (n − 2) , si n > 2

Realizar los ejercicios 3, 8 y 10 como práctica en el laboratorio.

Informática. Facultad de Ciencias (Matemáticas)


4 Relación de Problemas (Tema 5)

Relación complementaria (Tema 5)


1. Escribir un programa que calcule y visualice el más grande, el más pequeño y la media de
N números. El valor N se solicitará al principio del programa y los números serán
introducidos por teclado.

2. Encontrar el número natural N más pequeño tal que la suma de los N primeros números
enteros exceda una cantidad introducida por teclado.

3. El valor exp(x) se puede calcular mediante la siguiente serie:

x x2 x3
exp( x ) = 1 + + + +

1! 2 ! 3!

Escribir un programa que lea un valor x de teclado y calcule el valor de exp(x) deteniendo
el cálculo cuando el valor del último término sumado sea inferior a una diezmilésima.

4. Escribir un programa que calcule una aproximación al número π sabiendo que

π 1 1 1 1
= 1− + − + −

4 3 5 7 9

para ello sumará términos de la serie anterior mientras que sus valores absolutos sean
mayores a una diezmilésima y del valor obtenido despejará π.

5. Diseña un programa que multiplique dos números enteros mediante el algoritmo de la


multiplicación rusa. Este algoritmo multiplica por 2 el multiplicando y divide por dos el
multiplicador hasta que el multiplicador toma el valor 1. Después suma todos los
multiplicandos correspondientes a multiplicadores impares. Dicha suma es el producto de
los números originales.

Ejemplo: 37 * 12 = 444 (multiplicador: 37, multiplicando:12)

37 12
18 24
9 48
4 96
2 192
1 384

12 + 48 + 384 = 444

6. Escribe un algoritmo que lea un número natural N y un carácter. La salida debe ser un
rombo compuesto del carácter y de la anchura que especifica el número N. Por ejemplo, si
N es 5 y el carácter es *, el rombo sería:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 5) 5

7. Escribe un algoritmo que imprima una pirámide de dígitos como la de la figura, tomando
como entrada el número de filas de la misma siendo este número menor o igual que 9.


  

    

      

        

8. Escribir un programa que lea del teclado un número N (expresado en base 10), una base B
y pase N a base B.

9. Escribe un programa que determine si la cadena “abc” aparece en una sucesión de


caracteres cuyo final viene dado por un punto.

Informática. Facultad de Ciencias (Matemáticas)


Tema 6. Procedimientos y funciones 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de Facultad de Ciencias (Matemáticas)
la Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 6. Procedimientos y funciones


6.1 Concepto de subprograma ................................................................ ................. 2
6.2 Definición y llamadas a subprogramas ............................................................... 3
6.3 Parámetros formales y reales ................................................................ .............5
6.4 Paso de parámetros por valor y por referencia ................................................... 6
6.5 Procedimientos y Funciones................................................................ ............... 8
6.5.1 Diferencias entre procedimientos y funciones ............................................... 8
6.5.2 Funciones predefinidas ................................................................ ............... 10
6.5.3 Procedimientos predefinidos ................................................................ .......10
6.6 Uso de parámetros ................................................................ ........................... 11
6.6.1 Parámetros de entrada................................................................ ................ 11
6.6.2 Parámetros de salida ................................................................ .................. 11
6.6.3 Parámetros de entrada y salida................................................................ ...12
6.7 Anidamiento de subprogramas. Ámbitos........................................................... 12
6.7.1 Estructura de bloques ................................................................ ................. 12
6.7.2 Redefinición de elementos ................................................................ ..........13
6.7.3 Efectos laterales................................................................ .......................... 14
6.7.4 Doble referencia................................................................ .......................... 15
6.8 Sintaxis BNF para subprogramas ................................................................ .....15

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.

Informática. Facultad de Ciencias (Matemáticas)


2 6.1 Concepto de subprograma

Introducción
Cualquier programa de ordenador que realice una labor útil suele tener una extensión
considerable. El diseño de un algoritmo complejo de una sola vez es una labor complicada.
Vimos como la metodología de diseño descendente permite distinguir diferentes partes en la
descripción de un algoritmo, de modo que la agrupación de éstas constituya el algoritmo
completo. Estudiamos en este tema los mecanismos del lenguaje de programación Modula-2
que permiten dividir un programa complejo en partes más manejables que puedan ser
posteriormente combinadas.

6.1 Concepto de subprograma


Un subprograma es una parte de un programa. Desde el punto de vista de la programación,
un subprograma es un trozo de programa que se escribe por separado y que puede ser utilizado
invocándolo mediante su nombre.
Esto hace que distingamos dentro de un programa el programa principal (el algoritmo
correspondiente al módulo raíz) y subprogramas (los demás módulos). Cada subprograma
puede a su vez estar dividido en otros subprogramas.
Supongamos que queremos escribir un programa que calcule el perímetro de un triángulo con
vértices A, B y C:
A

Figura 1. Triángulo de vértices A, B y C

El diagrama de bloques para el algoritmo puede ser:

Cálculo
Perímetro Triángulo

Leer Vértices Calcular Imprimir


Perímetro Perímetro

Leer Leer Leer


Coordenada A Coordenada B Coordenada C

Figura 2. Diagrama de bloques para cálculo del perímetro del triángulo

El problema de calcular el perímetro de un triángulo puede dividirse en tres subproblemas: leer


las coordenadas, calcular el perímetro e imprimir el perímetro. A su vez, el primero de estos
subproblemas puede dividirse en tres subproblemas: leer la coordenada A, leer la coordenada B
y leer la coordenada C.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 3

El aspecto del programa resultante es el siguiente:


MODULE Perimetro;
...
PROCEDURE LeerVertices;
...
PROCEDURE LeerUnaCoordenada...;
BEGIN
...
END LeerUnaCoordenada;
...
BEGIN
(* Leer las tres coordenadas *)
LeerUnaCoordenada...;
LeerUnaCoordenada...;
LeerUnaCoordenada...;
END LeerVertices;

PROCEDURE CalcularPerimetro...;
BEGIN
...
END CalcularPerimetro;

PROCEDURE ImprimirPerimetro...;
BEGIN
...
END ImprimirPerimetro;
BEGIN
LeerVertices...
CalcularPerimetro...
ImprimirPerimetro...
END Perimetro.

Vemos como cada subprograma aparece descrito en la parte declarativa del programa
(declaración de subprograma) y luego se usa escribiendo su nombre en la parte ejecutiva
(llamada a subprograma).
Es deseable que en el cuerpo del programa principal se evite la aparición excesiva de
estructuras de control (selección y repetición) utilizando subprogramas. De esta forma, el
cuerpo del programa principal estará constituido fundamentalmente por llamadas a
subprogramas.
Se suelen emplear subprogramas en los siguientes casos:
• En programas complejos: si un programa complejo se escribe sin subprogramas
resulta difícil de entender. Dividiéndolo en subprogramas podemos centrarnos en cada
momento en un problema más pequeño que el problema original.
• Cuando se repite dentro de un algoritmo algún tipo de tratamiento: en este
caso podemos escribir un subprograma que describe cómo se hace el tratamiento una
sola vez y realizar una llamada a este subprograma cada vez que queramos usar el
tratamiento.

6.2 Definición y llamadas a subprogramas


Cuando para describir un algoritmo A se necesita emplear otro algoritmo B, de dice que el
algoritmo A llama al algoritmo B. En el ejemplo anterior el algoritmo principal llama a los
algoritmos LeerVertices, CalcularPerimetro e ImprimirPerimetro. El algoritmo
LeerVertices llama al algoritmo LeerUnaCoordenada.

Informática. Facultad de Ciencias (Matemáticas)


4 6.2 Definición y llamadas a subprogramas

A veces, un subprograma depende del valor de una o más variables. Por ejemplo, nos puede
interesar escribir un subprograma EscribeSumatorio que dado un número N calcule el valor
de la suma 1 + 2 + ... + (N-1) + N y escriba el resultado por pantalla. Para distintos valores de N
el subprograma obtiene distintos resultados. En este caso decimos que el subprograma
EscribeSumatorio está parametrizado por N, o que N es un parámetro del subprograma
EscribeSumatorio. Un subprograma puede depender de cero, uno o más parámetros.
Lo primero que debemos hacer para utilizar un subprograma es declararlo. La primera parte
de la declaración de un subprograma es su cabecera. En la cabecera de un subprograma se
indica el nombre de éste y los parámetros o argumentos que toma (especificando para cada uno
su tipo). La sintaxis que sigue Modula-2 para esto es:
PROCEDURE Nombre (argumento1 : Tipo1; argumento2 : Tipo2; ... );
En la cabecera del subprograma aparece la palabra reservada seguida del nombre del
subprograma. Este nombre debe ser un identificador válido. A continuación, y entre paréntesis,
se escribe la lista de argumentos separados por puntos y comas. El nombre de cada argumento
debe ser también un identificador. Detrás de cada argumento se especificará su tipo precedido
de un signo dos puntos. Si hay varios argumentos seguidos con el mismo tipo, podemos
abreviar y escribirlos separados por comas, indicando una sola vez el tipo de todos ellos.
La cabecera del subprograma EscribeSumatorio comentado previamente es:
PROCEDURE EscribeSumatorio ( N : CARDINAL );

Después de la cabecera del subprograma viene su cuerpo. La declaración finaliza con la palabra
END seguida del nombre del subprograma. En el cuerpo, aparecen las declaraciones de los
elementos necesarios para el subprograma y tras la palabra reservada BEGIN el código
necesario para resolver el subproblema. La declaración completa para el ejemplo es:
PROCEDURE EscribeSumatorio ( N : CARDINAL );
VAR Suma, i : CARDINAL;
BEGIN
Suma := 0;
FOR i := 1 TO N DO
Suma := Suma + i
END;
WrCard (Suma, 0)
END EscribeSumatorio;

Podemos observar que las variables Suma e i se declaran dentro del subprograma y no en el
programa principal. Esto se hace así porque sólo son necesarias para el cómputo que hace el
subprograma. A estas variables se les llama variables locales del subprograma. Las variables
locales de un subprograma se crean automáticamente cada vez que se llama al subprograma y se
destruyen cada vez que se sale del subprograma. Por esto NO CONSERVAN EL VALOR que
tenían de una llamada a otra. Tampoco toman valores iniciales por defecto; es el programador el
encargado de proporcionárselos.
Una vez que tenemos un subprograma declarado, podemos utilizarlo dentro del cuerpo de otro
subprograma (en condiciones que especificaremos un poco más adelante) o del programa
principal. Para utilizar el subprograma hay que llamarlo. La llamada a un subprograma se
realiza utilizando su nombre seguido de los valores (entre paréntesis y separados por comas)
con los que queremos que trabaje el subprograma. Una llamada a un subprograma con N
parámetros se escribe:
NombreSubprograma (parámetro1, parámetro2, ... ,parámetroN)
Si queremos llamar al subprograma EscribeSumatorio para que calcule la suma de los
primeros 50 naturales escribiremos:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 5

EscribeSumatorio (50)

6.3 Parámetros formales y reales


Supongamos que queremos escribir un programa que escriba por pantalla todos los números
primos menor a uno dado (Max). Para ello, escribiremos primero un subprograma que dado un
número Num escriba por pantalla si es primo y en otro caso no escriba nada:
PROCEDURE EscribirSiPrimo (Num : CARDINAL);
VAR
Divisor : CARDINAL;
EsPrimo : BOOLEAN;
BEGIN
EsPrimo := TRUE;
Divisor := 2;
WHILE EsPrimo AND (Divisor <= Num DIV 2) DO
EsPrimo := Num MOD Divisor <> 0;
Divisor := Divisor +1
END;
IF EsPrimo THEN
WrCard (Num, 0); WrLn
END
END EscribirSiPrimo;

El programa principal leerá el número Max del teclado y llamará a este subprograma para todos
los valores en el intervalo [1,Max]:
MODULE Primos;
FROM IO IMPORT WrCard, RdCard, RdLn, WrStr, WrLn;
VAR
Max, i : CARDINAL;

PROCEDURE EscribirSiPrimo (Num : CARDINAL);


VAR
Divisor : CARDINAL;
EsPrimo : BOOLEAN;
BEGIN
EsPrimo := TRUE;
Divisor := 2;
WHILE EsPrimo AND (Divisor <= Num DIV 2) DO
EsPrimo := Num MOD Divisor <> 0;
Divisor := Divisor + 1
END;
IF EsPrimo THEN
WrCard (Num, 0); WrLn
END
END EscribirSiPrimo;

BEGIN
WrStr (“Dame el máximo: “);
Max := RdCard(); RdLn;
WrStr (“Los primos menores son:”); WrLn;
FOR i := 1 TO Max DO
EscribirSiPrimo(i)
END
END Primos.

Se llaman parámetros formales a la lista de parámetros que aparece en la declaración de un


subprograma parametrizado. En el ejemplo, Num es un parámetro formal del subprograma
EscribirSiPrimo.

Informática. Facultad de Ciencias (Matemáticas)


6 6.4 Paso de parámetros por valor y por referencia

Se llaman parámetros reales a la lista de parámetros que aparece en la llamada al


subprograma. En el ejemplo, i es un parámetro real en la llamada a EscribirSiPrimo. Los
parámetros reales pueden ser, además de variables, expresiones y por lo tanto valores (a veces,
sólo pueden ser variables). En una llamada como EscribirSiPrimo(143), el parámetro real es
143.
Los parámetros formales de un subprograma son opcionales, esto es, se pueden escribir
subprogramas no parametrizados, pero si aparecen deben seguir una serie de normas:
• El número de parámetros reales en una llamada a un subprograma parametrizado debe
ser igual al número de parámetros formales en la definición de dicho subprograma.
• La correspondencia entre parámetros formales y reales es por posición. El i-ésimo
parámetro real se corresponde con el i-ésimo parámetro formal.
• El tipo del i-ésimo parámetro real debe ser compatible (o igual si es por referencia) con
el declarado para el i-ésimo formal.
• Los nombres de un parámetro formal y su correspondiente real pueden ser distintos.
Así, con el siguiente programa:
MODULE Ejemplo;
FROM IO IMPORT WrReal;
VAR x, y : REAL;
PROCEDURE EscribirDivCuadrados (x, y : REAL);
CONST
PRECISION = 5;
ANCHO = 0;
VAR Cociente : REAL;
BEGIN
Cociente := (x * x) / (y * y);
WrReal (Cociente, PRECISION, ANCHO)
END EscribirDivCuadrados;
BEGIN
x := 1.0;
y := 2.0;
EscribirDivCuadrados (y, x)
END Ejemplo.

El resultado que obtenemos es 4.0/1.0 = 4.0, ya que la correspondencia entre parámetros


formales y reales está dada por el orden y no por los nombres.
El efecto producido por la llamada a un subprograma puede resumirse como:
1) Se evalúan las expresiones que aparezcan en los argumentos (parámetros reales).
2) Se crean las variables correspondientes a los parámetros formales.
3) Se asignan los valores calculados en 1) a los correspondientes parámetros formales.
4) Se crean las variables locales al subprograma.
5) Se ejecuta el código correspondiente al subprograma.
6) Se destruyen las variables locales y las correspondientes a los parámetros formales.
7) Se continúa el programa por la instrucción siguiente a la llamada al subprograma.

6.4 Paso de parámetros por valor y por referencia


Consideremos el siguiente programa, en el que declaramos un subprograma EscribirModulo
que escribe por pantalla el módulo de un complejo con parte real Real y parte imaginaria Imag:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 7

MODULE Ejemplo;
FROM IO IMPORT WrLngReal, WrStr, WrLn;
FROM MATHLIB IMPORT Sqrt;
VAR x, y : LONGREAL;

PROCEDURE EscribirModulo (Real, Imag : LONGREAL);


VAR Modulo : LONGREAL;
BEGIN
WrStr ("El módulo del complejo ");
WrLngReal (Real, 5, 0);
WrStr (" ");
WrLngReal (Imag, 5, 0);
WrStr (" i es: ");
Real := Real * Real;
Imag := Imag * Imag;
Modulo := Sqrt (Real + Imag);
WrLngReal (Modulo, 5, 0);
WrLn
END EscribirModulo;

BEGIN
x := 5.0;
y := 6.0;
EscribirModulo (x, y);
WrLngReal (x, 5, 0); WrLn;
WrLngReal (y, 5, 0); WrLn
END Ejemplo.

La salida producida por pantalla al ejecutar este programa es:


El módulo del complejo 5.0000E+0 6.0000E+0 i es: 7.8102E+0
5.0000E+0
6.0000E+0

Al realizar la llamada al subprograma los valores de x (5.0) e y (6.0) se copian en los


parámetros formales Real y Imag. A continuación, se pasa a ejecutar el subprograma: se
escribe la primera línea por pantalla, Real pasa a valer 25.0, Imag pasa a valer 36.0, Módulo
pasa a valer Sqrt (61.0), se escribe este valor por pantalla ( “7.8102E+0”) y se salta a la
siguiente línea de pantalla. Después de esto, se vuelve al programa principal y continuamos por
donde se dejó: se escriben por pantalla los valores de x e y en distintas líneas ( 5.0000E+0 y
6.0000E+0). Lo importante de este ejemplo es que al cambiar el valor de los parámetros
formales (Real e Imag) dentro del subprograma, el valor de los parámetros reales (x e y) se ha
conservado.
A este modo de pasar parámetros se le llama paso de parámetros por valor. Los
parámetros reales y formales representan variables distintas, aunque al llamar al subprograma
se copian los valores de las reales sobre las correspondientes formales. Los argumentos reales
en la llamada al subprograma pueden darse en forma de expresiones, cuyos tipos deben ser
COMPATIBLES EN ASIGNACIÓN con los tipos de los argumentos formales.
Existe otra forma de pasar parámetros conocida como paso de parámetros por
referencia. En este caso, a la hora de realizar la llamada al subprograma, no se copian los
valores de los parámetros reales en los formales, sino que se identifican parámetros reales y
formales, de modo que cada vez que cambiemos el valor de un parámetro formal en el
subprograma estaremos cambiando el valor del parámetro real original. Para indicar que
queremos que un parámetro pase por referencia utilizaremos la palabra reservada VAR delante
de dicho argumento en la cabecera del subprograma. En nuestro ejemplo:
PROCEDURE EscribirModulo (VAR Real, Imag : LONGREAL);

Informática. Facultad de Ciencias (Matemáticas)


8 6.5 Procedimientos y Funciones

En este caso estamos pasando ambos parámetros por referencia y el resultado que obtenemos
por pantalla es:
El módulo del complejo 5.0000E+0 6.0000E+0 i es: 7.8102E+0
2.5000E+0
3.6000E+0

Cada parámetro puede independientemente estar declarado por valor o referencia:


PROCEDURE EscribirModulo (VAR Real : LONGREAL; Imag : LONGREAL);

Por referencia sólo el primero

PROCEDURE EscribirModulo (Real : LONGREAL; VAR Imag : LONGREAL);

Por referencia sólo el segundo


Si un argumento está declarado por referencia no podremos pasar como parámetros reales
expresiones, sino SOLO VARIABLES. Para argumentos declarados por referencia, el tipo de la
variable pasada como parámetro debe ser EXACTAMENTE EL MISMO que el del parámetro
formal correspondiente.
Tanto el paso por valor como el paso por referencia tienen sus ventajas e inconvenientes:
• Paso por valor:
• Ventajas: Aísla el efecto del subprograma a su propio ámbito. Esto hace más fácil
de seguir los programas. Si se usa paso por valor y se sigue el programa principal sin
entrar en los subprogramas, se sabe que las variables sólo cambian si cambian en el
principal.
• Desventajas: Utiliza más memoria. El parámetro real y el formal ocupan cada uno
su propio trozo de memoria en el ordenador.
• Paso por referencia:
• Ventajas: Utiliza menos memoria. El parámetro formal y real son los mismos y
ocupan el mismo espacio de memoria.
• Desventajas: Sólo permite variables como parámetros reales y el seguimiento del
programa resulta más complejo.

6.5 Procedimientos y Funciones

6.5.1 Diferencias entre procedimientos y funciones


Todos los subprogramas vistos hasta ahora son procedimientos. Un procedimiento es un
subprograma que toma opcionalmente una serie de parámetros y hace algo con ellos. Ejemplos
de procedimientos son las instrucciones de salida de datos del módulo de biblioteca IO.
Sin embargo, cuando se diseña un algoritmo aparecen con frecuencia operaciones que calculan
un valor simple a partir de ciertos parámetros o argumentos:
• Calcular el volumen de un cubo de lado l: se calcula el valor l a partir del parámetro l.
3

• Calcular la potencia x : se calcula el valor x elevado a n a partir de dos argumentos x y


n

n.
• Calcular el máximo de dos números a y b: se devuelve el mayor de a y b.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 9

Todas estas operaciones se pueden considerar subprogramas del tipo función. Una función es
un subprograma que calcula como resultado un valor simple a partir de otros valores dados
como argumentos. Una función se asemeja bastante al concepto de función matemática con
argumentos:
VolumenCubo(l ) = l3
Potencia( x, n) = xn
 a, si a > b
Maximo(a, b) = 
î b, en otro caso
La definición de una función es similar a la de un procedimiento. Sin embargo, la cabecera de
una función incluye al final el tipo de valor que devuelve precedido de dos puntos. Para los
ejemplos anteriores las cabeceras en Modula-2 son:
PROCEDURE VolumenCubo (l : REAL) : REAL;
PROCEDURE Potencia (x : REAL; n : CARDINAL) : REAL;
PROCEDURE Máximo (a, b : REAL) : REAL;

En el cuerpo de la función se debe indicar el valor devuelto. Esto se hace mediante la sentencia
RETURN. La declaración completa de las funciones anteriores es:
PROCEDURE VolumenCubo (l : REAL) : REAL;
BEGIN
RETURN (l*l*l)
END VolumenCubo;

PROCEDURE Potencia (x : REAL; n : CARDINAL) : REAL;


VAR i : CARDINAL;
Pot : REAL;
BEGIN
Pot := 1.0;
FOR i := 1 TO n DO
Pot := Pot * x
END;
RETURN Pot
END Potencia;

PROCEDURE Maximo (a, b : REAL) : REAL;


VAR Max : REAL;
BEGIN
IF (a>b) THEN
Max := a
ELSE
Max := b
END;
RETURN Max
END Maximo;

El tipo de la expresión que sigue a la sentencia RETURN debe ser COMPATIBLE EN


ASIGNACIÓN con el tipo declarado en la cabecera. Al ejecutarse la sentencia RETURN la
función termina y se vuelve al programa llamante (aunque hubiese más instrucciones en la
función). Dentro del cuerpo de una función puede aparecer más de una sentencia RETURN. Sin
embargo, esto es una mala práctica de programación y debe ser evitado. Como buena norma de
programación, EXIGIREMOS que dentro del cuerpo de una función sólo haya una sentencia
RETURN y que ésta sea la última sentencia en el cuerpo de la función (salvo en el caso de
funciones recursivas de las que hablaremos en un tema posterior).
La llamada a un procedimiento constituye una sentencia del programa por sí sola. Sin embargo,
la llamada a una función representa un valor y no es una sentencia por sí sola. Para construir
una sentencia con una llamada a función debemos hacer algo con el valor que devuelve

Informática. Facultad de Ciencias (Matemáticas)


10 6.5 Procedimientos y Funciones

(asignarlo a una variable compatible, escribirlo por pantalla, hacer que forme parte de una
expresión, etc. ...). Una llamada a función sólo puede aparecer en donde pueda aparecer una
expresión del tipo de la función. Así, son llamadas válidas a funciones:
x := VolumenCubo(3.0); (* Si x es REAL *)
WrReal (Maximo(1.0, 2.0), 5, 0);
x := Maximo (Potencia(10.0, 5), Volumen(8.0)); (* Si x es REAL *)

Pero no lo es:
x := x + 1.0;
VolumenCubo(3.0);
...

Como ejemplo, las operaciones de entrada del módulo de biblioteca IO son funciones.

6.5.2 Funciones predefinidas


Modula-2 dispone de una serie de funciones predefinidas, que pueden usarse el cualquier
programa sin ser importadas. La lista de estas funciones es:
ABS(X) CAP(C) CHR(X) FLOAT(X) HIGH(A)
MAX(T) MIN(T) ODD(X) ORD(C) SIZE(T)
TRUNC(R) VAL(T,X)

En los argumentos C representa un valor de tipo carácter, X de tipo número, T el nombre de un


tipo y A un array (ver tema sobre tipos de datos estructurados). Hemos descrito todas en los
temas anteriores excepto HIGH(A) (que veremos en el tema sobre tipos de datos estructurados)
y VAL(T, X) que devuelve el valor de X convertido al tipo T. Algunas de estas funciones
utilizan tipos como argumentos, cosa que el programador no puede hacer con los subprogramas
definidos por él.

6.5.3 Procedimientos predefinidos


También existen procedimientos predefinidos en Modula-2. Estos son:
DEC(X): Decrementa en 1 el valor de la variable X. X debe ser de tipo ordinal.
DEC(X,N): Decrementa en N el valor de la variable X que debe ser de tipo
ordinal. N puede ser CARDINAL o INTEGER pero siempre positiva
EXCL(S,X): Excluye el elemento X del conjunto S (ver tema sobre tipos de datos
estructurados).
HALT: Hace que finalice la ejecución del programa. Esta instrucción sólo
debe utilizarse en caso de que el programa alcance un error y éste
impida que sea posible continuar con la ejecución del programa. La
instrucción HALT debe precederse de una instrucción de salida que
indique el error cometido. Nunca debe utilizarse esta sentencia para
otra cosa que no sea un error.
INC(X): Incrementa en 1 el valor de la variable X que debe ser de tipo ordinal.
INC(X,N): Incrementa en N el valor de la variable X que debe ser de tipo ordinal.
INCL(S,X): Incluye el elemento X en el conjunto S (ver tema sobre tipos de datos
estructurados)

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 11

6.6 Uso de parámetros


Los parámetros de un subprograma suelen utilizarse fundamentalmente de tres modos:
• Como parámetros de entrada
• Como parámetros de salida
• Como parámetros de entrada y salida

6.6.1 Parámetros de entrada


Un parámetro es de entrada cuando se usa para pasar un valor desde el programa llamante
al subprograma llamado. Este tipo de parámetros debe declararse por valor.
❏ Ejemplo: El siguiente subprograma escribe por pantalla la tabla de multiplicar
correspondiente al argumento que toma:
PROCEDURE EscribirTablaDe (Num : CARDINAL);
VAR i : CARDINAL;
BEGIN
FOR i := 1 TO 10 DO
WrCard(Num, 0); WrStr(“ * “); WrCard(i,0);
WrStr(“ = “); WrCard(Num*i, 0); WrLn
END
END EscribirTablaDe;

El modo de usar el suprograma es el siguiente:


...
WrStr(“La tabla del 7 es”); WrLn;
EscribirTablaDe (7);
...

6.6.2 Parámetros de salida


Un parámetro es de salida cuando se usa para pasar un valor desde el subprograma llamado
al programa llamante. Este tipo de parámetros debe declararse por referencia.
Para comunicar un valor desde un subprograma al programa llamante debe utilizarse una
función. Sin embargo, una función sólo puede devolver un resultado. Si el subprograma ha de
devolver más de un valor, es necesario utilizar parámetros de salida.
❏ Ejemplo: El siguiente subprograma convierte un punto expresado en coordenadas polares a
los valores correspondientes en coordenada cartesianas. Los dos primeros parámetros son de
entrada y los dos últimos de salida:
PROCEDURE PolaresACartesianas ( Modulo, Angulo :LONGREAL;
VAR EjeX, EjeY : LONGREAL );
BEGIN
EjeX := Modulo * Cos(Angulo);
EjeY := Modulo * Sin(Angulo)
END PolaresACartesianas;

El modo de usar el suprograma es el siguiente:


...
PolaresACartesianas (1.0, PI/2.0, x, y);
WrStr(“La coordenada X es ”); WrLngReal(x, PRECISION, ANCHO);
WrLn;

Informática. Facultad de Ciencias (Matemáticas)


12 6.7 Anidamiento de subprogramas. Ámbitos

WrStr(“La
... coordenada Y es ”); WrLngReal(y, PRECISION, ANCHO);

6.6.3 Parámetros de entrada y salida


Un parámetro es de entrada y salida cuando establece una comunicación en ambos
sentidos entre el programa llamante y el subprograma llamado. El parámetro se usa para pasar
un valor desde el programa llamante al subprograma, pero este valor puede también ser
modificado por el subprograma y el programa llamante debe, también, ver el cambio. Este tipo
de parámetros deben declararse por referencia.
❏ Ejemplo: El siguiente subprograma toma un argumento real y lo redondea:
PROCEDURE Redondear (VAR Num : REAL);
BEGIN
Num := REAL (TRUNC (Num + 0.5))
END Redondear;

El modo de usar el suprograma es el siguiente:


...
x := 10.7;
Redondear (x);
WrReal(x, 5, 0);
...


❏ Ejemplo: El siguiente subprograma intercambia los valores almacenados en dos variables de
tipo INTEGER, utilizando para ello una tercera variable temporal:
PROCEDURE Intercambiar (VAR Var1, Var2 : INTEGER);
VAR Temp : INTEGER;
BEGIN
Temp := Var1;
Var1 := Var2;
Var2 := Temp
END Intercambiar;

El modo de usar el suprograma es el siguiente:


...
x := 10;
y := 20;
Intercambiar(x,y);
WrStr(“El valor de x es “); WrInt(x,0);
WrLn;
WrStr(“El valor de y es “); WrInt(y,0);
...

6.7 Anidamiento de subprogramas. Ámbitos

6.7.1 Estructura de bloques


Un subprograma se define de forma similar a un programa completo. En la parte de
declaraciones de un subprograma pueden aparecer constantes, variables y otros subprogramas.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 13

Si un subprograma sólo se utiliza dentro de otro subprograma, debe aparecer declarado dentro
de éste. Esto da lugar a lo que se llama anidamiento de subprogramas: subprogramas
declarados dentro de otro subprograma.
Todo lo que aparece declarado dentro de un subprograma se dice que es local a éste. Esto
significa que las variables, constantes y procedimientos definidos dentro de un subprograma
sólo pueden ser usadas por el propio subprograma y por los subprogramas definidos dentro de
él, pero no por el programa principal o por los subprogramas externos. Así surge una estructura
de bloques, de modo que los bloques más internos pueden acceder a lo declarado en los bloques
que les anidan pero no al revés:

Bloque A
Bloque B
Bloque C
Bloque D

Bloque E
Bloque F

Bloque G

Figura 3. Visibilidad entre bloques

La siguiente tabla muestra qué declaraciones ve cada bloque:

Bloque Bloques a cuyas declaraciones accede


A A
B B, A
C C, B, A
D D, C, B, A
E E, A
F F, E, A
G G, E, A
Además, hay que tener en cuenta que un subprograma puede llamar a otro que esté previamente
declarado, por lo que E y F pueden llamar además al subprograma B, y G a F y B. En general, si
se utiliza un identificador en la declaración de otro identificador, el primero debe estar
declarado antes. Por ejemplo, si se desea utilizar una variable no local en un subprograma la
declaración de esta variable debe ser anterior a la declaración del subprograma.
Modulo-2 permite además que un subprograma se llame a sí mismo, que es lo que se denomina
subprograma recursivo. Hablaremos de esto con más detalle en un tema posterior.

Informática. Facultad de Ciencias (Matemáticas)


14 6.7 Anidamiento de subprogramas. Ámbitos

6.7.2 Redefinición de elementos


Dentro de cada bloque se deben declarar los elementos necesarios dándoles los nombres más
adecuados en cada caso. Puede ocurrir que dentro de un bloque se defina un elemento con el
mismo nombre que un elemento definido en otro bloque y que también sea accesible. En dicho
caso se pierde la accesibilidad al elemento más externo.
Sea el siguiente programa:
MODULE Ejemplo;
VAR a, b, c : INTEGER;

PROCEDURE Anidado1 (a : REAL);


VAR b : REAL;

PROCEDURE Anidado2;
VAR c, d : CHAR;
BEGIN
Cuerpo de Anidado2
END Anidado2;

BEGIN
Cuerpo de Anidado1
END Anidado1;

BEGIN
Cuerpo del principal
END Ejemplo.

El procedimiento Anidado1 tiene visibilidad, en principio, sobre las variables a, b y c del


programa principal por estar anidado dentro de él. Sin embargo, uno de sus parámetros se llama
a y una variable local b, por lo que pierde la accesibilidad sobre las variables a y b del
programa principal. Si accedemos a las variables a y b dentro del cuerpo de Anidado1,
estaremos accediendo a las suyas locales. Aún así, podremos acceder a la variable c del
principal, ya que no hay ninguna c local. En el cuerpo del procedimiento Anidado2, si
nombramos la variable c estaremos accediendo a la de tipo CHAR y si accedemos a a y b lo
haremos a las de Anidado1. La variable d sólo es visible desde el cuerpo de Anidado2.
Hay que señalar además que, si la declaración de las variables del programa principal, se
realizan después de las declaraciones de los subprogramas Anidado1 y Anidado2, éstos
pierden automáticamente el acceso a dichas variables: RECUERDE: SOLO SE PUEDE
ACCEDER A ELEMENTOS YA DEFINIDOS.

6.7.3 Efectos laterales


Hemos visto que un subprograma puede acceder a variables no locales a él, siempre que no
haya colisión de nombres. Cuando un subprograma accede a una variable que no es ni local a él
ni es un parámetro formal suyo decimos que se está produciendo un efecto lateral.
Este tipo de situaciones son válidas en Modula-2 pero nosotros las PROHIBIREMOS
totalmente. Cuando se declara un subprograma con una serie de parámetros se está diciendo
que el subprograma depende únicamente de ellos. Un efecto lateral traiciona este convenio y
hace que los programas sean menos claros y más difíciles de entender.
Siempre es posible que un subprograma no efectúe efectos laterales: si un subprograma necesita
una serie de valores deben aparecer todos en la cabecera de su declaración y si necesita
variables para realizar cálculos locales a él, se deben declarar como variables locales.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 6. Procedimientos y funciones 15

6.7.4 Doble referencia


La doble referencia se produce cuando un mismo elemento se referencia con dos nombres
distintos. Esto suele ocurrir en dos situaciones concretas:
1) Cuando un subprograma utiliza una variable externa que también se le pasa como
argumento
2) Cuando en la llamada a un subprograma se le pasa la misma variable en más de un
argumento.
En ambos casos se pueden producir resultados difíciles de entender. Veamos un ejemplo de la
primera situación:
...
VAR Global : INTEGER;
...
PROCEDURE Cuadrado (VAR Dato : INTEGER);
BEGIN
Global := 5;
Dato := Dato * Dato;
END Cuadrado;
...
Global := 3;
Cuadrado (Global);
...

Después de la llamada a Cuadrado(Global) la variable Global vale 25 cuando esperaríamos


que valiese 9. Este tipo de situaciones se puede evitar si no usamos efectos laterales.
Un ejemplo del segundo tipo de situación es:
PROCEDURE CuadradoCubo (VAR x1, x2, x3 : INTEGER);
BEGIN
x2 := x1*x1;
x3 := x1*x1*x1
END CuadradoCubo;

que devuelve el cuadrado y el cubo del primer argumento en los argumentos segundo y tercero,
y la siguiente llamada:
...
A := 4;
CuadradoCubo (A, A, B);
...

Tras la ejecución de este fragmento de programa, los valores de las variables son A = 16 y B =
4096, en vez de 64. Este problema se podría haber resuelto declarando el primer argumento
como parámetro por valor.

6.8 Sintaxis BNF para subprogramas


Veamos la sintaxis BNF para la declaración de subprogramas en Modula-2:

Declaración_Subprograma := Cabecera_Subprograma ;
Bloque
Identificador ;
Cabecera_Subprograma ::= Cabecera_de_Procedimiento | Cabecera_de_Función
Cabecera_de_Procedimiento ::= PROCEDURE Identificador [ Parámetros_Formales ]

Informática. Facultad de Ciencias (Matemáticas)


16 6.8 Sintaxis BNF para subprogramas

Cabecera_de_Función ::= PROCEDURE Identificador


Parámetros_Formales : Identificador_de_Tipo
Parámetros_Formales ::= ( [ Grupo_de_Parámetros { ; Grupo_de_Parámetros } ] )
Grupo_de_Parámetros ::= [ VAR ] Lista_de_Identificadores : Identificador_de_Tipo
Bloque ::= Parte_Declarativa Parte_Ejecutiva END
Parte_Declarativa ::= { Declaración }
Declaración ::= Declaración_de_Constantes | Declaración_de_Variables |
Declaración_Subprograma

La sintaxis para la sentencia RETURN es:

Sentencia_RETURN ::= RETURN [ Expresión ]

La llamada a un procedimiento es una sentencia:

Sentencia_de_Llamada_a_Procedimiento
::= Identificador [ Parámetros_de_Llamada ]
Parámetros_de_Llamada ::= ( [Lista_de_Expresiones] )
Lista_de_Expresiones ::= Expresión { , Expresión }

Por último la llamada a una función es un factor (ver definición de Expresión):

Factor ::= Variable | Identificador_de_Constante | ...


... | Llamada_a_Función
Llamada_a_Función ::= Identificador Parámetros_de_Llamada

Obsérvese que tanto en la llamada como en la definición de procedimientos, si no hay


argumentos, los paréntesis son opcionales, mientras que en las funciones son siempre
obligatorios.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 6) 1

Relación de Problemas 6
1. Supongamos que tenemos un programa principal con las siguientes variables:
VAR

x, y : REAL;
m : INTEGER;
c : CHAR;
n : CARDINAL;

y la siguiente declaración de subprograma:


PROCEDURE Prueba (a, b : INTEGER; VAR c, d : REAL;
VAR e : CHAR);

Averigua cuáles de las siguientes llamadas son válidas:


1) Prueba (m+3,10,x,y,c);
2) Prueba (n+3,10,x,y,c);
3) Prueba (m, 19, x, y);
4) Prueba (m, m*m, y, x, c);
5) Prueba (m, 10, 35.0, y, ‘E’);
6) Prueba (30, 10, x, x+y, c);
2. Escribe una función que calcule las combinaciones de m elementos tomados de n en n.
Para ello escribir primero otra función que calcule el factorial de un número.
NOTA: Recuérdese que

 m m!
 n  = n!(m − n)!
Escribe otra función más eficiente que no necesite calcular el factorial.
3. Escribe una función que tome tres parámetros, dos de tipo REAL y uno de tipo CHAR. La
función debe sumar, restar, multiplicar o dividir los valores de los dos primeros
parámetros dependiendo del código indicado en el tercero (+,-,*,/) y devolver el resultado.
Al diseñar esta función, supón que el tercer argumento es necesariamente uno de los
cuatro (+,-,*,/).
Escribe un programa que ofrezca al usuario la posibilidad de realizar una de esas cuatro
operaciones o salir del programa hasta que el usuario decida abandonar el programa, lea
los operandos necesarios para realizar la operación, utilice la función anterior para hacer
los cálculos y muestre el resultado en pantalla. Asegúrate de que el programa utiliza la
función anterior de forma correcta.
4. Dos números a y b se dice que son amigos si la suma de los divisores de a (salvo el
mismo) coincide con b y viceversa. Diseña un programa que tenga como entrada dos
número naturales n y m, y muestre por pantalla todas las parejas de números amigos que
existan en el intervalo determinado por n y m.

Informática. Facultad de Ciencias (Matemáticas)


2 Relación de Problemas (Tema 6)

5. Considera el siguiente procedimiento:


PROCEDURE Escr (C : CHAR; Long : INTEGER);
BEGIN
WHILE Long > 0 DO
WrChar (C);
Long := Long – 1
END
END Escr;

a) Si Ch tiene el valor ‘X’ y Numero el valor 5, ¿cuál sería el efecto de ejecutar cada
una de las siguientes llamadas al procedimiento?:
Escr(Ch,4*Numero-12) Escr(Ch,6) Escr(5,Numero)
Escr(‘/’,Numero) Escr(‘.’,6) Escr(‘p’,-10)

b) Escribe llamadas al procedimiento Escr para que cuando se ejecuten produzcan


las siguientes salidas:
• 35 guiones sucesivos
• 6 veces tantos espacios en blanco como el valor de Numero
• el valor actual de Ch 14 veces
6. Dadas las siguientes declaraciones en un determinado algoritmo:
VAR a,b,c : CARDINAL;
si : BOOLEAN;
...
PROCEDURE Uno(x, y : CARDINAL) : BOOLEAN;
...
PROCEDURE Dos(VAR x : CARDINAL; y : CARDINAL);
...
PROCEDURE Tres(x : CARDINAL) : CARDINAL;

¿ Cuáles de las siguientes llamadas a subprograma son válidas?


a) IF Uno(a,b) THEN...
b) Dos(a,b+3)
c) si := Uno(c,5)
d) si := Dos(c,5)
e) Dos(a,Tres(a))
f) Dos(Tres(b),c)
g) IF Tres(a) THEN ...
h) b := Tres(Dos(a,5))
i) Dos(4,c)
7. Escribe un algoritmo que tome como entrada desde teclado dos números naturales N e i, e
imprima en pantalla el dígito que ocupa la posición i-ésima del número N. Si i es mayor
que el número de dígitos de N, se escribirá en pantalla el valor -1. Por ejemplo, para N =
25064 e i = 2, el resultado es el dígito 6, y para i = 7, el resultado es -1.
8. ¿ Qué salida produce por pantalla la ejecución del siguiente algoritmo?
MODULE Anidado;
FROM IO IMPORT WrStr, WrCard, WrLn;
VAR a, b, c, x, y : CARDINAL;
PROCEDURE Primero;
BEGIN

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 6) 3

a := 3 * a;
c := c + 4;
WrStr (“Primero”);
WrCard (a, 0); WrCard (b, 0); WrCard (c, 0);
WrLn
END Primero;
PROCEDURE Segundo;
VAR b : CARDINAL;
BEGIN
b := 8;
c := a + c + b DIV 3;
WrStr (“Segundo”);
WrCard (a, 0); WrCard (b, 0); WrCard (c, 0);
WrLn
END Segundo;
PROCEDURE Tercero (VAR x : CARDINAL; y : CARDINAL);
BEGIN
x := x + 4;
y := y + 1;
WrStr (“Tercero”);
WrCard (a, 0); WrCard (b
, 0);
WrCard (c, 0); WrCard (x, 0);
WrLn
END Tercero;
BEGIN
a := 3;b := 2;
c := 1;x := 11;
y := 22;
Primero; Segundo;
Tercero (a,b);
WrStr (“Anidado”);
WrCard (a, 0); WrCard (b, 0);
WrCard (c, 0); WrCard (x, 0);
WrCard (y, 0)
END Anidado.

9. Realiza un procedimiento que intercambie el valor de dos variables de tipo CHAR.

10. Escribe un procedimiento que, dadas las coordenadas polares de un número complejo (r,
θ), obtenga las correspondientes cartesianas (x, y).
NOTA: Recordar que
x = r ⋅ cosθ
y = r ⋅ sen θ
11. Escribir un programa que lea un número positivo del teclado y escriba por pantalla los
números perfectos menores a él. Para ello definir primero una función EsPerfecto que
tome como parámetro un número y devuelva TRUE o FALSE dependiendo de que sea
perfecto o no.
12. Escribe un programa que lea un número positivo menor que 100 y escriba la traducción de
dicha cantidad a inglés. Por ejemplo para la entrada 42 se obtendría la salida “forty two”.

Informática. Facultad de Ciencias (Matemáticas)


4 Relación de Problemas (Tema 6)

Relación complementaria 6
1. Escribe un algoritmo que acepte como entrada desde teclado un número entero positivo.
El algoritmo debe producir como salida el resultado de sumar dos a dos los dígitos que
aparecen en posiciones simétricas respecto al dígito central dentro del número dado como
entrada. Por ejemplo, para el número 23548 la salida es: 2+8 = 10, 3 + 4 = 7, 5 para el
número 6582 la salida es: 6 + 2 = 8, 5 + 8 = 13
2. Escribe un subprograma para calcular el máximo común divisor de cuatro números
utilizando para ello otro subprograma que calcule el máximo común divisor de dos
números (algoritmo de Euclides).
3. Escribe un programa que lea dos enteros positivos correspondientes a un año y un mes, y
escriba el correspondiente calendario de la siguiente forma:
D L M X J V
S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29

Este problema puede dividirse en las siguientes partes:


1) Leer los valores del mes y año
2) Encontrar el día de la semana en que empieza el mes (ver ejercicio sobre
congruencia de Zeller).
3) Averiguar cuántos días tiene el mes.
4) Escribir el correspondiente calendario.
Usa un subprograma separado para cada parte.
4. Escribe un programa que lea un número positivo del teclado y escriba por pantalla los
números primos menores a él. Para ello define primero una función EsPrimo que tome
como parámetro un número y devuelva TRUE o FALSE dependiendo de que sea primo o no.
5. Escribe un programa que calcule el máximo de tres números enteros, definiendo
previamente una función que calcule el máximo de dos.
El ejercicio 3 se realizará como práctica en el laboratorio.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 7. Tipos de datos simples. 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Facultad de Ciencias (Matemáticas)
Ciencias de la Computación
http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 7. Tipos de datos simples


7.1 Concepto de tipo de datos. Clasificación................................. ......................... 2
7.1.1 Clasificación de tipos en Modula-2 ....................................................... 2
7.2 Tipos simples definidos por el programador.................................................... 4
7.2.1 Tipo enumerado ................................................................ ................... 4
7.2.2 Tipo subrango ................................................................ ...................... 6
7.3 Compatibilidad de Tipos en Modula-2 .............................................................. 7
7.4 Tipos procedimiento y función................................................................ ..........8

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.

Informática Facultad de Ciencias (Matemáticas)


2 7.1 Concepto de tipo de datos. Clasificación.

Introducción
En este tema definimos el concepto de tipo de dato en un lenguaje de programación. También se
clasifican los distintos tipos de datos que proporciona el lenguaje Modula-2. Además, se
presentan las declaraciones de tipos que permiten al programador definir nuevos tipos de datos.
En concreto, se estudian tres tipos de datos definibles por el programador: enumerados,
subrangos y tipos subprogramas. Por último, se definen las reglas que sigue el compilador para
detectar errores de tipo en un programa.

7.1 Concepto de tipo de datos. Clasificación.


Un tipo de datos es una descripción formal del conjunto de valores ( o dominio ) que una
variable o expresión de dicho tipo puede tener, junto con el conjunto básico de operaciones que
pueden ser aplicadas a estos valores.
En un lenguaje de alto nivel el concepto de tipo es de una gran importancia. En Modula-2, la
declaración de una variable debe ir acompañada por la especificación de su tipo, ya que éste
determina el espacio de memoria requerido para su almacenamiento y las combinaciones de
operadores y operandos permitidos. El tipo de una constante puede ser deducido
automáticamente por el compilador.
El aspecto práctico más importante de los datos es el modo en que pueden ser manipulados. Para
ello, a cada tipo de datos se le asocia un conjunto de operadores básicos. La selección de estos
operadores básicos es en cierta medida arbitraria, y podría haberse aumentado o disminuido. El
criterio habitualmente seguido es seleccionar el conjunto mínimo de operadores que permita al
programador construir cualquier operación de un modo razonablemente eficiente.
Los operadores más importantes definidos para cualquier tipo de dato son:
• La asignación (:= ). Evalúa la expresión a su derecha y guarda el resultado en la
variable a su izquierda.
• La verificación de igualdad ( = ). Comprueba si los valores a su izquierda y a su
derecha son iguales.
Un mismo símbolo (por ejemplo los dos operadores anteriores) puede utilizarse como operador
para distintos tipos de datos. Esto se denomina sobrecarga. EL operador + también está
sobrecargado, ya que puede usarse para sumar valores de tipo INTEGER, CARDINAL y REAL
(aunque los tipos de los dos argumentos han de ser compatibles).

7.1.1 Clasificación de tipos en Modula-2


Podemos clasificar los tipos que aparecen en el lenguaje Modula-2 como:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 7. Tipos de datos simples. 3

• Tipos simples (escalares)


• Ordinales
• Predefinidos
• CARDINAL, LONGCARD
• INTEGER, LONGINT
• CHAR
• BOOLEAN
• Definidos por el programador
• Enumerados
• Subrango
• REAL, LONGREAL
• Tipos procedimiento y función
• Tipo estrucuturados
• ARRAY
• RECORD
• SET
• FILE
• POINTER
Todos los tipos simples son tipos escalares, ya que:
a) Están formados por elementos indivisibles
b) Están ordenados, es decir, se pueden comparar usando =, <>, >, ...
Todos los tipos simples, excepto los tipos reales, son ordinales:
c) Además de a) y b) cada valor (excepto el primero y el último) tiene un predecesor y un
sucesor único.
Para todos los tipos ordinales podremos usar las siguientes operaciones:
• ORD(X) es una función que devuelve un CARDINAL (de cero en adelante),
correspondiente al orden de X.
• VAL(T,X) es una función que dado un tipo T y un número de orden X devuelve el valor de
tipo T con dicho orden. Nótese que ORD(X) = VAL (CARDINAL, X) (X de un tipo ordinal)
y CHR(X) = VAL(CHAR, X) (X valor entero entre 0 y 255).
• INC(X) es un procedimiento que asigna a la variable X el sucesor del valor que contenga.
Es un error utilizar INC(X) si X contiene el último valor posible para el tipo.
• DEC(X) es un procedimiento que asigna a la variable X el antecesor del valor que
contenga. Es un error utilizar DEC(X) si X contiene el primer valor posible para el tipo.
• INC(X,N) y DEC(X,N). Aplican N veces INC(X) o DEC(X) respectivamente. N puede ser
de tipo INTEGER o CARDINAL.
• MAX(T) es una función que devuelve el máximo valor posible para el tipo T.
• MIN(T) es una función que devuelve el mínimo valor posible para el tipo T.
Recordemos que la variable de control de un bucle FOR debe ser ordinal y que el tipo de la
expresión en una sentencia CASE también.
Los operadores tienen que aplicarse a objetos de tipos de datos compatibles (iguales o no). La
información del tipo de dato ayuda a los compiladores a detectar operaciones inapropiadas con
tipos de datos no compatibles.

Informática Facultad de Ciencias (Matemáticas)


4 7.2 Tipos simples definidos por el programador

7.2 Tipos simples definidos por el programador


Aunque los tipos simples predefinidos vistos son útiles para cualquier propósito (que no requiera
tipos estructurados), es conveniente que el programador pueda definir sus propios tipos simples
por dos razones principalmente:
1) Interpretación de cada valor. Si una variable representa un número de naranjas y otra un
número de personas, y representamos ambas variables con tipo CARDINAL podríamos
sumar naranjas y personas, lo cual no tiene mucho sentido. Sería mejor poder definir un
tipo NARANJAS y otro PERSONAS.
2) Dejar claro el rango de los posibles valores. Si sabemos a priori que para resolver cierto
problema el valor de una variable nunca va a salirse de cierto rango, es conveniente
declarar la variable con un nuevo tipo que se corresponda a dicho rango para que, en el
caso de que por error tome un valor fuera de este rango, el compilador nos avise de ello.
La especificación de nuevos tipos se realiza usando una declaración de tipo. Por ejemplo:
TYPE COLOR = ...
VAR Color1, Color2: COLOR;

Una declaración de tipo debe hacerse en la parte declarativa del programa antes de la declaración
de variables de ese tipo y consiste en la palabra reservada TYPE seguida por el nombre del tipo,
el signo igual y la especificación del tipo. En notación BNF:

Declaracion_de_Tipo ::= TYPE Identificador_de_Tipo = Tipo_Enumerado |


Tipo__Subrango |
Tipo_Función |
Tipo_Procedimiento | ...
Identificador_de_Tipo ::= Identificador

7.2.1 Tipo enumerado


Consiste en una enumeración de todos los posibles valores que puede tomar una variable de
dicho tipo encerrados entre paréntesis. Una variable de tipo enumerado sólo podrá tomar, en un
determinado momento, uno de esos valores.
El uso de tipos enumerados ayuda a mejorar la legibilidad de los algoritmos cuando los valores
de ciertas variables tienen una determinada interpretación. Si quisiéramos definir una variable
para que represente una situación de un problema en el que hay cinco posibles colores, (Rojo,
Amarillo, Verde, Azul y Naranja) podríamos hacer lo siguiente:
CONST
Rojo = 0 ;
Verde = 1 ;
Azul = 2 ;
Amarillo = 3 ;
Naranja = 4 ;
VAR
Color1, Color2 : CARDINAL ;

Sin embargo, en Modula-2 podemos definir un nuevo tipo ordinal que conste de esos cinco
valores exactamente:
TYPE COLOR = (Rojo, Verde, Azul, Amarillo, Naranja);
VAR Color1, Color2 : COLOR;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 7. Tipos de datos simples. 5

La declaración de un tipo de esta índole consiste en asociar a un identificador una enumeración


de los posibles valores que una variable de ese tipo puede tomar.
Otros ejemplos de tipos enumerados son:
TYPE
PALO = (Oros, Copas, Espadas, Bastos);
DIA = (Lunes, Martes, Miercoles, Jueve
s, Viernes,
Sabado, Domingo);
SEXO = (Hombre, Mujer);
FIGURA = (Circulo, Triangulo, Cuadrado, Rectangulo);

Un mismo valor NO PUEDE aparecer la declaración de DOS TIPOS ENUMERADOS distintos.


Es decir, dada la declaración anterior la siguiente declaración NO es válida, ya que Rojo, Verde y
Azul están usados en el tipo COLOR:
TYPE COLORBASICO = (Rojo, Verde, Azul);

El orden de los valores de estos nuevos tipos declarados por el programador será aquel en que
aparecen dentro de la lista de enumeración de los elementos del tipo. El tipo enumerado es
también escalar y ordinal.
La función ORD devuelve el número ordinal de un valor perteneciente a un tipo enumerado. La
función VAL produce el efecto contrario:
Expresión Valor
ORD(Rojo) 0
ORD(Azul) 2
VAL(COLOR,1) Verde
VAL(COLOR,4) Naranja

Al tratarse de tipos ordinales, los valores de tipo enumerado tienen su predecesor (excepto el
primero del tipo) y sucesor (excepto el ultimo del tipo), por lo que es válido:
IF Color1 < Azul THEN ...
FOR Color1 := Rojo TO Azul DO...
CASE Color1 OF
Rojo: ...

No existen operaciones aritméticas definidas para los tipos enumerados. Por lo tanto, es
INVÁLIDO:
Color1 := Color1 + 1;

Sí se pueden utilizar, en cambio, los procedimientos generales INC y DEC.


Hay que tener en cuenta que se pueden producir errores al emplear los procedimientos INC y DEC
si se excede el rango del tipo de la variable:
VAR Color1 : COLOR;
i : INTEGER;
...
Color1 := Rojo;
i := 1000;
INC (Color1); ---> Color1 = Amarill
o
INC (i); ---> i = i + 1 = 1001
DEC (Color1, 4); ---> Error
INC (i,40000); ---> Error
...

Informática Facultad de Ciencias (Matemáticas)


6 7.2 Tipos simples definidos por el programador

Los valores de tipo enumerado no pueden leerse ni escribirse directamente. Es tarea del
programador el implementar los procedimientos y funciones adecuados. Por ejemplo el siguiente
procedimiento puede ser usado para escribir un valor del tipo COLOR:
PROCEDURE WrColor(c : COLOR);
BEGIN
CASE c OF
Rojo : WrStr (“Rojo”) |
Verde : WrStr (“Verde”) |
Amarillo : WrStr (“Amarillo”)
|
Azul : WrStr (“Azul”) |
Naranja : WrStr (“Naranja”)
END
END WrColor;

La sintaxis BNF para la declaración de un tipo enumerado es:

Tipo_Enumerado ::= (Lista_de_Identificadores) ;

Por último comentemos que el tipo BOOLEAN es realmente un tipo enumerado definido como:
TYPE BOOLEAN = (FALSE, TRUE);

para el cual han sido definidos otros operadores adicionales: AND, OR y NOT.

7.2.2 Tipo subrango


Siempre que podamos, debemos definir tipos cuyo rango de valores esté contenido en el rango de
valores de otros tipos ordinales, ya que ayuda a clarificar el rango de valores que pueden tomar
determinadas variables, evitando o descubriendo posibles errores. Los tipos con esta
característica se llaman tipos subrango. Por ejemplo:
TYPE
COLOR = (Rojo, Verde, Azul, Amaril
lo, Naranja);
EDAD = [0..130]; (* Subrango de CARDINAL *)
NOTA = [0..10]; (* Subrango de CARDINAL *)
DIADELMES = [1..31]; (* Subrango de CARDINAL *)
LETRAMINUSCULA = ['a'...'z']; (* Subrango de CHAR *)
ENTEROPEQUENYO = [-10..10]; (* Subrango de INTEGER *)
COLORPRIMARIO = [Rojo..Azul]; (* Subrango de COLOR *)

VAR EdadAlumno: EDAD;


NotaAlumno: NOTA;

Los valores de un tipo subrango deben ser un rango de valores CONSECUTIVOS pertenecientes
a un tipo ya definido, al cual se le denomina tipo base. SÓLO tipos ORDINALES pueden ser
tipos base.
En la definición del tipo se puede indicar el tipo base:
Edad = [0..130]; (* Subrango de CARDINAL *)
Edad = INTEGER [0..130]; (* Subrango de INTEGER *)

pero se puede también omitir, pues el compilador puede calcularlo a partir del tipo de las
expresiones en todos los casos, salvo si es incapaz de decidir si se trata de CARDINAL o
INTEGER, en cuyo caso se toma por defecto el tipo CARDINAL.
Uno de los motivos para usar los tipos subrango o enumerado es detectar cierta clase de fallos en
la que se dan valores fuera de rango. No obstante, por defecto el compilador no detecta este tipo
de errores, por lo cual se producen resultados indefinidos en tiempo de ejecución de los
programas. En Top-Speed, si queremos que lo haga hay que indicárselo expresamente con una
José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA
Carmen M. García López
Tema 7. Tipos de datos simples. 7

anotación en el programa. Es lo que se llama una directiva para el compilador, que se sitúa al
principio del programa escribiendo (*#check (range=>on) *). El mismo efecto se consigue
poniendo a on la opción correspondiente dentro del submenú Runtime cheks del menú Project.
En caso de que en ejecución se presente un error de los que controla dicha directiva, aparecerá un
mensaje de error indicándolo y preguntará si queremos ir al lugar del fichero fuente en el que se
produjo. Hay que tener en cuenta que cuando está activa esta directiva la velocidad de ejecución
decrece apreciablemente, por lo que normalmente sólo se usa en las etapas iniciales del
desarrollo de un programa, de forma que cuando éste ha sido probado y suponemos que funciona
se vuelve a compilar, pero sin indicar la directiva, con lo que el código objeto obtenido es más
eficiente.
La sintaxis BNF para definir tipos subrango es:

Tipo_Subrango ::= [Identificador_de_Tipo] [Expresión_Constante.. Expresión_Constante] ;

Las expresiones constantes deben tener tipo ordinal.


Como tipo ordinal que es, al tipo subrango también se le pueden aplicar las funciones que vimos
en el primer apartado de este tema.

7.3 Compatibilidad de Tipos en Modula-2


Modula-2 es un lenguaje fuertemente tipado. Esto significa que el compilador chequea los
tipos de los valores que aparecen en las sentencias y expresiones para comprobar el uso correcto
de los mismos. Para ver las reglas que se siguen para ello definiremos previamente:
• Tipos Equivalentes: Se dice que dos tipos son equivalentes si tienen el mismo
nombre, o están derivados del mismo nombre de tipo. Ejemplo:
TYPE
T1 = REAL;
T2 = T1;
T3 = T1;

Son todos tipos equivalentes.


Se requiere que los tipos de los parámetros formales y reales sean equivalentes, si el paso
de éstos es por referencia.
• Tipos Compatibles: Dos tipos T1 y T2 son compatibles, si y sólo si es cierta una de
las siguientes condiciones:
1) T1 y T2 son tipos equivalentes
2) T2 es subrango de T1 o viceversa.
3) T1 y T2 son subrangos del mismo tipo base.
Modula-2 requiere compatibilidad de tipos para:
1) Los operandos de una expresión aritmética.
2) Los operandos de una expresión relacional.
3) Las etiquetas y expresión de una sentencia CASE.
4) El valor inicial, límite y variable de control de un bucle FOR.
• Compatibilidad de asignación: Dos tipos son compatibles en la asignación si son
compatibles o si ambos son INTEGER o CARDINAL, o bien subrangos de los tipos
INTEGER o CARDINAL. Ejemplo:

Informática Facultad de Ciencias (Matemáticas)


8 7.4 Tipos procedimiento y función

TYPE
T1 = [0..10];
T2 = [-3..3];

Modula-2 requiere este tipo de compatibilidad para:


1) La variable y la expresión de una sentencia de asignación.
2) El tipo del índice de un ARRAY y la expresión usada como índice (Tema 8).
3) Un parámetro por valor y su correspondiente formal.
4) EL tipo del resultado que devuelve una función y el valor que sigue al RETURN
de dicha función.

7.4 Tipos procedimiento y función


En Modula-2 es posible declarar variables de tipo subprograma (procedimientos o funciones) y
asignar valores a éstas. Los valores asignables son nombres de subprogramas. Dichas variables
pueden ser usadas como identificadores de subprogramas en una sentencia de llamada a
procedimiento o función y el subprograma que se llame será el último que se haya asignado a la
variable. Por ejemplo, el siguiente programa pide al usuario que elija una entre varias funciones,
un punto x y evalúa la función en dicho punto:
MODULE Funciones
FROM IO IMPORT WrStr, WrLn, WrLngReal, RdLngReal, RdLn, RdChar;
FROM MATHLIB IMPORT Sin, Sqrt;
CONST
PRECISION = 5;
TYPE
FUNCION = PROCEDURE (LONGREAL) : LONGREAL;
VAR
f : FUNCION;
x : LONGREAL;
Opcion : CHAR;

PROCEDURE Cuadrado ( X : LONGREAL) : LONGREAL;


BEGIN
RETURN x*x
END Cuadrado;

PROCEDURE Cubo ( X : LONGREAL) : LONGREAL;


BEGIN
RETURN x*x*x
END Cubo;

BEGIN
WrStr (“Dame x:”);
x := RdLngReal(); RdLn;

WrStr (“1. Cuadrado”); WrLn;


WrStr (“2. Cubo”); WrLn;
WrStr (“3. Seno”); WrLn;
WrStr (“4. Raiz cuadrada”);
WrLn;

WrStr (“Qué función: “);


Opcion = RdChar(); RdLn;

CASE Opcion OF
‘1’ : f := Cuadrado |

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 7. Tipos de datos simples. 9

‘2’ : f := Cubo |
‘3’ : f := Sin |
‘4’ : f := Sqrt
END;

WrStr (“El valor de la función en el punto es: “);


WrLngReal (f(x), PRECISION, 0)
END Funciones.

La variable f es una variable de tipo función. En concreto se puede asignar a f el nombre de


cualquier función que tome un valor LONGREAL y devuelva un valor LONGREAL. El tipo
FUNCION es el tipo de todas las funciones con rango un valor LONGREAL y dominio un valor
LONGREAL. No se puede asignar a f el nombre de otra función que no tenga este tipo. Por
supuesto, podemos declarar distintos tipos funciones correspondientes a otras clases de
funciones:
TYPE
FUNCION2 = PROCEDURE (INTEGER, INTEGER) : INTEGER;
FUNCION3 = PROCEDURE (CHAR) : INTEGER;

FUNCION2 es el tipo de aquellas funciones que toman dos enteros y devuelven un entero.
Similarmente FUNCION3 es el tipo de las funciones que toman un carácter y devuelven un valor
entero.
Podemos observar que se pueden asignar funciones importadas de otros módulos como son Sin y
Sqrt, siempre y cuando sean del tipo adecuado. No podremos asignar nombres de subprogramas
anidados. Sólo se pueden asignar nombres de subprogramas definidos en el nivel más externo o
importados de otros módulos. Tampoco se pueden asignar nombres de operadores (+, - , ...).
Todo lo explicado para funciones vale para procedimientos. Tipos procedimientos válidos son:
TYPE
PROCEDIMIENTO1 =PROCEDURE (INTEGER, INTEGER);
PROCEDIMIENTO2 =PROCEDURE (CHAR);

La sintaxis BNF para los tipos función y procedimientos son:

Tipo_Procedimiento ::= PROCEDURE [Lista_de_Tipos_Formales ] ;


Tipo_Función ::= PROCEDURE Lista_de_Tipos_Formales : Identificador_de_Tipo ;
Lista_de_Tipos_Formales ::= ( [ Tipo_Formal { , Tipo_Formal } ] )
Tipo_Formal ::= [ VAR ] Identificador_de_Tipo

Informática Facultad de Ciencias (Matemáticas)


Tema 7. Tipos de datos simples. 1

Relación de Problemas (Tema 7)


1. Escribe un programa que solucione el sistema de ecuaciones:
ax + by = c
dx + ey = f
Para ello diseña un subprograma que tome como parámetros por valor a, b, c, d, e y f ,
como parámetros de salida por referencia x, y y Estado, donde Estado es del tipo
enumerado:
TYPE Estado = (Determinado, Indeterminado, Incompatible);
Si el sistema está determinado x e y contendrán la solución al sistema y Estado valdrá
Determinado. En otro caso Estado valdrá Indeterminado o Incompatible y se enviará
un mensaje al usuario indicando el estado del sistema.
El programa principal deberá leer los coeficientes, llamar al subprograma y escribir el
resultado por pantalla.
NOTA: Si ae - bd = 0 y bf - ec = 0 el sistema es indeterminado. Si ae-bd = 0 pero bf-ec ≠0
el sistema es incompatible.
2. Escribir una función que tome como parámetros dos funciones f y g ambas de rango y
dominio LONGREAL, un valor x de tipo LONGREAL y devuelva f compuesta de g evaluada
en el punto x. Probar la función con casos concretos de f y g.
NOTA: ( f g )( x ) = f ( g ( x ))
3. Escribir un subprograma que tome como parámetro una función f de rango y dominio
LONGREAL y un valor x de tipo LONGREAL, y devuelva un valor aproximado de la
derivada de f en dicho punto utilizando para ello la siguiente fórmula con ε igual a una
millonésima.
f ( x + ε ) − f ( x)
f '( x ) =
ε
Escribir un programa que utilice la función anterior para calcular el valor de la derivada
de las funciones seno y coseno en los puntos π y π/2.
4. Escribir una función que encuentre un cero de una función utilizando el algoritmo de
bipartición. Este algoritmo toma como parámetros, entre otros, una función f (de dominio
y rango LONGREAL que suponemos continua), y tres valores a, b y Epsilon de tipo
LONGREAL, y devuelve el cero calculado. El algoritmo debe comprobar que el valor de
b sea mayor que a y que el signo de f(a) es distinto que el signo de f(b) (condiciones del
Teorema de Bolzano). A partir de aquí se repite el siguiente proceso: calcular el punto
medio del intervalo [a,b] y llamarlo c. Si f(c) = 0, entonces hemos encontrado un cero de
f en c y acabamos. En otro caso determinamos en qué mitad del intervalo [a,b] se produce
el cambio de signo ([a,c] o [c,b]) y repetimos el proceso con dicha mitad. Todo este
proceso se repite hasta que se da la condición anterior (f(c) = 0) o hasta que el ancho del
intervalo considerado sea menor que Epsilon. Utilizar la función anterior para encontrar
un cero de la función coseno en [0, π].
5. Escribir una función Pliega que tiene cuatro parámetros: f, a, b, Inicial. El primer
parámetro es una función que toma dos enteros y devuelve un entero. Los otros tres
parámetros son valores enteros. El resultado de la función Pliega es de tipo entero y debe
ser el resultado de calcular la expresión:

Informática Facultad de Ciencias (Matemáticas)


2 Relación de Problemas (Tema 7)

f ( f ( ...f ( f ( f ( Inicial, a), a+1), a+2), ... b-1), b).


Es decir , calculamos primero f con parámetros Inicial y a y obtenemos un resultado.
Con este resultado y a+1 volvemos a calcular f. Seguimos así hasta que llegamos a que el
segundo parámetro es b.
Escribir un programa que utilice la función Pliega para calcular el factorial de un
número N leído de teclado y el sumatorio de los primeros N naturales, donde N es un
número leído del teclado
6. Realizar un procedimiento Filtra que toma tres parámetros: f, a y b. f es una función
que toma un entero y devuelve un booleano. a y b son dos valores enteros. Filtra deberá
escribir por pantalla todos los valores enteros x en el intervalo [a..b] para los cuales f(x)
sea cierto.
Utilizar el procedimiento Filtra en un programa que escriba los números perfectos y
los números primos menores a un valor Maximo dado por teclado.
7. Escribir una función que calcule el valor de la integral definida
b

∫ f ( x )dx .
a

Esta función tomará como parámetros: una función f de dominio y rango LONGREAL,
los valores a y b de tipo LONGREAL, y un parámetro Epsilon también LONGREAL.
Para ello dividiremos el intervalo [a,b] en intervalos de amplitud menor o igual a Epsilon
y aproximaremos la integral de cada subintervalo con la fórmula del trapecio:

x1 − x 0
x1

∫ f ( x)dx ≅ 2
[ f ( x0) + f ( x1)]
x0

El valor de ∫ f ( x )dx será el sumatorio de las aproximaciones anteriores. Utilizar la


a
función anterior para calcular la integral definida de la función seno entre 0 y π/2.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 9. Almacenamiento externo de la información 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de Facultad de Ciencias (Matemáticas)
la Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 9. Almacenamiento externo de la información


9.1. Ficheros ................................................................ ................................ .............2
9.1.1 Ficheros en MS-DOS ................................................................ .................... 2
9.2. Tipos de ficheros................................................................ ................................ 3
9.2.1 Clasificación de ficheros según su contenido................................................ 3
9.2.1.1 Ficheros de texto................................................................ ..................... 3
9.2.1.2 Ficheros binarios ................................................................ ..................... 4
9.2.2 Clasificación de ficheros según su acceso .................................................... 4
9.2.2.1 Ficheros secuenciales ................................................................ .............4
9.2.2.2 Ficheros con acceso directo................................................................ ....4
9.3 Gestión de ficheros................................................................ ............................. 4
9.3.1. Manejadores de ficheros ................................................................ ..............4
9.3.2 Apertura y cierre de ficheros ................................................................ .........5
9.3.3 Escritura de ficheros ................................................................ ..................... 6
9.3.4 Lectura de ficheros ................................................................ ....................... 7
9.3.4.1 Lectura de múltiples datos desde teclado................................................ 8
9.3.4.2 Diferencia entre la lectura de teclado y de fichero ...................................9
9.3.4.3 Detección del fin de fichero ................................................................ ...10
9.3.4.4 Lectura de cadenas de caracteres. RdStr.............................................. 10
9.3.4.5 Convenios para la escritura en ficheros de texto ...................................11

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.
• Fundamentos de programación. L. Joyanes. McGraw-Hill.

Informática. Facultad de Ciencias (Matemáticas)


2 9.1. Ficheros

Introducción
En este tema estudiamos el tipo de dato File de TopSpeed Modula-2 y las operaciones para el
manejo de éste que se encuentran en la biblioteca FIO.

9.1. Ficheros
En el tema 1 estudiamos los tipos de memoria existentes en un ordenador: memoria principal
(RAM) y memoria masiva (discos, cintas magnéticas,...). Vimos que para que un programa se
ejecutase era necesario que éste y los datos que manejase estuviesen cargados en memoria
principal. También estudiamos las características de ambos tipos de memoria, que podemos
resumir en:
• memoria principal: rápida, capacidad limitada y volátil.
• memoria masiva: más lenta, mayor capacidad y no volátil.
Todos los datos que se han manipulado hasta el momento en los algoritmos presentados tienen
una característica común: están almacenados en memoria principal. Al ser la memoria principal
volátil, la consecuencia inmediata es que el contenido de estos datos se pierde al apagar el
ordenador (e incluso cuando termina el programa que se está ejecutando). Sin embargo, los
ordenadores suelen utilizarse para almacenar datos que serán recuperados posteriormente. Para
conseguir esto son necesarios mecanismos que permitan:
• copiar el contenido de la información situada en memoria principal sobre la memoria
secundaria (antes de terminar la ejecución del programa)
• recuperar el contenido de información almacenada previamente en memoria secundaria.
Para trabajar con datos almacenados en memoria secundaria es necesario declarar variables
donde temporalmente se copien éstos durante la ejecución del programa y operaciones
adicionales que establecen la conexión entre dichas variables en memoria principal y los datos
en memoria secundaria.
En Modula-2 esta conexión se consigue mediante el tipo de datos fichero (File) y sus
operaciones asociadas. En TopSpeed Modula-2, el módulo de biblioteca FIO contiene todos los
servicios para el tratamiento de ficheros, incluyendo la definición del tipo File, que no es un
tipo predefinido en Modula-2.
Nota: el modulo FIO es propio de TopSpeed Modula-2 y no es estándar. Otros compiladores
pueden no contar con las operaciones que veremos en este tema, aunque poseerán algunas
similares.
En la memoria secundaria la información está organizada en archivos o ficheros. Un fichero se
puede definir como una colección de información o elementos lógicamente relacionados y
almacenados en memoria secundaria. En realidad un fichero es tan solo una colección de bytes,
pero éstos pueden estar organizados jerárquicamente en estructuras que facilitan y aumentan la
eficiencia de las operaciones sobre el mismo.

9.1.1 Ficheros en MS-DOS


Dentro de un mismo dispositivo de almacenamiento secundario (disco duro, disquete, cinta
magnética, CD-ROM, ...) pueden almacenarse varios ficheros. Cuando queremos acceder a un
fichero lo hacemos mediante su nombre. El nombre del fichero es una cadena de caracteres
que debe seguir ciertas reglas, las cuales dependen del sistema operativo que utilice el
ordenador. En nuestro caso, el sistema operativo MS-DOS exige que el nombre del fichero esté

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 9. Almacenamiento externo de la información 3

formado por no más de ocho caracteres. El primer carácter deberá ser obligatoriamente una
letra (no podrá ser un número ni un signo). Tampoco podremos utilizar el espacio en blanco
como parte del nombre de un fichero. Adicionalmente podremos añadir no más de tres letras al
fichero precedidas del carácter punto. A esto se llama extensión del fichero y se suele usar para,
siguiendo cierto convenio, indicar el tipo de datos almacenado en el fichero. Por último, hemos
de comentar que MS-DOS no distingue entre letras mayúsculas y minúsculas en el nombre de
un fichero. Así, los nombres de fichero “Datos”, “DATOS” y “datos” se refieren todos a un
mismo fichero.
Algunos ejemplos de nombres de fichero son:
• “Datos”: nombre de fichero de cinco letras sin extensión
• “Programa.mod”: nombre de fichero con ocho letras y extensión de tres letras. La
extensión “mod” se suele usar para ficheros cuyo contenido es el texto de un programa
escrito en el lenguaje Modula-2.
• “Programa.exe”: nombre de fichero con ocho letras y extensión de tres letras. La
extensión “exe” se suele utilizar para ficheros cuyo contenido es un programa
ejecutable, resultado de compilar un programa.

9.2. Tipos de ficheros


Simplificando mucho, un fichero es tan solo una secuencia de bytes que contienen cierta
información. Consideramos a continuación un par de criterios para clasificar ficheros: su
contenido y el modo en que se puede acceder a la información que contienen.

9.2.1 Clasificación de ficheros según su contenido


Uno de los criterios que podemos seguir para clasificar ficheros es el tipo de información que
almacenan. De este modo podemos distinguir entre ficheros de texto y binarios.
9.2.1.1 Ficheros de texto
El contenido del fichero es una secuencia de caracteres pertenecientes a un determinado código
de entrada/salida, normalmente el código ASCII. Suelen estar compuestos por una serie de
líneas.
Un ejemplo típico de fichero de texto es aquel que almacena el texto correspondiente a un
programa escrito en Modula-2. Las líneas del fichero corresponden a las distintas líneas del
programa que contiene.
Cuando desde un programa escrito en Modula-2 se almacena el contenido de un valor cadena
de caracteres en un fichero de texto, sólo es necesario copiar los caracteres desde memoria
interna al fichero correspondiente. Algo similar sucede para operaciones de lectura.
Sin embargo, se necesita más trabajo cuando se pretenden escribir valores de otros tipos
(INTEGER, CARDINAL, REAL, ...). En este caso hay que realizar una conversión de la
representación interna en memoria del valor (una serie de bytes que representan el número en
binario como puede ser 00011001) a la correspondiente secuencia de caracteres que lo
representan externamente (en este caso podría ser “25”). En las operaciones de lectura se
realiza la conversión inversa. Los subprogramas en la biblioteca FIO se encargan de esta tarea.
La principal ventaja que presenta este tipo de ficheros es que una persona puede entender
directamente su contenido si lo inspecciona.
Nota: la conversión también ocurre en las lecturas/escrituras en dispositivos estándares
(teclado/pantalla).

Informática. Facultad de Ciencias (Matemáticas)


4 9.3 Gestión de ficheros

9.2.1.2 Ficheros binarios


En lugar de realizar las conversiones anteriormente comentadas para almacenar caracteres en un
fichero, las propias representaciones internas de los datos pueden copiarse en un fichero
directamente. Un fichero creado de esta forma se denomina binario.
La principal ventaja de este tipo de ficheros es que las operaciones con ellos son más rápidas
(no es necesario realizar la transformación desde la representación interna de memoria a la
cadena de texto correspondiente). La principal desventaja es que su contenido no puede ser
entendido directamente por una persona al inspeccionarlo.
Modula-2 también provee operaciones en el modulo de biblioteca FIO para el manejo de este
tipo de ficheros, aunque no las estudiaremos en este curso.

9.2.2 Clasificación de ficheros según su acceso


Si atendemos al modo en que se puede acceder a la información almacenada en el fichero
podemos clasificar éstos como: ficheros secuenciales y ficheros con acceso directo.
9.2.2.1 Ficheros secuenciales
Un fichero secuencial es aquel en el cual para acceder al elemento del contenido que ocupa la
posición i-ésima hay que acceder previamente a los i-1 elementos previos.
9.2.2.2 Ficheros con acceso directo
Un fichero con acceso directo es aquel que permite acceder al elemento que ocupa la posición i-
ésima directamente (al estilo del acceso en un ARRAY) sin pasar necesariamente por los
elementos previos.
La biblioteca FIO de TopSpeed Modula-2 posee operaciones para el manejo de ficheros
secuenciales y con acceso directo, aunque en este curso sólo estudiaremos las primeras.

9.3 Gestión de ficheros


Los ficheros se suelen utilizar de tres modos:
• para escribir datos en ellos
• para leer datos contenidos en ellos
• para modificar o almacenar información adicional a la que ya contenían
A la hora de utilizar un fichero debemos indicar de qué modo vamos a utilizarlo. Veremos en
los puntos siguientes como hacerlo.
La gestión básica de ficheros en TopSpeed Modula-2 se realiza a través de la biblioteca FIO.
Para utilizar los servicios ofrecidos por esta biblioteca, es necesario importar dicho módulo.

9.3.1. Manejadores de ficheros


Hemos visto que un fichero se identifica mediante su nombre y que antes de utilizarlo hay que
indicar de qué modo va a ser utilizado (para lectura, escritura o ambas). En Modula-2, para
utilizar un fichero hay que abrirlo previamente mediante una de las siguientes operaciones:
Open, OpenRead, Create o Append. Al utilizar las operaciones anteriores se especifica el
nombre del fichero que se va a utilizar. Estas operaciones devuelven un valor de tipo File, que
es conocido como manejador de fichero. Posteriormente cuando queramos leer o escribir
información en el fichero NO utilizaremos el nombre de éste, sino el manejador que obtuvimos

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 9. Almacenamiento externo de la información 5

al abrirlo. Cada fichero abierto tiene un manejador de fichero diferente. Cuando hayamos
terminado de trabajar con el fichero debemos cerrarlo mediante la operación Close.
Asociada a cada fichero abierto existe una cabeza de lectura/escritura. Ésta indica la
posición dentro del fichero de donde se leerá (o en donde se escribirá) información la próxima
vez que se ejecute una operación de lectura (o escritura). En el apartado siguiente veremos que,
dependiendo de la operación de apertura utilizada, la cabeza se colocará inicialmente al
principio o al final del fichero. También veremos que las operaciones de lectura y escritura
avanzan automáticamente esta cabeza cada vez que se ejecutan.

9.3.2 Apertura y cierre de ficheros


Para la apertura de un fichero existen diversas posibilidades. Hay cuatro formas de apertura.
• Creación de un nuevo fichero: en este caso usaremos la función Create:
PROCEDURE Create(Nombre: ARRAY OF CHAR): File;
El parámetro de esta función es el nombre del fichero que se desea crear.
• Si el fichero no existe, se crea uno nuevo vacío. Después de esta operación el fichero
queda abierto, listo para ser usado por el programa.
• Si el fichero indicado ya existe, su contenido se borra y se abre como si se hubiese
creado uno nuevo.
El valor devuelto por dicha función es el manejador asociado a dicho fichero. Este
manejador sirve para poder referenciar al fichero que se acaba de crear. La cabeza de
lectura/escritura se sitúa al principio del fichero.
• Apertura de un fichero ya existente para lectura o escritura. En este caso usamos la
función Open:
PROCEDURE Open(Nombre: ARRAY OF CHAR): File;
• Si el fichero ya existe, abre el fichero especificado para lectura o escritura, e
inicializa la cabeza de lectura/escritura al principio del mismo.
• Si el fichero no existe se produce un error en tiempo de ejecución.
• Apertura de un fichero ya existente sólo para lectura. En este caso se usa la función
OpenRead:
PROCEDURE OpenRead(Nombre: ARRAY OF CHAR): File;
En este caso, sólo podemos realizar operaciones que lean datos del fichero. Si usamos
operaciones que intenten modificar el contenido de éste se produce un error en tiempo
de ejecución.
• Apertura de un fichero ya existente para añadir datos al final. En este caso usamos la
función Append:
PROCEDURE Append(Nombre: ARRAY OF CHAR): File;
• Si el fichero ya existe, Append abre el fichero especificado para lectura o escritura, e
inicializa la cabeza de lectura/escritura al final del mismo. Por lo tanto, se utiliza
cuando se quiere añadir datos detrás de los ya existentes.
• Si el fichero no existe se produce un error en tiempo de ejecución.
Siempre hay que cerrar un fichero cuando se termina de trabajar con él. El procedimiento
Close se utiliza para ello.
PROCEDURE Close(Fich: File);

Informática. Facultad de Ciencias (Matemáticas)


6 9.3 Gestión de ficheros

Este procedimiento toma como parámetro el manejador del fichero abierto que queremos cerrar.
Si hemos de determinar si un fichero existe o no antes de elegir la operación con la cual abrirlo,
podemos utilizar la función Exists:
PROCEDURE Exists(Nombre: ARRAY OF CHAR): BOOLEAN;
Esta función toma como parámetro el nombre de un fichero y devolverá TRUE si el fichero ya
existe, o FALSE en caso contrario.
Veamos un ejemplo de uso de las operaciones anteriores. Se trata de un programa para añadir
nueva información al final del fichero “Datos.txt”. Previamente comprobamos si el fichero
existe (en cuyo caso lo abrimos con Append) o no (en este caso se crea con Create). Una vez
añadida la información al fichero, se cierra:
MODULE Fichero;
FROM FIO IMPORT File, Create, Append, Exists, Close;
CONST
NOMBREFICHERO = “Datos.txt”;

VAR
Manejador : File;

BEGIN
IF Exists(NOMBREFICHERO) THEN
Manejador := Append(NOMBREFICHERO)
ELSE
Manejador := Create(NOMBREFICHERO)
END;
...
(* Operaciones de escritura *)
...
Close(Manejador)
END Fichero.

9.3.3 Escritura de ficheros


Las operaciones de escritura en ficheros son similares a las de escritura por pantalla, pero
toman un parámetro adicional que es el manejador del fichero en el cual queremos escribir el
dato. Algunas de estas operaciones son:
PROCEDURE WrChar(Fich:File, V:CHAR);
PROCEDURE WrInt(Fich:File, V:INTEGER, Long:INTEGER);
PROCEDURE WrLngInt(Fich:File, V:LONGINT, Long:INTEGER);
PROCEDURE WrCard(Fich:File, V:CARDINAL, Long:INTEGER);
PROCEDURE WrLngCard(Fich:File, V:LONGCARD, Long:INTEGER);
PROCEDURE WrReal(Fich:File, V:REAL, Pre:CARDINAL, Lon:INTEGER);
PROCEDURE WrLngReal(Fich:File, V:LONGREAL, Pre:CARDINAL,Lon:INTEGER);
PROCEDURE WrFixReal(Fich:File, V:REAL, Pre:CARDINAL, Lon:INTEGER);
PROCEDURE WrFixLngReal(Fich:File, V:LONGREAL, Pre:CARDINAL,Lon:INTEGER);
PROCEDURE WrStr(Fich: File, V:ARRAY OF CHAR);
PROCEDURE WrLn(Fich: File);
Estos procedimientos escriben los datos que toman como parámetros en el fichero indicado a
partir de la posición actual de la cabeza de lectura/escritura. La posición de la cabeza se
incrementa automáticamente para que apunte a la siguiente posición a los caracteres escritos.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 9. Almacenamiento externo de la información 7

Veamos un ejemplo. Supongamos que hemos abierto un fichero con la operación Create. Esta
operación deja la cabeza al principio del fichero por lo que tenemos la siguiente situación:

Si a continuación utilizamos la operación de escritura de enteros en ficheros


WrInt(Manejador,123,0) la situación del fichero pasa a ser:

‘1’ ‘2’ ‘3’


El procedimiento WrLn escribe una indicación de fin de línea en el fichero. Esta indicación
consiste en escribir los caracteres CHR(13) y CHR(10) en el fichero en este orden exactamente.
Si llamamos a WrLn(Manejador) el estado del fichero queda:
CHR(13) CHR(10)
‘1’ ‘2’ ‘3’
Los procedimientos WrFixReal y WrFixLngReal se usan para escribir valores reales en
notación fija (no científica).
Obsérvese que el nombre de los procedimientos coincide con el nombre de los procedimientos
de salida por pantalla del módulo IO. Si nos interesa utilizar un procedimiento del módulo IO y
otro de FIO con el mismo nombre en un mismo programa, podemos utilizar la importación de
módulos completos y referirnos a la operación concreta precediéndola del nombre de la
biblioteca y un punto:
MODULE Importar;
IMPORT IO, FIO;
...
BEGIN
...
(* Escribe por pantalla *)
IO.WrStr(“A pantalla”);
...
(* Escribe en un fichero *)
FIO.WrStr(Manejador, “A fichero”);
...
END Importar.

Nota Importante: La lectura de los datos almacenados en el fichero debe respetar una serie de
convenios, por lo tanto, dichos datos se deben escribir (tanto con un programa en Modula-2
como de cualquier otra forma, por ejemplo con un editor) teniendo en cuenta que dichos datos
serán posteriormente leídos. Estos convenios tienen que ver con el funcionamiento de las
funciones de lectura/escritura de Modula-2.

9.3.4 Lectura de ficheros


El módulo FIO también contiene operaciones que permiten leer datos desde un fichero. Algunas
de éstas son:
PROCEDURE RdChar(Fich:File): CHAR;
PROCEDURE RdInt(Fich:File): INTEGER;
PROCEDURE RdLngInt(Fich:File): LONGINT;
PROCEDURE RdCard(Fich:File): CARDINAL;
PROCEDURE RdLngCard(Fich:File): LONGCARD;
PROCEDURE RdReal(Fich:File): REAL;
PROCEDURE RdLngReal(Fich:File): LONGREAL;
PROCEDURE RdStr(Fich: File, VAR V:ARRAY OF CHAR);

Informática. Facultad de Ciencias (Matemáticas)


8 9.3 Gestión de ficheros

Estos subprogramas se comportan de forma parecida a sus homólogos del módulo IO, salvo que
los caracteres se toman del fichero especificado como primer parámetro, a partir de la posición
indicada por la posición de lectura/escritura. Tras la lectura, dicha posición se incrementa para
que apunte a la siguiente posición a los caracteres leídos.
El procedimiento RdStr se comporta de forma diferente al homólogo de IO, por lo que lo
estudiaremos con más detenimiento.
9.3.4.1 Lectura de múltiples datos desde teclado
Hasta ahora hemos usado siempre la instrucción RdLn tras cualquier operación de entrada desde
teclado. Vamos a explicar en este punto porqué ha sido esto necesario y cómo funciona la
lectura desde teclado más detalladamente.
Sabemos que cuando un programa escrito en Modula-2 llega a una operación de entrada desde
teclado se detiene, muestra un cursor parpadeante y espera a que el usuario introduzca un dato.
El programa no reanuda su ejecución hasta que el usuario pulsa la tecla especial <ENTER>.
Los datos tecleados por el usuario se almacenan en una zona llamada buffer del teclado. La
pulsación de la tecla <ENTER> hace que se almacenen los caracteres CHR(13) y CHR(10) al
final del buffer del teclado. Ahora bien, nada impide que el usuario teclee más de un dato
cuando aparece el cursor parpadeante. Supongamos que un programa ejecuta la instrucción
N:=RdInt() y que el usuario escribe 12, un espacio en blanco, 15 y entonces pulsa <ENTER>.
El contenido del buffer del teclado queda como se indica:

‘1’ ‘2’ ‘b’ ‘1’ ‘5’ CHR(13) CHR(10)

Como RdInt lee del teclado hasta encontrar un carácter que no forme parte de un número, N
toma el valor 12 y la cabeza de lectura/escritura queda en el espacio en blanco (la lectura desde
teclado no salta el separador final):

‘1’ ‘2’ ‘b’ ‘1’ ‘5’ CHR(13) CHR(10)

El resto de la información queda almacenada en el buffer del teclado, de modo que si el


programa ejecuta otra operación M:=RdInt() no llega a detenerse ya que hay información
suficiente en el buffer. En este caso M tomaría el valor 15 y el buffer del teclado quedaría:

‘1’ ‘2’ ‘b’ ‘1’ ‘5’ CHR(13) CHR(10)

La ejecución de un programa no se detiene en una operación de lectura desde teclado hasta que
se agota el contenido del buffer. En ese momento el programa se detendrá a la espera de que el
usuario introduzca más datos.
Lo único que hace la operación de lectura RdLn es vaciar el buffer del teclado. Si llamamos a
RdLn tras cualquier operación de lectura desde teclado, borramos cualquier información extra
que el usuario haya podido introducir.
A veces puede ser interesante no llamar a RdLn tras una operación de entrada. El siguiente
programa pide al usuario que introduzca diez números de una vez y los lee en un ARRAY:
MODULE SinRdLn;
FROM IO IMPORT RdInt, RdLn, WrStr;
CONST
MAXIMO = 10;
VAR
Vector : ARRAY [1.. MAXIMO] OF INTEGER;
i : CARDINAL;
BEGIN
WrStr(“Dame 10 nºs separados por espacio y pulsa ENTER: ”);
FOR i := 1 TO MAXIMO DO
Vector[i] := RdInt()

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 9. Almacenamiento externo de la información 9

END;
RdLn
...
END SinRdLn.

9.3.4.2 Diferencia entre la lectura de teclado y de fichero


Supongamos que el buffer de teclado contiene la siguiente información (el próximo carácter a
leer es el enmarcado en trazo doble).
‘b’ ‘b’ ‘1’ ‘2’ ‘3’ ‘b’ ‘A’ ...
Después de realizar la operación N:=RdInt(), N valdrá 123 y el buffer queda como sigue:
‘b’ ‘b’ ‘1’ ‘2’ ‘3’ ‘b’ ‘A’ ...
si ahora realizamos la operación C:=RdChar(), C contiene el carácter blanco ‘b’.
Veamos ahora lo que sucede en el caso de lectura de ficheros (la posición de lectura/escritura se
muestra en trazo doble):
‘b’ ‘b’ ‘1’ ‘2’ ‘3’ ‘b’ ‘A’ ...
Después de realizar la operación N:=RdInt(Manejador), N también vale 123 pero la posición
de lectura/escritura queda como sigue:
‘b’ ‘b’ ‘1’ ‘2’ ‘3’ ‘b’ ‘A’ ...
con lo que si realizamos la operación C:=RdChar(Manejador) C contendrá el carácter ‘A’.
La diferencia fundamental entre la lectura desde el teclado y la lectura desde fichero es que las
operaciones de lectura desde teclado no saltan el separador (en este caso el blanco) que origina
el fin del número, mientras que las operaciones de lectura desde fichero sí lo hacen (avanzan
una posición más).
Nótese que las funciones para leer enteros, cardinales y reales saltan los separadores (‘b’,
CHR(9), CHR(10), CHR(13), CHR(26)) que se encuentran antes de los datos a leer. En las
bibliotecas IO y FIO se encuentra una variable de tipo SET OF CHAR que contiene los valores
que se consideran separadores. Esta variable es Separators y se puede modificar utilizando
los procedimientos INCL y EXCL.
Si el dato introducido por el usuario no representa un número válido del tipo que se intenta leer
el valor devuelto por las funciones de lectura es indefinido. Se puede importar la variable
booleana OK del módulo IO (o de FIO si estamos leyendo desde ficheros) que se pone a FALSE
si el dato introducido por el usuario no fue válido. El siguiente programa pide un dato de tipo
CARDINAL por teclado y no cesa hasta conseguirlo:
MODULE Leer;
IMPORT IO;
VAR
c : CARDINAL;
BEGIN
LOOP
IO.WrStr("Dame un valor CARDINAL: ");
c := IO.RdCard();
IF IO.OK THEN
EXIT
END;
IO.WrStr("Valor no válido. Prueba de nuevo");
IO.WrLn
END;
IO.WrStr("El valor leído es: ");
IO.WrCard(c, 0)

Informática. Facultad de Ciencias (Matemáticas)


10 9.3 Gestión de ficheros

END Leer.
9.3.4.3 Detección del fin de fichero
Para controlar correctamente la lectura de datos de un fichero, necesitamos algún mecanismo
para saber si existen más datos en éste o no, es decir, si la posición de lectura/escritura se
encuentra al final del fichero.
Para ello se utiliza la variable booleana EOF (siglas de End Of File) que se importa del módulo
FIO. Su utilización es similar a la de la variable OK ya vista. Si la variable EOF es TRUE,
indica que en la última operación de lectura de fichero que se intentó, la cabeza de
lectura/escritura ya estaba sobre el final del fichero. Obsérvese que HAY QUE INTENTAR
LEER ALGO Y LUEGO COMPROBAR SI LO LEÍDO ES VÁLIDO en este orden
exactamente.
Veamos un programa que muestra el contenido de un fichero de texto por pantalla:
MODULE Fichero;
IMPORT IO, FIO;
CONST
MAX = 20;
TYPE
Nombre = ARRAY [1..MAX]OF CHAR;
VAR
C : CHAR;
Fich : Nombre;
Manejador : FIO.File;
BEGIN
IO.WrStr("Introduce el nombre del fichero: ");
IO.RdStr(Fich);
Manejador := FIO.Open(Fich); (* Apertura de fichero *)
LOOP
C := FIO.RdChar(Manejador); (* Lectura de datos *)
IF FIO.EOF THEN (* Fin de fichero ? *)
EXIT
END;
IO.WrChar(C); (* Escribir en pantalla *)
END;
FIO.Close(Manejador);
END Fichero.

9.3.4.4 Lectura de cadenas de caracteres. RdStr


El comportamiento del procedimiento RdStr del módulo FIO es diferente a su homólogo del
módulo IO. Su comportamiento es como se indica a continuación:
Se leen caracteres del fichero especificado hasta que:
1) Se encuentra los caracteres correspondientes a un fin de línea (CHR(13) y CHR(10)).
Estos caracteres, sin embargo, no se almacenan en la cadena leída. Se coloca en su lugar
un carácter CHR(0).
2) Se encuentra el fin de fichero. En este caso también se añade un carácter CHR(0) al final
de la cadena.
3) Se llena la cadena argumento.
Algunas consideraciones sobre esta operación son:
• Si los primeros caracteres leídos son CHR(13) y CHR(10), la cadena contiene la cadena
vacía (una cadena con el carácter CHR(0) en su primera posición).

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 9. Almacenamiento externo de la información 11

• Si la llamada a RdStr es seguida por una llamada a RdChar, ésta lee el primer carácter
del fichero que no leyó RdStr. Este carácter dependerá de la causa que motivó el fin de
la operación RdStr. Para cada uno de los casos anteriores tenemos:
1) Se leerá el primer carácter de la siguiente línea. Nótese que los caracteres de fin de
línea son saltados.
2) Se producirá fin de fichero.
3) Se leerá el siguiente carácter a los que se leyeron.
9.3.4.5 Convenios para la escritura en ficheros de texto
Las funciones para leer enteros, cardinales y reales desde un fichero leen hasta encontrarse un
carácter que no corresponda a un número de dicho tipo. Este carácter (que llamaremos
separador) es leído también. Como consecuencia de esto, resulta que es necesario escribir algún
carácter especial (como puede ser un espacio en blanco) siempre que escribamos un dato
numérico en un fichero y queramos que éste pueda ser leído posteriormente. Si no seguimos
este convenio no podremos recuperar la información almacenada posteriormente con éxito. En
efecto, supongamos que NO lo hacemos y que escribimos dos enteros (10 y 25) en un fichero:
...
Manejador := FIO.Open(Fich);
FIO.WrInt(Manejador, 10, 0);
FIO.WrInt(Manejador, 25, 0);
...

el contenido del fichero sería:

‘1’ ‘0’ ‘2’ ‘5’ ‘b’ ...


Por lo cual, si posteriormente abrimos el fichero para lectura e intentamos leer un número
obtendremos 1025. Sin embargo, si seguimos el convenio indicado:
...
Manejador := FIO.Open(Fich);
FIO.WrInt(Manejador, 10, 0);
FIO.WrChar(Manejador, ‘ ‘);
FIO.WrInt(Manejador, 25, 0);
FIO.WrChar(Manejador, ‘ ‘);
...

el contenido del fichero es ahora


‘1’ ‘0’ ‘b’ ‘2’ ‘5’ ‘b’ ‘b’ ...
y los dos enteros almacenados pueden ser leídos con éxito:
...
Manejador := FIO.OpenRead(Fich);
a := FIO.RdInt(Manejador);

b := FIO.RdInt(Manejador);
...

Por otro lado, la función de lectura de cadenas de caracteres RdStr lee hasta fin de línea, por lo
tanto se debe escribir el fin de línea después de escribir en el fichero una cadena de caracteres,
si queremos que sea posible leerla posteriormente con RdStr:
...
Manejador := FIO.Open(Fich);
FIO.WrStr(Manejador, “Hola”);
FIO.WrLn(Manejador);
...

Informática. Facultad de Ciencias (Matemáticas)


Relación de Problemas (Tema 9) 1

Relación de problemas (Tema 9)


1. Escribe un programa que lea del teclado el nombre de tres ficheros y escriba en el tercero
el contenido del primero seguido del contenido del segundo.
2. Escribe un programa que codifique y decodifique ficheros de texto usando el método de
cifrado César. Para codificar un fichero de texto (cuyo nombre y extensión se leerá de
teclado) se generará otro fichero de texto con el mismo nombre y con extensión “.COD”.
La codificación consistirá en reemplazar cada carácter del fichero original por el tercero
siguiente según la tabla ASCII (Por ejemplo, el carácter ‘a’ será sustituido por el carácter
‘d’). Si el carácter a codificar es CHR(10) o CHR(13) no se cambiará al codificarlo. La
opción de decodificación deberá leer de teclado el nombre de un fichero codificado y
recuperar en otro fichero con mismo nombre y con extensión “.DEC” la información
original.
NOTA: El desplazamiento en tres caracteres debe ser circular tanto en un sentido como en
otro. Por ejemplo, el último carácter de la tabla ASCII (CHR(255)) es codificado como
CHR(2).
3. Diseña un programa que cree un fichero que contenga los datos relativos a los artículos de
un almacén. Para cada artículo hay que guardar la siguiente información:
Código del artículo (Numérico)
Nombre del artículo (Cadena de caracteres)
Existencias actuales (Numérico)
Precio (Numérico).
Los datos de los distintos artículos se pedirán del teclado y se almacenarán previamente
en un array. Supondremos que el número máximo de artículos que se pueden introducir es
treinta. Una vez leídos, los datos se ordenarán en el array (el orden será ascendente según
el código del artículo) y se grabarán en el fichero.
4. Escribe un programa que a partir de dos ficheros generados por el programa anterior
(recordar que los artículos están ordenados por código) genere un tercer fichero que sea el
resultado de mezclar de formar ordenada los dos primeros. Escribe otro programa que
haga lo mismo pero con n ficheros (n será un número positivo leído de teclado).
5. Escribe un programa que tome como entrada un fichero generado por el programa del
ejercicio 3 y una condición sobre el campo referente a las existencias actuales. La
condición podrá ser:
[<,<=,>,>=, <>, =] <número>
Es decir, que las referencias actuales sean menores a un número dado, o menores o
iguales, o mayores, etc.
El programa debe generar como salida un fichero llamado "salida.dat" que contenga
todos aquellos artículos para los que se cumple la condición de entrada.
6. Escribe un programa que tenga como entrada el nombre de un fichero que contenga un
texto y muestre en pantalla una estadística de la longitud de las distintas palabras que
contiene (número de palabras de cada longitud), así como cuántas veces aparecen los
caracteres separadores (",", ".", ":", ";", "b").
7. Escribe un programa que a partir de un fichero de entrada que contiene números enteros
positivos expresados en cualquier base entre 2 y 10, genere un listado por pantalla y un
fichero de salida que contenga las mismas cantidades del fichero de entrada, pero

Informática. Facultad de Ciencias (Matemáticas)


2 Relación de Problemas (Tema 9)

expresadas en base 10.


El fichero de entrada contendrá en cada línea la base y el número separados por el carácter
‘/’.
Ejemplo:
Fichero de Entrada:
2/101
5/412
9/111
10/923
Salida por pantalla: Se deberá generar un listado por pantalla con las siguientes líneas:
Número de entrada 2/101 Resultado 5
Número de entrada 5/412 Resultado 107
Número de entrada 9/111 Resultado 91
Número de entrada 10/923 Resultado 923
El contenido del fichero de salida será:
5 107 91 923
8. Escribe un programa que busque en un archivo de texto, que contiene números enteros
separados por un espacio, los valores máximo y mínimo y los muestre por pantalla.
9. Escribe un programa que calcule el número de líneas que han de leerse de un archivo de
texto hasta que se hayan leído del fichero todas las letras minúsculas del alfabeto al menos
una vez. El programa debe mostrar también el número total de caracteres leídos hasta ese
momento. Si se alcanza el final del fichero y no se consigue el objetivo, el programa
mostrará un mensaje por pantalla que lo indique.
10. Escribe un programa que a partir de un fichero de texto, cree otro que sólo contenga
aquellas líneas del primero en las que figure una palabra introducida previamente por el
usuario desde el teclado.
NOTA: Suponer que la longitud máxima de una línea del fichero es 128 caracteres, que
las palabras están separadas por espacios en blanco o saltos de línea y que los únicos
caracteres que aparecen en el texto son letras, espacios en blanco y los caracteres que
forman el salto de línea.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 9) 3

Relación complementaria (Tema 9)


11. Escribe un programa que mezcle dos archivos de texto, que contengan números enteros
separados por espacios, y que estén ordenados ascendentemente. El fichero resultante
deberá estar también ordenado en orden ascendente.
12. Escribe un programa que a partir de los nombres de dos ficheros de texto que contienen
valores cardinales en el rango [1..1000] separados por espacios, genere dos nuevos
ficheros de texto con la unión e intersección de los dos ficheros anteriores.
13. Se desea escribir un programa que resuelva el juego Sopa d e Letras. Para ello, se
considerarán las siguiente especificaciones:
• La tabla de caracteres que forma la sopa de letras tendrá como dimensiones 15
filas por 15 columnas.
• En fichero de entrada "SOPA.DAT" contendrá las distintas líneas que forman la
tabla.
• En fichero "PATRONES.DAT" contendrá las palabras a buscar dentro de la SOPA.
• Sólo será necesario buscar los patrones dentro de la SOPA por filas y por
columnas en ambos sentidos (No será necesario buscar en las diagonales).
La salida del programa será un listado por pantalla mostrando cada palabra encontrada
junto con las coordenadas donde fue hallada.
14. Mejora el programa anterior para que busque también en diagonales
15. Se desea ordenar un fichero de números enteros que no cabe en la memoria central del
ordenador. Se opta por ordenarlo considerándolo como un array en disco. Para ello será
necesario escribir los siguientes subprogramas:
1) PROCEDURE Longitud (NombreFich : NOMBREFICH) : LONGCARD; Devuelve
la cantidad de números enteros almacenada en el fichero que toma como
argumento.
2) PROCEDURE LeerFich (NombreFich : NOMBREFICH; Posicion :
LONGCARD) : INTEGER; Devuelve el valor del número que está situado en la
posición indicada dentro del fichero.
3) PROCEDURE EscribirFich (NombreFich : NOMBREFICH; Posicion :
LONGCARD; Valor : INTEGER); Modifica el contenido del fichero, de modo que
el valor del número que ocupa la posición indicada pasa a ser Valor. El resto de
los números en el fichero permanecen iguales.
Utilizando estos subprogramas, diseña el programa que ordene el fichero y muestre el
resultado por pantalla.
16. Otra alternativa al procedimiento anterior es dividir el fichero inicial en subficheros de
como máximo 100 bytes. Cada uno de estos ficheros se carga en memoria en un vector de
tamaño 50, se ordena en el vector y se vuelca en el correspondiente subfichero.
Posteriormente se mezclan los subficheros obteniéndose el fichero original ordenado.
Escribe un programa para esto.

Nota: Los problemas 14, 15 y 16 presentan un alto nivel de dificultad.

Informática. Facultad de Ciencias (Matemáticas)


Tema 10. Diseño modular 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de la Facultad de Ciencias (Matemáticas)
Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema 10. Diseño modular


10.1 Los módulos de Modula-2................................................................ ................. 2
10.2 Ventajas de la modularización de programas.................................................... 2
10.3 Módulos de biblioteca ................................................................ ....................... 3
10.3.1 Módulos de definición ................................................................ ................. 3
10.3.2 Módulos de implementación................................................................ ........4
10.3.3 Inicialización de un módulo ................................................................ .........6
10.3.4 Usos de los módulos de biblioteca .............................................................. 7
10.4. Compilación separada ................................................................ ..................... 8

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de Educación
a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.

Informática Facultad de Ciencias (Matemáticas)


2 10.1 Los módulos de Modula-2

Introducción
En este tema estudiamos el sistema de módulos del lenguaje Módula-2. Este mecanismo es
esencial para poder escribir programas reales (con cierto tamaño). Además, hace posible la
creación de módulos de biblioteca reutilizables.

10.1 Los módulos de Modula-2


En programación, un módulo es un fragmento de un programa que se desarrolla de forma
independiente del resto del programa. Esta independencia hace posible un mecanismo de
compilación por separado que limita la complejidad del programa que se está desarrollando. Al
compilarse el módulo por separado, la persona que lo desarrolla sólo debe preocuparse de él,
prescindiendo en parte de cómo se utiliza este módulo dentro del programa. Quien escriba el
resto del programa no debe preocuparse de los detalles del módulo sino sólo de cómo utilizarlo.
Los módulos son, probablemente, la característica más innovadora del lenguaje Modula-2. Hay
cuatro tipos de módulos:
• módulos de programa
• módulos internos
• módulos de definición
• módulos de implementación
Los módulos de programa son unidades de programa completas que pueden importar recursos
(constantes, variables, procedimientos..) de módulos de biblioteca. Son módulos de programa
todos los que hemos utilizado hasta el momento.
Los módulos de definición e implementación son las dos partes que constituyen los módulos
de biblioteca.
Los módulos internos o locales pueden ser definidos dentro del cuerpo de, bien módulos de
programa, bien módulos de implementación, y se usan para ayudar a controlar el ámbito y la
visibilidad de los distintos objetos.
Un programa en Modula-2 consta de un módulo de programa y cualquier número de módulos
de biblioteca, de los que el módulo de programa importa entidades (constantes, tipos, variables,
funciones y procedimientos). Estos módulos de biblioteca pueden a su vez importar de otros
módulos de biblioteca.
Cada sistema Modula-2 suministra un conjunto de bibliotecas básico. El programador tiene,
además, la posibilidad de crear módulos de biblioteca adicionales para usar en uno o más
programas.
Los módulos usan listas de importación para indicar los recursos externos que van a utilizar.

10.2 Ventajas de la modularización de programas


Los módulos son una herramienta indispensable para crear programas reales cuyo tamaño sea
grande. Un programador debe diseñar un programa grande como un conjunto de módulos,
relacionados entre sí mediante interfaces definidas apropiadamente. Escribir y depurar el
programa es más fácil porque el programador puede trabajar con un módulo cada vez, usando
los servicios facilitados por otros módulos pero ignorando los detalles de cómo estos módulos
trabajan (principio de abstracción). Este tipo de diseño modular es particularmente

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 10. Diseño modular 3

necesario cuando el programa se está desarrollando entre un conjunto de programadores, que es


lo más habitual en cualquier programa de cierta envergadura.
Los programas modulares son más fáciles de modificar. Puesto que los detalles de
implementación de un módulo se ocultan a los demás, se pueden cambiar detalles de un módulo
sin afectar al resto.
La modularización hace los programas mas portables. El programador puede ocultar los
detalles dependientes de la máquina en un único módulo, de forma que cuando se transporte a
otro ordenador, sólo debe preocuparse de reescribir dicho módulo.
Cada módulo se compila por separado. El programa se divide en trozos, que el compilador
puede procesar separadamente. De esta forma, un cambio en un módulo sólo requiere volver a
compilar dicho módulo, no el programa completo.
El desarrollo de bibliotecas con código reutilizable conlleva no sólo un ahorro de trabajo, sino
además un aumento de la fiabilidad del programa, pues dichas bibliotecas están más probadas
que si la parte de la biblioteca que se usa se codifica de nuevo.

10.3 Módulos de biblioteca


Los módulos de biblioteca se usan para exportar recursos a otros módulos. El programador que
utiliza estos módulos sólo necesita conocer cuales son los recursos disponibles pero no cómo
están implementados. Un módulo de biblioteca consta de dos partes, la parte de definición y la
parte de implementación. La parte de definición contiene sólo las definiciones de entidades
que el módulo exporta; define la interfaz del módulo de biblioteca con el resto del programa,
informa al programador de los recursos existentes en la biblioteca. La parte de implementación
describe cómo implementar los servicios indicados en la parte de definición. Para resaltar que
cada parte es por sí sola una unidad de programa, la primera es llamada DEFINITION
MODULE, y la segunda IMPLEMENTATION MODULE. Cada una está contenida en un
fichero. En TopSpeed Modula-2, el nombre del fichero que contiene la parte de definición debe
tener la extensión .DEF, mientras que el fichero que almacena la parte de implementación
deberá tener la extensión .MOD.

10.3.1 Módulos de definición


Un módulo de definición contiene definiciones de constantes, tipos, variables y subprogramas.
Las definiciones son similares a las declaraciones, pero pueden tener menos información.
• Constantes
Son idénticas a las declaraciones de constantes normales.
• Tipos.
Podemos definir tipos de dos formas. Una posibilidad es usar una declaración de tipo normal:
TYPE
COMPLEJO = RECORD
PReal, PImag : REAL;
END;

En este caso se llama tipo transparente y la estructura del tipo es visible en los módulos que lo
importen. Un módulo que importe el tipo COMPLEJO, sabe que el tipo está definido como un
registro con dos campos cuyos nombres son campos PReal y PImag, y puede acceder a éstos.
Otra alternativa consiste en omitir el signo de igualdad y la especificación del tipo:
TYPE

Informática Facultad de Ciencias (Matemáticas)


4 10.3 Módulos de biblioteca

COMPLEJO;
A un tipo definido de esta forma se le llama tipo opaco. Un módulo que lo importe no conoce
su estructura. En este caso, sólo se pueden efectuar operaciones de asignación o de
comparación de igualdad y desigualdad sobre variables declaradas de dicho tipo opaco, además
de las operaciones definidas en el módulo de definición para dicho tipo. La definición completa
del tipo opaco debe aparecer en el módulo de implementación y deberá ser un tipo puntero (ver
tema 12).
• Variables.
Las definiciones de variables son idénticas a las declaraciones de variables normales:
VAR
EsCorrecto : BOOLEAN;

• Subprogramas.
La definición de un subprograma consta de la cabecera del mismo. La declaración completa
debe aparecer en el módulo de implementación.
Un módulo de definición puede incluir también listas de importación; sólo debe importar
aquellas entidades externas (normalmente tipos) que se usen en el módulo de definición. Es un
error común importar aquí entidades que sólo son necesarias en la parte de implementación.
La forma exacta de escribir un módulo de definición se describe mediantes las siguiente reglas
BNF:

Módulo_definición ::= Cabecera_definición {Definición_de_elementos } END Identificador.


Cabecera_definición ::= DEFINITION MODULE Identificador;
{ Lista_importados ; }
[ Lista_exportados ; ]
Definición_de_elementos::= Declaración_de_constantes |
TYPE { Iidentificador [ = Esquema_de_tipo]; }|
Declaración_de_variables | Cabecera_subprograma;

10.3.2 Módulos de implementación


Un módulo de implementación tiene la misma sintaxis que un módulo de programa, salvo que
comienza con las palabras IMPLEMENTATION MODULE, en lugar de MODULE.
Al igual que los módulos de programa, los módulos de implementación pueden contener tanto
listas de importación como declaraciones. Mediante listas de importación un módulo de
implementación debe importar las entidades de otros módulos de biblioteca que utilice.
Las declaraciones sirven para dos propósitos:
1) Especificar los aspectos indefinidos en el módulo de definición.
2) Declarar entidades que sean estrictamente locales al módulo de implementación.
Cualquier entidad declarada en la parte de definición de un módulo de biblioteca es visible en
la parte de implementación.
En TopSpeed Modula-2, las entidades importadas en la parte de definición son visibles en la
parte de implementación automáticamente aunque esto no es un estándar del lenguaje Modula-
2.
En un módulo de implementación pueden aparecer declaraciones de cinco clases de entidades:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 10. Diseño modular 5

• Constantes.
Las constantes definidas en la parte de definición son visibles en la parte de implementación de
forma automática. Todas las constantes declaradas en un módulo de implementación son locales
a dicho módulo; no son visibles fuera del módulo de implementación.
• Tipos.
Los tipos definidos en la parte de definición del módulo son visibles automáticamente en la
parte de implementación. Si en la parte de definición aparece la especificación del tipo, no tiene
que aparecer una declaración del mismo en la parte de implementación, sin embargo, si se
omitió dicha especificación (se definió como tipo opaco), debe aparecer una declaración
completa en la parte de implementación. Esta declaración debe ser obligatoriamente de tipo
POINTER (que será estudiado en un tema posterior). Pueden aparecer, además, declaraciones
de nuevos tipos, en cuyo caso sólo son visibles dentro de la parte de implementación.
Ejemplo. Si el tipo COMPLEJO ha sido definido como opaco en el módulo de definición,
habría que declarar:
TYPE
COMPLEJO = POINTER TO RECORD
PReal, PImag: REAL
END;

• Variables.
Las variables declaradas en la parte de definición no pueden volver a ser declaradas en la parte
de implementación puesto que son visibles en ésta automáticamente. Todas las variables
declaradas en la parte de implementación son locales a ésta.
• Subprogramas.
La parte de implementación de un módulo debe contener una declaración completa de todos los
subprogramas cuya cabecera aparece en la parte de definición. Hay que respetar estrictamente
la forma en que se definió la cabecera del procedimiento en la parte de definición, es decir, hay
que declarar los mismos parámetros y tipos para ellos. Además, pueden declararse otros
procedimientos que serán locales al módulo de implementación.
• Módulos.
Dentro de un módulo de implementación pueden declararse otros módulos. A estos módulos se
los llama módulos internos. No los estudiaremos en este curso.
Los módulos de biblioteca pueden tener un cuerpo, cuyas instrucciones se ejecutan al comienzo
de la ejecución del programa (sobre esta cuestión insisteremos más adelante).Todas las
variables declaradas en el nivel más externo de un módulo de biblioteca retienen sus valores
durante todo el tiempo de vida del programa, a diferencia de lo que ocurre con las variables
declaradas en un subprograma. Por tanto, sólo es necesario inicializar una vez las variables
pertenecientes a un módulo de biblioteca (cuando el programa comienza la ejecución).
En el siguiente ejemplo, se define un módulo de biblioteca con operaciones para la
determinación de la posición del máximo y el mínimo elemento de un array de números reales
de mil elementos.

Informática Facultad de Ciencias (Matemáticas)


6 10.3 Módulos de biblioteca

MaxMin.Def

DEFINITION MODULE MaxMin;


TYPE (* Tipo Transparente *)
ARRAYREAL = ARRAY [1..1000] OF REAL;

PROCEDURE MaxPos(A : ARRAYREAL): CARDINAL;


PROCEDURE MinPos(A : ARRAYREAL): CARDINAL;
END MaxMin.

MaxMin.Mod
IMPLEMENTATION MODULE MaxMin;

PROCEDURE MaxPos(A : ARRAYREAL):CARDINAL;


VAR
Indice : CARDINAL;
PosMax : [1..1000];
BEGIN
PosMax := 1;
FOR Indice := 2 TO 1000 DO
IF A[Indice] > A[PosMax] THEN
PosMax := Indice
END
END;
RETURN PosMax
END MaxPos;

PROCEDURE MinPos(A : ARRAYREAL):CARDINAL;


VAR
Indice : CARDINAL;
PosMin : [1..1000];
BEGIN
PosMin := 1;
FOR Indice := 2 TO 1000 DO
IF A[Indice] < A[PosMin] THEN
PosMin := Indice
END
END;
RETURN PosMin
END MinPos;

END MaxMin.

10.3.3 Inicialización de un módulo


Los módulos de implementación pueden contener código para su inicialización. Este código es
ejecutado una sola vez, antes de ejecutar el código del módulo cliente (aquel que hace uso de
él). En el caso de que varios módulos de biblioteca contengan código para su inicialización,
cada bloque de código de inicialización es ejecutado sólo una vez en la secuencia dada por la
lista de importación.
Como ejemplo, consideremos los siguientes módulos de biblioteca y el siguiente módulo de
programa:
DEFINITION MODULE biblioteca1;
END biblioteca1.

IMPLEMENTATION MODULE biblioteca1;


FROM IO IMPORT WrStr, WrLn;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 10. Diseño modular 7

BEGIN
WrStr(“Dentro de Biblioteca 1”);
WrLn;
END biblioteca1.

DEFINITION MODULE biblioteca2;


END biblioteca2.

IMPLEMENTATION MODULE biblioteca2;


FROM IO IMPORT WrStr, WrLn;
BEGIN
WrStr(“Dentro de Biblioteca 2”);
WrLn;
END biblioteca2.

DEFINITION MODULE biblioteca3;


END biblioteca3.

IMPLEMENTATION MODULE biblioteca3;


FROM IO IMPORT WrStr, WrLn;
BEGIN
WrStr(“Dentro de Biblioteca 3”);
WrLn;
END biblioteca3.

MODULE principal;
FROM IO IMPORT WrLn, WrStr;
IMPORT Biblioteca3;
IMPORT Biblioteca1;
IMPORT Biblioteca2;
BEGIN
WrLn;
WrStr(“Dentro del modulo principal”);
END principal.

La sálida por pantalla que resulta al ejecutar el módulo principal es:


Dentro de Biblioteca 3
Dentro de Biblioteca 1
Dentro de Biblioteca 2
Dentro del modulo principal.

10.3.4 Usos de los módulos de biblioteca


El concepto de módulo está muy ligado al concepto de abstracción. Un módulo debe definir un
elemento abstracto o varios relacionados entre sí. El diseño del módulo debe permitir su uso de
modo que se sepa qué hace cada parte sin que sea necesario saber cómo está implementada.
Se pueden construir módulos de biblioteca con las siguientes finalidades:
1) Agrupar una cierta cantidad de identificadores de constantes relacionadas. Por
ejemplo podríamos construir un módulo de biblioteca que reuniera las principales
constantes matemática (π, e,...). En este caso, solo aparecen definiciones en el módulo
de definción de la biblioteca. El módulo de implementación queda vacío.
2) Agrupar subprogramas relacionados. Por ejemplo la biblioteca MATHLIB agrupa las
funciones matemáticas de uso más frecuente (seno, coseno, raíz cuadrada...). En este
caso, sólo aparecen definiciones de subprogramas en el módulo de definición. Las
correspondientes implementaciones aparecen en el módulo de implementación.
3) Definir un tipo abstracto de datos. Se utiliza la biblioteca para definir un nuevo tipo y
las operaciones que se pueden realizar con él. Ejemplo: biblioteca FIO. En este caso la

Informática Facultad de Ciencias (Matemáticas)


8 10.4. Compilación separada

interfaz del módulo equivale a la especificación del tipo abstracto de datos, y la


realización del módulo establece su representación interna. En el módulo de definición
aparece la definición de tipo y los subprogramas asociados.
4) Ocultar una única variable de un tipo abstracto (dato encapsulado). El módulo
constituye una cápsula que protege al dato de manera que sólo se puede acceder a él
usando las operaciones de la interfaz. En el módulo de definición sólo aparecen
definiciones de subprogramas. En el módulo de implementación hay una variable global
(el dato encapsulado). Los subprogramas acceden al dato encapsulado mediante efectos
laterales. Este es uno de los pocos casos en los que es lícito usar efectos laterales, ya
que éstos quedan aislados a los subprogramas del módulo.
El siguiente ejemplo muestra un módulo en el que se define un dato encapsulado:
DEFINITION MODULE Autor;
PROCEDURE Leer;
PROCEDURE Escribir;
END Autor.

IMPLEMENTATION MODULE Autor;


IMPORT IO;
VAR autor:ARRAY [0..20] OF CHAR;
PROCEDURE Leer;
BEGIN
IO.RdStr(autor)
END Leer;

PROCEDURE Escribir;
BEGIN
IO.WrStr(autor)
END Escribir;

END Autor.

MODULE Principal;
IMPORT Autor;
BEGIN
Autor.Leer;
Autor.Escribir
END Principal.

10.4. Compilación separada


Modula-2 permite compilar módulos de programa y de biblioteca de forma separada. El
compilador chequea que cada entidad importada de un módulo de biblioteca sea usada de forma
consistente con su definición en el módulo (para ello usa la información contenida en la parte
de definición del módulo de biblioteca).
Una de las ventajas proporcionadas por la compilación separada es que cuando cambian
algunos de los módulos de un programa, sólo es necesario recompilar los módulos afectados, y
no todos los módulos del programa.
Muchos sistemas Modula-2 requieren que se compilen los módulos de definición; sin embargo,
en TopSpeed sólo necesitan ser compilados los módulos de implementación y de programa. La
compilación de cada fichero con extensión .MOD genera un fichero con el mismo nombre y
extensión .OBJ. Los ficheros .DEF no producen ficheros .OBJ ya que no son compilados.
Si un programa consta de varios ficheros con extensión .MOD (un módulo de programa y varios
módulos de implementación) se puede seguir cualquier orden a la hora de compilar los ficheros
siempre que se respete la siguiente norma:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 10. Diseño modular 9

El módulo de definición de una librería debe existir antes de que pueda compilarse su
correspondiente módulo de implementación o cualquier otro módulo que importe de la
librería.
Una vez generados todos los ficheros .OBJ, hay que enlazarlos. El proceso de enlazado genera
un fichero ejecutable con el nombre del módulo de programa y con extensión .EXE.
Si se modifica uno de los ficheros que forman el programa, no es necesario volver a compilar
todos los ficheros de nuevo. Las normas son:
• Si un módulo de programa o un módulo implementación es modificado, sólo él debe ser
recompilado. A continuación basta con repetir el proceso de enlazado para obtener un
nuevo fichero ejecutable actualizado.
• Si un módulo de definición es modificado, su correspondiente módulo de
implementación y todos los módulos que importen la biblioteca deben ser
recompilados. Es necesario también repetir el proceso de enlazado.
En TopSpeed Modula-2 todo este proceso está automatizado mediante la opción Make del
menú. Cuando esta opción es seleccionada, el sistema recompila los módulos necesarios y,
además, realiza el proceso de enlazado.
El siguiente ejemplo muestra el proceso de compilación y enlazado para un programa formado
por un módulo de programa (Programa.Mod) y un módulo de biblioteca (Bib.Mod y Bib.Def):

Program.Mod Bib.Mod Bib.Def

MODULE Programa; IMPLEMENTATION MODULE Bib; DEFINITION MODULE Bib;

IMPORT Bib; ... ...


...
END Bib. END Bib.
END Programa.

COMPILACIÓN COMPILACIÓN

Programa.Obj Bib.Obj

ENLAZADO

Programa.Exe

Informática Facultad de Ciencias (Matemáticas)


10 10.4. Compilación separada

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 10) 1

Relación de Problemas (Tema 10)


1. Escribe una biblioteca MatConst que exporte las siguientes constantes matemáticas:
PI: relación entre el radio y la circunferencia de un círculo.
E: el valor de la función exponencial en el punto 1.0.
LN10: valor de la función logaritmo neperiano en el punto 10.
2. Diseña una biblioteca que exporte el tipo de dato COMPLEJO y operaciones para construir,
sumar, restar, multiplicar, dividir, leer de teclado y escribir por pantalla de valores de dicho
tipo. El módulo de definición será el siguiente:
DEFINITION MODULE Complejo;

TYPE
COMPLEJO = RECORD
PReal : LONGREAL;
PImag : LONGREAL
END;
PROCEDURE RdComplejo () : COMPLEJO;
PROCEDURE WrComplejo (C : COMPLEJO);
PROCEDURE AsignaRectangular (Real, Imag: LONGREAL) : COMPLEJO;
PROCEDURE AsignaPolar (Modulo, Argumento: LONGREAL) : COMPLEJO;
PROCEDURE Suma (C1, C2 : COMPLEJO) : COMPLEJO;
PROCEDURE Resta (C1, C2 : COMPLEJO) : COMPLEJO;
PROCEDURE Producto (C1, C2 : COMPLEJO) : COMPLEJO;
PROCEDURE Division (C1, C2 : COMPLEJO) : COMPLEJO;
PROCEDURE SonIguales (C1, C2 : COMPLEJO) : BOOLEAN;

END Complejo.

La función AsignaRectangular toma dos números reales, correspondientes a la parte real


e imaginaria de un número complejo, y devuelve un número complejo con dichas
componentes. La función AsignaPolar es similar, pero construye el número a partir de su
módulo y argumento. La función SonIguales devuelve TRUE si sus dos argumentos
corresponden al mismo valor complejo.
Prueba la biblioteca construida con el siguiente programa:
MODULE Ej1002;
IMPORT Complejo;
FROM MatConst IMPORT PI;
FROM IO IMPORT WrLn;
VAR
a, b, c, Suma, Resta, Producto, Division : Complejo.COMPLEJO;

BEGIN
a := Complejo.AsignaRectangular(4.0, 5.0);
b := Complejo.AsignaPolar(1.0, PI);
c := Complejo.RdComplejo()
;
Complejo.WrComplejo(a); WrLn;
Complejo.WrComplejo(b); WrLn;
Complejo.WrComplejo(c); WrLn;
Suma := Complejo.Suma(a, c);
Resta := Complejo.Resta(a, c);
Producto := Complejo.Producto(a, b);
Division := Complejo.Division(c, b);
Complejo.WrCompl
ejo(Suma); WrLn;
Complejo.WrComplejo(Resta); WrLn;
Complejo.WrComplejo(Producto); WrLn;

Informática Facultad de Ciencias (Matemáticas)


2 Relación de Problemas (Tema 10)

Complejo.WrComplejo(Division)
END Ej1002.

3. Se desea escribir una función que devuelva un número real aleatorio entre 0 y 1 cada vez
que se llame.
Aunque no es fácil generar mediante un programa de ordenador números realmente
aleatorios, existen técnicas para la generación de números pseudo-aleatorios. Una de estas
técnicas consiste en utilizar los valores obtenidos al ejecutar repetidamente la sentencia:
Semilla := (a * Semilla + c) MOD m;
Donde a, c y m son valores enteros. No es fácil escoger valores apropiados para a, c y m,
pero se puede demostrar que si el valor inicial de Semilla es menor que m y se cumplen
las siguientes igualdades:
a = 4c + 1

c = 2x + 1

m = 2y
donde x e y son cualquier valor entero, los valores asignados a Semilla comienzan a
repetirse cíclicamente tras m valores y todos los valores en el rango [0..m-1] aparecen
exactamente una vez en cada ciclo.
Por ejemplo, si escogemos y = 11 y x = 3, tenemos que m = 2048, c = 7 y a = 29. Al repetir
la sentencia
Semilla := (29 * Semilla + 7) MOD 2048
cada uno de los valores asignados a Semilla será un número pseudo-aleatorio en el rango
[0..2047]. Para obtener números aleatorios entre 0 y 1 basta con dividir los sucesivos
valores de Semilla por 2047.
Escribir un módulo de biblioteca para la generación de números pseudo aleatorios según el
siguiente módulo de definición y utilizando el método explicado:
DEFINITION MODULE NumAleat;

PROCEDURE EstablecerSemilla (NuevaSemilla : CARDINAL);


PROCEDURE NumeroAleatorio () : REAL;

END NumAleat.

La función NumeroAleatorio devuelve un número real entre 0 y 1 cada vez que se llama.
En el módulo de implementación se definirá una variable global Semilla que se
actualizará, según la fórmula vista, con cada llamada a la función NumeroAleatorio. La
función EstablecerSemilla permite cambiar el valor inicial de la semilla.
NOTA: Obsérvese que tanto NumeroAleatorio como EstablecerSemilla acceden a la
variable Semilla mediante un efecto lateral. Una variable global encapsulada dentro de un
módulo de biblioteca es una de las pocas circunstancias en las cuales es lícito el acceso a
una variable mediante un efecto lateral.
4. Desarrolla una biblioteca que permita trabajar con cadenas de caracteres implementadas
según el criterio habitual en Modula-2. Las cadenas se representan mediante arrays de
caracteres, con terminador CHR(0), si la longitud de la cadena es menor al tamaño del
array. La biblioteca se llamará Cadenas y debe construirse de manera que el programa
adjunto funcione correctamente, por lo que debe definir las siguientes operaciones:
Una función llamada Longitud que toma como parámetro una cadena de caracteres, y

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 10) 3

devuelve su longitud (número de caracteres de la cadena que contiene).


Una función llamada Buscar, que toma como parámetros dos cadenas de caracteres y


permite buscar la primera cadena dentro de la segunda. Para ello, devuelve la posición
de la primera ocurrencia de la primera cadena en la segunda (o el valor –1 si no
aparece).
Un procedimiento Sustituir, que toma como parámetros dos cadenas de caracteres y


sustituye la primera ocurrencia de la primera cadena en la segunda (si está) por


asteriscos.
Un procedimiento llamado Borrar, que toma como parámetros dos cadenas de


caracteres y elimina de la primera cadena todas las ocurrencias de la segunda cadena.


Un procedimiento llamado WrCadena, que toma como parámetro una cadena de


caracteres, y escribe por pantalla los caracteres que la forman (no se puede usar
IO.WrStr).


Un procedimiento llamado RdCadena, que lee de teclado una cadena de caracteres y la


almacena en la variable que toma como parámetro. (no se puede usar IO.RdStr).


Un procedimiento llamado Insertar, que toma tres parámetros, el primero una cadena
de caracteres, el segundo un valor de tipo CARDINAL, y el tercero una cadena de
caracteres, y realiza la inserción de la primera cadena en la segunda a partir de la
posición indicada por el segundo parámetro. En caso de que el segundo parámetro
haga referencia a una posición no válida, el procedimiento no hace nada.
NOTA: Los argumentos que representan cadenas de caracteres se pasan como arrays
abiertos. La primera letra dentro de una cadena tiene como posición asociada el valor cero.
MODULE Ej1004;
FROM Cadenas IMPORT RdCadena, WrCadena, Longitud, Buscar,
Sustituir, Insertar, Borrar;
FROM IO IMPORT RdLn, RdChar, WrLn, WrInt;
VAR
C1 : ARRAY [1..20] OF CHAR;
C2 : ARRAY [1..10] OF CHAR;
Fin : CHAR;
Pos : INTEGER;
BEGIN
REPEAT
WrCadena("Introduzca una cadena de caracteres: "); WrLn;
RdCadena(C1);
WrCadena("Se ha leido la cadena");
WrCadena(C1); WrLn;
WrCadena("La longitud de la cadena leida es: ");
WrCard(Longitud(C1), 0); WrLn;
WrCadena("Introduzca una cadena a buscar:");
RdCadena(C2);
Pos := Buscar(C2,C1);
IF Pos = -1 THEN
WrCadena("El patron NO aparece en la cadena");
ELSE
WrCadena("Encontrado en la posición:");
WrInt(Pos,0)
END;
WrLn;
Sustituir(C2,C1);
WrCadena("Tras Sustituir, la cadena inicial queda:");
WrCadena(C1); WrLn;
Borrar(C1,"ABC");
WrCadena("Despues de borrar ABC, la cadena queda:");
WrCadena(C1); WrLn;

Informática Facultad de Ciencias (Matemáticas)


4 Relación de Problemas (Tema 10)

Insertar("DDD", 3, C1);
WrCadena("Despues de insertar DDD, la cadena queda:");
WrCadena(C1); WrLn;
WrCadena("Desea repetir el proceso (S/N)?: ");
Fin := RdChar(); RdLn;
UNTIL CAP(Fin) = 'N'
END Ej1004.

5. Diseña una biblioteca Punto que exporte el tipo de datos PUNTO, que representa un punto
del plano bidimensional. La biblioteca debe exportar, además, operaciones para
asignar valor a un punto,


escribir un punto en pantalla




leer un punto de teclado




comparar dos puntos




desplazar un punto según la dirección determinada por el origen de coordenadas y otro




punto especificado como parámetro


girar un cierto ángulo expresado en radianes el vector determinado por el origen y otro


punto (especificado como parámetro) en el sentido contrario a las agujas del reloj
NOTA: Representar el punto bidimensional mediante un tipo registro con campos las
componentes x e y del punto.
6. Sabemos que un conjunto es una colección desordenada de elementos en la que no existen
elementos duplicados. En programación, los elementos de un conjunto tienen todos el
mismo tipo al que se le llama tipo base del conjunto. En TopSpeed Modula-2, el tipo base
de un conjunto puede ser CARDINAL, SHORTCARD, CHAR, un tipo enumerado, o un
subrango de cualquiera de estos tipos. Esta restricción limita bastante el uso de los tipos
conjuntos. Se desea desarrollar un programa que trabaje con conjuntos de puntos del plano
bidimensional, para lo cual es necesario disponer conjuntos con dicho tipo base.
Diseña el tipo de datos CONJUNTOPUNTOS (conjunto de puntos del plano bidimensional),
construyendo para ello una biblioteca denominada ConjPunt que ofrezca, además del tipo
CONJUNTOPUNTOS, las siguientes operaciones:
Vacio: devuelve un conjunto vacío de este tipo.
Pertenece(p,c): devuelve TRUE si el punto p es un elemento del conjunto c, o FALSE en
otro caso.
Incluye(p,c): incluye el punto p en el conjunto c. El subprograma debe comprobar
previamente si el elemento ya estaba, en cuyo caso no hace nada.
Excluye(p,c): excluye el punto p del conjunto c. Si el punto no está en el conjunto se
escribirá en pantalla un mensaje indicándolo y no se modificará el conjunto c.
WrConjunto(c): muestra por pantalla todos los elementos del conjunto c.
Union(c1,c2): devuelve un nuevo conjunto que es la unión de los conjuntos c1 y c2. Los
conjuntos c1 y c2 no se modifican.
Interseccion(c1,c2): devuelve un nuevo conjunto que es la intersección de los
conjuntos c1 y c2. Los conjuntos c1 y c2 no se modifican.
Diferencia(c1,c2): devuelve un nuevo conjunto que es la diferencia c1-c2. Los
conjuntos c1 y c2 no se modifican.
Incluido(c1,c2): devuelve TRUE si el conjunto c1 está incluido en el conjunto c2.
SonIguales(c1,c2): devuelve TRUE si el conjunto c1 coincide con el conjunto c2.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 10) 5

Téngase en cuenta que el orden de los elementos no es relevante para determinar la


igualdad de los dos conjuntos.
NOTA: Para simplificar, consideraremos que el cardinal máximo de cualquier variable del
tipo CONJUNTOPUNTOS será 20 elementos. El tipo PUNTO debe importarse de la biblioteca
desarrollada en el problema anterior.
Escribe un programa de prueba para la biblioteca desarrollada.

Informática Facultad de Ciencias (Matemáticas)


6 Relación de Problemas (Tema 10)

Relación de Problemas complementarios (Tema 10)


7. Escribe una biblioteca, similar a la del ejercicio 2, en la que se defina el tipo RACIONAL y
operaciones para asignar, operar, comparar, leer del teclado y escribir por pantalla valores
de dicho tipo. Prueba la biblioteca con un programa que la use. La función de comparación
devolverá un valor en el rango [-1..1] (-1 si el primer argumento es menor al segundo, 0 si
son iguales y +1 si el primero es mayor al segundo).
8. Escribe una biblioteca que exporte las funciones trigonométricas Seno y Coseno. Para
evaluarlas suma las siguientes series hasta que los términos obtenidos sean menores a una
milésima.

x3 x5 x7
Seno( x) = x − + − + 

3! 5! 7!
x2 x4 x6
Coseno( x) = 1 − + − + 

2! 4! 6!
Dado que estas series convergen cuando x → 0, deberán sumarse para valores de x en el
rango 0 ≤ x ≤ π/2. Si los argumentos no se encuentran en este rango, se deberán utilizar las
siguientes igualdades:
Seno ( x) ≡ Seno (π − x) si π / 2 ≤ x ≤ π
Seno ( x) ≡ − Seno ( x − π ) si π ≤ x ≤ 3π / 2
Seno ( x) ≡ − Seno (2π − x) si 3π / 2 ≤ x ≤ 2π
Coseno ( x) ≡ − Coseno (π − x) si π / 2 ≤ x ≤ π
Coseno ( x) ≡ − Coseno ( x − π ) si π ≤ x ≤ 3π / 2
Coseno ( x) ≡ Coseno (2π − x) si 3π / 2 ≤ x ≤ 2π
9. Escribe una biblioteca estadística en la cual se definan las siguientes operaciones, que
toman como argumento un array abierto de valores reales:
Maximo: devuelve la medida de valor máximo del array que toma como argumento.
Minimo: devuelve la medida de valor mínimo del array que toma como argumento.
Media: devuelve la media de los valores del array que toma como argumento.
DesviacionTipica: devuelve la desviación típica de los valores del array que toma como
argumento.
Varianza: devuelve la varianza de los valores del array que toma como argumento.
Mediana: devuelve la mediana de los valores del array que toma como argumento.
Moda: devuelve la moda de los valores del array que toma como argumento. Si ésta no es
única escribirá un mensaje de aviso en pantalla y devolverá la menor de las modas.
Para este ejercicio calcularemos la mediana como:
- el valor que ocupa la posición (N+1) /2 en el array una vez ordenado si el número de
datos (N) es impar,
- la media aritmética de los valores que ocupan las posiciones N/2 y (N+1)/2 en el array
una vez ordenado si el número de datos es par .

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 11. Diseño de algoritmos recursivos 1

Apuntes para la asignatura

Informática
Facultad de Ciencias (Matemáticas)
Departamento de Lenguajes y http://www.lcc.uma.es/personal/pepeg/mates
Ciencias de la Computación
UNIVERSIDAD DE MÁLAGA

Tema 11. Diseño de algoritmos recursivos


11.1. Recursión................................................................ ................................ .........2
11.1.1 Verificación de los subprogramas recursivos............................................... 3
11.2 Recursividad frente a iteración................................................................ ..........4
11.3 Eficiencia de los algoritmos recursivos ............................................................. 5
11.3.1 Sobrecarga debida al funcionamiento interno .............................................5
11.3.2 Sobrecarga debida a la solución recursiva .................................................. 6
11.3.3 Iteración o recursión................................................................ .................... 7
11.4 Ejemplos clásicos de recursividad ................................................................ ....8
11.4.1 Las torres de Hanoi................................................................ ..................... 8
11.4.2 Algoritmos de búsqueda recursivos .......................................................... 11
11.4.2.1 Búsqueda lineal recursiva................................................................ ....11
11.4.2.2 Búsqueda binaria recursiva ................................................................ .12

Bibliografía
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.
• Pascal y estructuras de datos. Nell Dale y Susan C. Lilly. McGraw-Hill.
• Fundamentos de programación. L. Joyanes. McGraw-Hill.
2 11.1. Recursión

11.1. Recursión
Una definición es recursiva cuando el concepto que se introduce está definido en base a sí
mismo. Un gran número de problemas se plantean de forma recursiva. Esto significa que su
solución se apoya en la solución del mismo problema pero para un caso más fácil.
En matemáticas es frecuente definir conceptos en términos de sí mismos. Por ejemplo la
siguiente definición de factorial de un número n positivo es recursiva:
1, si n = 0
n!= 
în × (n − 1)!, si n ≠ 0
La definición es recursiva porque estamos definiendo el factorial de n como el producto de n
por el factorial de n-1, si n es distinto de cero. El concepto que definimos (factorial) aparece en
la propia definición. Si queremos calcular, por ejemplo, el factorial de 4 con la definición
anterior procederíamos del siguiente modo:
4! = 4 × 3! = 4 × 3 × 2! = 4 × 3 × 2 × 1! = 4 × 3 × 2 × 1 × 0! = 4 × 3 × 2 × 1 × 1 = 24
El siguiente ejemplo es una definición recursiva del sumatorio de los primeros n números
naturales:

0, si n = 0
n

∑ i=
n +
n −1

∑ i, si n ≠ 0
i =0 î i=0

Un último ejemplo es la definición de una potencia con exponente entero positivo:

1, si e = 0
be =  ( e −1)
î b×b , si e ≠ 0
Vemos que la recursividad es una técnica que permite dar definiciones simples y elegantes. En
Modula-2 es posible definir subprogramas recursivos, es decir, subprogramas en cuyo cuerpo
aparezcan llamadas a sí mismos.
La definición anterior de la función factorial nos lleva a la siguiente función Modula-2:
PROCEDURE Factorial (n : CARDINAL) : CARDINAL;
BEGIN
IF n = 0 THEN
RETURN 1
ELSE
RETURN n * Factorial(n-1)
END
END Factorial;

Obsérvese que hay una llamada a la propia función que se está definiendo (Factorial) en la
parte ELSE. Esto no presenta ningún problema ya que las reglas de ámbito de Modula-2 lo
permiten.
Por otro lado, la función presenta más de una instrucción RETURN. Esta práctica es común a la
hora de escribir funciones recursivas. En el caso de funciones no recursivas, es mejor estilo de
programación utilizar una única sentencia RETURN al final del subprograma.
Veamos ahora qué es lo
que ocurre si un programa ejecuta la sentencia
IO.WrCard(Factorial(3), 0) que llama a la función recursiva anterior:

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 3

1) El subprograma WrCard necesita conocer el valor de su argumento Factorial(3) para


poder imprimirlo por lo cual se entra en la función Factorial. Como el argumento está
declarado por valor se crea una nueva variable n y se asigna a ésta el valor 3.
Llamemos a esta variable n (1.
2) Como n(1 es distinto de cero el valor que devuelve la llamada a Factorial(3) es la
correspondiente al ELSE, o sea, n(1 * Factorial(n(1-1). Para poder calcular este
producto es necesario calcular Factorial(n(1-1)que provoca una nueva llamada a la
función Factorial, en este caso con argumento 2, ya que n(1 vale 3.
3) Se vuelve a entrar en la función Factorial, por lo que se crea una nueva variable que
llamaremos n(2 y cuyo valor es 2. Como el valor de n(2 es distinto de cero esta llamada
devuelve n(2 * Factorial(n(2-1). De nuevo para calcular este producto se debe
conocer el valor de la segunda parte lo que provoca una tercera llamada a Factorial con
argumento 1, ya que n(2 vale 2.
4) Se crea n(3 con valor 1, y se devuelve n(3 * Factorial(n (3-1). Se produce una ultima
llamada a factorial con argumento 0.
5) Al entrar en la función Factorial se crea una nueva variable n(4 con valor 0. Como n(4
vale 0 la función Factorial devuelve el valor indicado en la parte IF, o sea, 1.
6) El producto del punto 4) puede ser calculado, por lo que la tercera llamada a factorial
devuelve el valor n(3 * Factorial(n(3-1)= 1 * 1 = 1.
7) El producto del punto 3) puede ser calculado, por lo que la segunda llamada a factorial
devuelve el valor n(2 * Factorial(n(2-1)= 2 * 1 = 2.
8) El producto del punto 2) puede ser calculado, por lo que la primera llamada a factorial
devuelve el valor n(1 * Factorial(n(1-1)= 3 * 2 = 6.
9) El valor de Factorial(3) ha sido calculado y puede ser impreso por pantalla.

11.1.1 Verificación de los subprogramas recursivos


Hemos visto que la ejecución de un subprograma recursivo hace, en general, que éste sea
llamado varias veces. Para asegurar que este proceso termina es necesario que el subprograma
recursivo definido cumpla las siguientes condiciones:
1) Hay al menos una posible llamada al subprograma que produce un resultado sin
provocar una nueva llamada recursiva. A esto se le llama caso base de la recursión.
En el caso de la función Factorial el caso base es n=0.
La siguiente definición de la función Factorial no es correcta ya que no posee caso
base:
PROCEDURE Factorial (n : CARDINAL) : CARDINAL;
BEGIN
RETURN n * Factorial(n-1)
END Factorial;

2) Todas las llamadas recursivas se refieren a un caso que es más cercano al caso base. En
el caso de la función Factorial, cada llamada disminuye en uno el valor del
argumento, por lo que éste se va acercando a cero, que es el caso base.
A partir de la expresión n!= (n − 1)!× n , y la sustitución n = m + 1 se obtiene que
(m + 1)!= m!× (m + 1) , de donde despejando se deduce m!= (m + 1)! / (m + 1) . Con
esta expresión podemos construir el siguiente subprograma recursivo:
PROCEDURE Factorial (m : CARDINAL) : CARDINAL;

Facultad de Ciencias (Matemáticas) Informática


4 11.2 Recursividad frente a iteración

BEGIN
IF m = 0 THEN
RETURN 1
ELSE
RETURN Factorial(m+1) DIV (m+1)
END
END Factorial;

Este subprograma presenta un caso base, sin embargo no es correcto ya que no


cumple la condición 2). Cada llamada recursiva incrementa en uno el valor del
argumento por lo que éste se va alejando del caso base y la recursión nunca termina.
3) El caso base debe acabar alcanzándose.
El siguiente subprograma cumple las condiciones 1) y 2), pero no es correcto. Si lo
llamamos con un argumento cuyo valor sea impar, el caso base nunca se alcanza.
PROCEDURE Factorial (n : CARDINAL) : CARDINAL;
BEGIN
IF n = 0 THEN
RETURN 1
ELSE
RETURN n * (n-1) * Factorial(n-2)
END
END Factorial;

Para algunos algoritmos recursivos el caso base puede ser múltiple. Un ejemplo es la sucesión
de Fibonacci, que se define del siguiente modo: el primer número de Fibonacci es 0, el segundo
es 1 y cualquier otro número de Fibonacci se obtiene sumando los dos números de Fibonacci
que le preceden:

0, si n = 1

Fibonacci(n) = 1, si n = 2

î Fibonacci(n − 1) + Fibonacci(n − 2), en otro caso
Los casos bases en este ejemplo son n = 1 y n = 2. Una función en Modula-2 que calcule el
n-ésimo número de Fibonacci según esta definición es:
PROCEDURE Fibonacci (n : CARDINAL) : CARDINAL;
BEGIN
IF n = 0 THEN
WrStr (“Error: Fibonacci(0) no está definido”);
HALT
ELSIF n = 1 THEN
RETURN 0
ELSIF n = 2 THEN
RETURN 1
ELSE
RETURN Fibonacci(n-1) + Fibonacci(n-2)
END
END Factorial;

11.2 Recursividad frente a iteración


Cualquiera de los problemas resueltos de modo recursivo puede ser resuelto de modo iterativo.
En algunos casos, como en el de la función Factorial, la solución iterativa es bastante simple:
PROCEDURE Factorial (n : CARDINAL) : CARDINAL;
VAR i, Resultado : CARDINAL;

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 5

BEGIN
Resultado := 1;
FOR i := 1 TO n DO
Resultado := Resultado * i
END;
RETURN Resultado
END Factorial;

Podemos observar que, en la versión iterativa, la recursión ha sido sustituida por una iteración
(un bucle FOR, en este caso). También ha sido necesario introducir, en la versión iterativa, dos
variables locales (i y Resultado). En general, en los algoritmos recursivos se suele utilizar una
sentencia de selección (IF), mientras que en los iterativos equivalentes se usa una sentencia de
iteración (FOR, WHILE, REPEAT o LOOP) y es necesario introducir variables locales que
almacenen los resultados intermedios del cómputo.
En otros casos la solución iterativa al problema es más complicada. En un apartado posterior
veremos una solución recursiva al problema de las Torres de Hanoi. No es nada fácil encontrar
una solución iterativa a este problema.
Un poco más complicada, aunque también fácil, es la solución iterativa para la recurrencia de
Fibonacci. En este caso, necesitamos almacenar en dos variables (FibNMenos1 y FibNMenos2)
los valores de las dos iteraciones previas:
PROCEDURE Fibonacci (n : CARDINAL) : CARDINAL;
VAR FibN, FibNMenos1, FibNMenos2, i : CARDINAL;
BEGIN
IF n = 0 THEN
WrStr (“Error: Fibonacci(0) no está definido”);
HALT
ELSIF n = 1 THEN
FibN := 0
ELSE
FibNMenos1 := 0;
FibN := 1;
FOR i := 2 TO n-1 DO
FibNMenos2 := FibNMenos1;
FibNMenos1 := FibN;
FibN := FibNMenos2 + FibNMenos1;
END
END;
RETURN FibN
END Fibonacci;

11.3 Eficiencia de los algoritmos recursivos


Los algoritmos recursivos suelen ser menos eficientes (en tiempo) que los iterativos
correspondientes. Esto es así por dos motivos principalmente:
• La sobrecarga debida al funcionamiento interno de la recursividad.
• La posible sobrecarga debida a la solución recursiva.

11.3.1 Sobrecarga debida al funcionamiento interno


En la primera sección de este tema hicimos un seguimiento en detalle de la evaluación de una
llamada a la función recursiva Factorial. Podemos observar que, en general, una llamada a
una función recursiva produce varias llamadas más a la propia función. Cada una de estas
llamadas requiere cierto tiempo para:
• Hacer la llamada.

Facultad de Ciencias (Matemáticas) Informática


6 11.3 Eficiencia de los algoritmos recursivos

• Crear las variables locales y copiar los parámetros por valor.


• Ejecutar las instrucciones en parte ejecutiva de la función.
• Destruir las variables locales y parámetros por valor.
• Salir de la función.
Si se realizan unas cuantas llamadas recursivas a una función, puede ocurrir que el tiempo
necesario para hacer las llamadas y crear y destruir variables prevalezca sobre el tiempo
necesario para realizar el cómputo propiamente dicho.
Este tipo de sobrecarga aparece en todos los algoritmos recursivos. Si utilizamos una
función iterativa equivalente esta sobrecarga no aparece, ya que la función iterativa no da lugar
a varias llamadas a sí misma.

11.3.2 Sobrecarga debida a la solución recursiva


Este tipo de sobrecarga no se debe al modo en que funciona la recursividad en sí misma, sino a
que la solución recursiva al problema es mala. Esta sobrecarga aparece en la función recursiva
que calcula los números de Fibonacci. Si analizamos el algoritmo podemos observar que los
mismos valores son calculados varias veces. En la siguiente figura podemos observar las
llamadas que se producen al intentar evaluar la expresión Fibonacci(5):
Fibonacci (5)

Fibonacci (4) Fibonacci (3)

Fibonacci (3) Fibonacci (2) Fibonacci (2) Fibonacci (1)

Fibonacci (2) Fibonacci (1)

Figura 1. Llamadas realizadas al evaluar Fibonacci(5).

Podemos observar que han sido necesarias un total de 9 llamadas a la función para calcular este
valor y que el cálculo de Fibonacci(2), por ejemplo, ha sido repetido 3 veces. Se ha
realizado un montón de trabajo repetido.
Este tipo de sobrecarga no aparece en todos los algoritmos recursivos (por ejemplo, no aparece
en el caso de la función Factorial recursiva). Al evaluar Factorial(3), no se repite ningún
cómputo:
Factorial (3)

Factorial (2)

Factorial (1)

Factorial (0)

Figura 2. Llamadas realizadas al evaluar Factorial(3).

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 7

Es posible mejorar el algoritmo recursivo para el calculo de los números de Fibonacci de modo
que se evite este tipo de sobrecarga. En concreto, necesitamos añadir un par de parámetros
extras en la función que almacenen el valor de los dos números de Fibonnaci anteriores. En la
primera llamada estos argumentos tomarán valores 0 y 1 (los dos primeros números de la
recurrencia):
PROCEDURE Fibonacci (n : CARDINAL) : CARDINAL;
PROCEDURE FibAux (n, FibN, FibNMas1 : CARDINAL) : CARDINAL;
BEGIN
IF n = 1 THEN
RETURN FibN
ELSE
RETURN FibAux(n-1, FibNMas1, FibN+FibNMas1)
END
END FibAux;
BEGIN
IF n = 0 THEN
WrStr(“Error: Fibonacci(0) no está definido”);
HALT
ELSE
RETURN FibAux(n, 0, 1)
END
END Fibonacci;

Si calculamos Fibonacci(5) con este algoritmo no repetimos cálculo:


Fibonacci(5)

FibAux(5,0,1
)

FibAux(4,1,1)

FibAux(3,1,2)

FibAux(2,2,3)

FibAux(1,3,5)

Figura 3. Llamadas realizadas al evaluar Fibonacci(5).

Sin embargo, la nueva solución no es tan directa como la original, lo cual era la ventaja
fundamental de la solución recursiva.

11.3.3 Iteración o recursión


El hecho de que la recursividad presente cierta sobrecarga (en tiempo de ejecución del
algoritmo) frente a la iteración no significa que no se deba utilizar, sino que hay que utilizarla
cuando realmente sea apropiada.
Cuando el problema a resolver es relativamente simple, suele ser igualmente fácil escribir la
versión iterativa y la recursiva a éste. En este caso es preferible utilizar la solución iterativa ya
que es más eficiente. Sin embargo, las soluciones a ciertos problemas más complicados pueden
ser mucho más fáciles si utilizamos para ello la recursividad (ver el ejemplo de las Torres de
Hanoi). En este caso, la claridad y simplicidad del algoritmo recursivo debe prevalecer frente

Facultad de Ciencias (Matemáticas) Informática


8 11.4 Ejemplos clásicos de recursividad

al tiempo extra que consume la recursión. Este enfrentamiento entre simplicidad y eficiencia
aparece a menudo en la programación de ordenadores.
Otro uso común de la recursividad es como herramienta para desarrollar prototipos de
programas. Podemos comenzar con una solución recursiva simple, que desarrollemos
rápidamente, y posteriormente, si necesitamos un programa más eficiente, mejorarlo utilizando
una solución iterativa.
El verdadero valor de la recursividad es como herramienta para resolver problemas para los que
no hay soluciones iterativas simples.

11.4 Ejemplos clásicos de recursividad

11.4.1 Las torres de Hanoi


El primero de los ejemplos que estudiaremos es fácilmente resuelto mediante un algoritmo
recursivo. Sin embargo, una solución iterativa al problema es mucho más complicada. El
enunciado del problema es el siguiente:
Sean tres estacas verticales y n discos de distintos radios que pueden insertarse en las
estacas formando torres. Inicialmente los n discos están situados todos en la primera
estaca por orden decreciente de radios (ver figura 3). Se trata de pasar los n discos de la
primera estaca a la última siguiendo las siguientes reglas:
1) En cada paso se mueve un único disco.
2) En ningún paso se puede colocar un disco sobre otro disco de radio menor.
3) Puede usarse la estaca central como estaca auxiliar.

Figura 4. Situación inicial del juego.

En el caso de una torre de altura uno, la solución es trivial:

   

Posición inicial

La solución en el caso de una torre de dos discos consta de solo tres pasos:

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 9

Posición inicial    


 

   

Si suponemos resuelto el problema para una torre de altura n-1 discos (es decir sabemos mover
n-1 discos de una estaca a otra usando la que queda como auxiliar), el problema de pasar n
discos desde la estaca izquierda a la derecha queda resuelto del siguiente modo:
1) Pasar n-1 discos de la estaca izquierda a la estaca central, utilizando la derecha como
auxiliar.
2) Mover el disco mayor de la estaca izquierda a la estaca derecha.
3) Mover n-1 discos de la estaca central a la derecha usando la izquierda como auxiliar.

                   ! " # $ % & ' ( & ) * ) ' + , - . ) *

/ 0 1 0 2 3 4 1 5 6 7 0 8 6 2 0 3 9 2 9 5 : 0 / 0 1 0 2 ; < = 3 4 1 5 6 1 0 3 9 2 9 5 : 0

Obsérvese que para solucionar el problema para una torre de altura n hemos supuesto resuelto
el problema para una torre de altura n-1; la solución es recursiva.
El programa en Modula-2 que usa la estrategia anterior para mostrar por pantalla los pasos
necesarios para solucionar una torre cuya altura se ha leído desde el teclado es:
MODULE Hanoi;
FROM IO IMPORT WrStr, WrLn, RdCard, RdLn;
CONST
IZQ = "Izquierda ";
DER = "Derecha ";
CEN = "Central ";
VAR

Facultad de Ciencias (Matemáticas) Informática


10 11.4 Ejemplos clásicos de recursividad

Altura : CARDINAL;
PROCEDURE WrMovimientos (N : CARDINAL;
Izq, Der, Cen : ARRAY OF CHAR);
BEGIN
IF N = 1 THEN
WrStr ("Mover de "); WrStr(Izq); WrStr("a "); WrStr(Der);
WrLn
ELSE
WrMovimientos(N-1, Izq, Cen, Der);
WrStr ("Mover de "); WrStr(Izq); WrStr("a "); WrStr(Der);
WrLn;
WrMovimientos(N-1, Cen, Der, Izq)
END
END WrMovimientos;

BEGIN
WrStr ("Dame la altura: ");
Altura := RdCard(); RdLn;

WrMovimientos(Altura, IZQ, DER, CEN)


END Hanoi.

La clave del programa anterior es el subprograma recursivo


PROCEDURE WrMovimientos (N : CARDINAL;
Izq, Der, Cen : ARRAY OF CHAR);

que muestra por pantalla los movimientos necesarios para mover N discos desde la estaca cuyo
nombre es Izq a la estaca cuyo nombre es Der, usando para ello la estaca Cen como estaca
auxiliar.
Este subprograma tiene como caso base aquél en el cual la altura de la torre a resolver es 1. Para
resolver este caso (mover una torre de altura 1 que esta situada en la estaca izquierda a la estaca
derecha) basta con realizar un único movimiento: mover un disco desde la estaca izquierda a la
derecha.
En otro caso, si la altura de la torre es mayor a 1:
1) Escribimos primero los pasos necesarios para mover N-1 discos desde la estaca
izquierda a la central usando la derecha como auxiliar. Esto se consigue con la llamada
recursiva
WrMovimientos(N-1, Izq, Cen, Der);

Obsérvese que Izq aparece como primer parámetro en esta llamada, por lo cual la
estaca desde la que se mueven los N-1 discos es desde la izquierda. Cen es el segundo
argumento, por lo que la estaca hacia la que se mueven los discos es la central. Por
último Der es el tercer argumento e indica que se use la estaca derecha como auxiliar.
2) Movemos el disco que queda en la estaca izquierda a la derecha
WrStr ("Mover de "); WrStr(Izq); WrStr("a "); WrStr(Der);

3) Escribimos los pasos necesarios para mover N-1 discos desde la torre central a la
derecha usando la izquierda como auxiliar.
WrMovimientos(N-1, Cen, Der, Izq);

Podemos comprobar que la solución cumple las tres condiciones de corrección:


1) Existe un caso base (la torre de altura 1).

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 11

2) Cada llamada recursiva se aproxima más al caso base ya que se decrementa en 1 el valor
de N.
3) El caso base será alcanzado. Dado que en cada llamada recursiva decrementamos el
valor de N exactamente uno, en algún momento N será 1.
La llamada que activa el subprograma desde el programa principal hace que se muestren los
pasos necesarios para mover tantos discos como indique la variable Altura desde la estaca
izquierda a la derecha, utilizando la central como auxiliar. Si ejecutamos el algoritmo para una
altura inicial de 3 discos obtenemos la siguiente salida por pantalla:
Dame la altura: 3
Mover de Izquierda a Derecha
Mover de Izquierda a Central
Mover de Derecha a Central
Mover de Izquierda a Derecha
Mover de Central a Izquierda
Mover de Central a Derecha
Mover de Izquierda a Derecha
El lector puede comprobar que la solución obtenida es correcta.

11.4.2 Algoritmos de búsqueda recursivos


En el tema 7 estudiamos varios algoritmos para buscar un elemento en un vector. Veremos
ahora como podemos expresar estos algoritmos de forma recursiva.
11.4.2.1 Búsqueda lineal recursiva
Recordemos que la búsqueda lineal consiste en ir inspeccionando cada elemento del vector de
izquierda a derecha hasta que, o bien se encuentra el elemento buscado, o bien se ha recorrido
todo el vector. En este último caso el elemento buscado no está en el vector.
Realizaremos la recursión sobre la longitud del vector. Así, podemos expresar esta idea como:
1) Si buscamos un elemento en un vector de tamaño cero podemos concluir que el
elemento no está en éste
2) Si el elemento buscado coincide con el primer elemento del vector entonces lo hemos
encontrado.
3) En otro caso deberemos buscar (llamada recursiva) a partir de la siguiente posición del
vector.
El siguiente subprograma implementa esta búsqueda recursiva:
PROCEDURE BuscarLineal ( A : ARRAY OF ELEMENTO;
Elemento : ELEMENTO ) : INTEGER;
PROCEDURE BuscarDesde( A : ARRAY OF ELEMENTO;
Elemento : ELEMENTO;
Primero, Maximo :CARDINAL ) : INTEGER;
CONST
NOENCONTRADO = -1;
BEGIN
IF Primero > Maximo THEN
RETURN NOENCONTRADO
ELSIF A[Primero] = Elemento THEN
RETURN Primero
ELSE
RETURN BuscarDesde(A, Elemento, Primero+1, Maximo)
END

Facultad de Ciencias (Matemáticas) Informática


12 11.4 Ejemplos clásicos de recursividad

END BuscarDesde;
BEGIN
RETURN BuscarDesde(A, Elemento, 0, HIGH(A))
END BuscarLineal;

La función devuelve la posición en la que se encontró el elemento o el valor -1 si no se


encontró. El algoritmo recursivo se implementa en BuscarDesde. Esta función busca
recursivamente el elemento Elemento en el vector A . En todo momento, el segmento del vector
que queda por inspeccionar es el determinado por las variables Primero y Maximo. Con cada
llamada recursiva el tamaño del segmento a inspeccionar se reduce en una unidad. El caso 1) se
produce cuando (Primero > Maximo). El caso 2) es el correspondiente a la parte ELSIF
mientras que el 3) aparece tras la cláusula ELSE. La función BuscarLineal hace que comience
la búsqueda, estableciendo como segmento inicial la totalidad del vector.
Obsérvese que al ser BuscarDesde una función recursiva, será probablemente llamada varias
veces. Al comienzo de cada llamada se reservará espacio para cada uno de los parámetros por
valor de esta función. Si el tamaño de la variable A es relativamente grande, reservar espacio
para ella en cada llamada puede ser costoso (en tiempo y consumo de memoria). Para
solucionar este problema podemos, aunque su valor no sea modificado, declarar este parámetro
por referencia (VAR A:ARRAY OF ELEMENTO), ya que no se crean nuevas copias con cada
llamada para este tipo de parámetros.
11.4.2.2 Búsqueda binaria recursiva
La búsqueda binaria puede ser también fácilmente implementada de modo recursivo. Si el
vector está ordenado en orden ascendente, el algoritmo es el siguiente:
1) Si buscamos un elemento en un vector de tamaño cero podemos concluir que el
elemento no está en éste
2) Si el elemento que ocupa la posición central del vector coincide con el buscado, lo
hemos encontrado en la posición central.
3) Si el elemento buscado es menor que el que ocupa la posición central del vector, buscar
en la mitad izquierda del segmento.
4) En otro caso, buscar en la mitad derecha del segmento.
El siguiente subprograma implementa esta búsqueda recursiva:
PROCEDURE BuscarBinaria ( A : ARRAY OF ELEMENTO;
Elemento : ELEMENTO ) : INTEGER;
PROCEDURE BuscarEn (A : ARRAY OF ELEMENTO;
Elemento : ELEMENTO;
Primero, Ultimo :CARDINAL ) : INTEGER;
CONST
NOENCONTRADO = -1;
VAR
Central : CARDINAL;
BEGIN
IF Primero > Ultimo THEN
RETURN NOENCONTRADO
ELSE
Central := (Primero + Ultimo)DIV 2;
IF A[Central] = Elemento THEN
RETURN Central
ELSIF A[Central] > Elemento THEN
RETURN BuscarEn(A, Elemento, Primero, Central-1)
ELSE
RETURN BuscarEn(A, Elemento, Central+1, Ultimo)
END
END

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 13

END BuscarEn;
BEGIN
RETURN BuscarEn(A, Elemento, 0, HIGH(A))
END BuscarLineal;

En este caso, el tamaño del segmento donde realizar la búsqueda queda reducido a la mitad en
cada llamada recursiva. Las mismas consideraciones de eficiencia estudiadas para la búsqueda
lineal recursiva son aplicables a este algoritmo.

Facultad de Ciencias (Matemáticas) Informática


Tema 11. Diseño de algoritmos recursivos 1

Relación de problemas (Tema 11)


1. El máximo común divisor de dos números enteros se puede definir con ayuda de las
siguientes propiedades elementales:
mcd ( a, b) = mcd' ( a , b ),
mcd' (a, a) = a,
mcd' (a, 0) = a,
mcd' (a, b) = mcd' (b, a),
mcd' (a, b) = mcd' (a − b, b), si a>b
Escribe un programa recursivo que calcule el máximo común divisor de dos números
enteros usando las propiedades citadas.
Sigue las llamadas realizadas por el algoritmo que diseñes para calcular el máximo común
divisor de 6 y 32.
2. Observe que la última propiedad del ejercicio anterior permite reducir el cálculo del
máximo común divisor de dos números al mismo problema pero con dos números más
pequeños, utilizando para ello la operación diferencia. Tal operación puede sustituirse por
el resto de la división entera, ya que si k es un divisor de a y b (siendo a>b), entonces k
también es un divisor del resto de la división de a entre b:
mcd ' (a , b) = mcd ' (a MOD b, b), si a>b
Dicho resto puede ser cero, y en ese caso el máximo común divisor es el valor de b
(puesto que a sería un múltiplo de b). Esta propiedad permite acelerar el método del
ejercicio anterior.
Escribe un programa recursivo que calcule el máximo común divisor de dos números
enteros usando las propiedades citadas, junto con las del ejercicio anterior.
Sigue las llamadas realizadas por el algoritmo que diseñes para calcular el máximo común
divisor de 6 y 32.
3. Escribe un programa recursivo que calcule números combinatorios teniendo en cuenta las
siguientes propiedades:

 m
  = 1, si n = 0 o si m=n
 n
 m
  = m, si n = 1 o si n=m − 1
 n
 m  m − 1  m − 1
  =  + 
 n   n − 1  n 
Sigue las llamadas que realiza el algoritmo para calcular 5 sobre 2.
4. Escribe un programa recursivo que calcule la suma de los primeros N números pares
naturales.
5. La función de Ackermann se describe del siguiente modo:

Facultad de Ciencias (Matemáticas) Informática


2 Tema 11. Diseño de algoritmos recursivos

A(m, n) = n + 1, si m = 0
A(m, n) = A(m − 1,1), si n = 0
A(m, n) = A(m − 1, A(m, n − 1)), si m > 0 y n>0
Esta función recursiva es muy conocida porque aun pareciendo muy simple da lugar a
cálculos muy complicados. Por ejemplo la llamada A(2, 2) produce 27 llamadas recursivas
para devolver el valor 7.
Escribe un subprograma recursivo que calcule la función de Ackermann para valores de m
y n dados. Utiliza éste para escribir un programa que produzca la siguiente tabla:
m\n 0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
2 3 5 7 9 11 13 15 17
... ... ... ... ... ... ... ... ...
Puede que notes que el ordenador tarda un poco en calcular los últimos valores de la tabla.
Esto no es extraño ya que el cálculo de A(2, 6), por ejemplo, da lugar a 172.233 llamadas
recursivas.
NOTA: Para la versión 1 de TopSpeed se produce un error al intentar evaluar A(3, n) si n
> 7, A(4, n) si n > 0 o A(m,n) si m > 4.
6. Escribe un programa recursivo que lea un número entero positivo desde teclado y lo
escriba por pantalla en orden inverso (si se lee 124 se escribirá 421).
7. Escribe un programa recursivo que calcule la suma de las cifras de un número entero
positivo leído de teclado.
8. Escribir un programa recursivo que lea un número entero positivo expresado en base 10
desde teclado y lo pase a otra base entre 2 y 10 también leída de teclado. No se conoce a
priori el número de cifras del número en la base destino, por lo cual no se podrá utilizar
un array para ir almacenando las cifras calculadas.
9. Supongamos que tenemos un ordenador que sólo trabaja con números naturales y para el
cual sólo existen dos funciones aritméticas:
PROCEDURE Suc(n : CARDINAL) : CARDINAL;
BEGIN
RETURN n+1
END Suc;

PROCEDURE Pred(n : CARDINAL) : CARDINAL;


BEGIN
IF n = 0 THEN
WrStr(“ERROR, número negativo”);
HALT
ELSE
RETURN n-1
END
END Pred;

Escribe los siguientes subprogramas de un modo recursivo:


PROCEDURE Suma (a, b : CARDINAL) : CARDINAL;
PROCEDURE Resta (a, b : CARDINAL) : CARDINAL;
PROCEDURE Producto (a, b : CARDINAL) : CARDINAL;

Informática Facultad de Ciencias (Matemáticas)


Tema 11. Diseño de algoritmos recursivos 3

PROCEDURE Cociente (a, b : CARDINAL) : CARDINAL;

Teniendo en cuenta las siguientes propiedades:


Suma (a ,0) = a
Suma (a , b) = Suc( Suma (a , Pred (b)), si b ≠ 0
Resta (a ,0) = a
Resta (0, a ) = ERROR, numero negativo
Resta (a , b) = 0, si a = b
Resta(a,b) = Resta ( Pred(a), Pred(b)), si a ≠ b
Producto(a ,0) = 0
Producto(a , b) = Suma ( Producto(a , Pred (b)), a )
Cociente(a ,0) = ERROR, division por cero
Cociente(a , b) = Suc(Cociente( Resta (a , b), b)), si a ≥ b
Cociente(a , b) = 0, si a < b
Recuerda que no puedes usar +, -, *, DIV o MOD en estos cuatro subprogramas.
Intenta deducir tú las propiedades de Resto(a,b) (valor del resto obtenido al dividir a por
b) y escribe el correspondiente subprograma recursivo.
10. Escribir un programa recursivo que lea una cadena de caracteres acabada en punto desde
el teclado y la imprima al revés por pantalla. No se conoce el tamaño máximo de la
cadena a priori, por lo cual el programa no podrá almacenar la cadena en un array tal
como se va leyendo. Utiliza lectura múltiple de datos (sin RdLn).

Nota: Hablamos de programa recursivo cuando utiliza subprogramas recursivos.

Facultad de Ciencias (Matemáticas) Informática


4 Tema 11. Diseño de algoritmos recursivos

Relación complementaria (Tema 11)

11. Escribe un programa recursivo que calcule potencias enteras de números reales, usando
para ello las siguientes propiedades:
a0 = 1
an = a ⋅ a n −1 si n > 0
−n
 1
an =   si n < 0
 a
12. Escribe un procedimiento recursivo que escriba N saltos de línea por pantalla, siendo N un
argumento de este subprograma.
13. Escribe un programa recursivo que calcule el capital Cn obtenido, situando el capital C0 a
interés compuesto durante n años al interés anual r (expresado en porcentaje). La
recurrencia que permite calcular Cn es:
C1 = C0 ⋅ (1 + r / 100)
C2 = C1 ⋅ (1 + r / 100)
...
Cn = Cn −1 ⋅ (1 + r / 100)
14. Obtén una fórmula no recursiva para Cn .Escribe un programa que calcule Cn a partir de C0
y r utilizando la fórmula obtenida.
15. Escribir un programa recursivo que lea un número entero positivo expresado en base b
desde teclado (con b ≤ 9) y lo pase a base 10. (Utiliza el tipo LONGCARD)
16. Escribe una función recursiva que devuelva el elemento de mayor valor en un array de
enteros.
17. Escribe una función recursiva que devuelva la suma de los valores almacenados en un
array de enteros.
18. Escribe una función recursiva que devuelva el n-ésimo elemento de menor valor en un
array de enteros. Por ejemplo, si el array contiene los valores 12 7 3 9 1 5 21 y n =1
devuelve 1, si n=2 devuelve 3, si n=5 devuelve 9 y si n=28 se produce un error.

Informática Facultad de Ciencias (Matemáticas)


Tema 12. Estructuras de datos dinámicas 1

Apuntes para la asignatura

Informática
Departamento de Lenguajes y Ciencias de la Facultad de Ciencias (Matemáticas)
Computación http://www.lcc.uma.es/personal/pepeg/mates
UNIVERSIDAD DE MÁLAGA

Tema Estructuras de datos dinámicas


12.
12.1 Gestión dinámica de la memoria ................................................................ ......2
12.2 El tipo POINTER................................................................ ............................... 3
12.3 Procedimientos NEW y DISPOSE ................................................................ ....4
12.4 Operaciones con Punteros ................................................................ ............... 5
12.5 Listas enlazadas ................................................................ ............................... 8
12.5.1 Listas ordenadas................................................................ ....................... 21

Bibliografía
• Programación 1. José A. Cerrada y Manuel Collado. Universidad Nacional de
Educación a Distancia.
• Programming with TopSpeed Modula-2. Barry Cornelius. Addison-Wesley.
• Fundamentos de programación. Algoritmos y Estructuras de datos. L. Joyanes Aguilar.
McGraw Hill.
• Pascal y estructuras de datos. Nell Dale y Susan C. Lilly. McGraw-Hill

Facultad de Ciencias (Matemáticas) Informática


2 12.1 Gestión dinámica de la memoria

Introducción
Las estructuras de datos dinámicas permiten trabajar con variables cuyo tamaño no es conocido
en tiempo de compilación. Estas estructuras pueden crecer o decrecer durante la ejecución del
programa, según sea necesario. En Modula-2, estas estructuras son implementadas utilizando el
tipo puntero (POINTER).
En este tema, introducimos el tipo puntero y sus operaciones asociadas. Una de las estructuras
dinámicas más simples que se pueden implementar con punteros es la lista enlazada.
Implementaremos un módulo de biblioteca para el manejo de listas enlazadas.

12.1 Gestión dinámica de la memoria


Hasta ahora, todos los tipos de datos estudiados, ya sean simples o estructurados, tienen una
propiedad común: son estáticos. Esto significa que las variables de estos tipos mantienen la
misma estructura y tamaño durante toda la ejecución del programa. El tamaño de estas variables
se debe anticipar cuando se escribe el programa y queda fijado durante la compilación (tiempo
de compilación). Es imposible ampliar el tamaño de la estructura durante la ejecución del
programa (tiempo de ejecución).
Así, por ejemplo:
• Si declaramos un Array de cinco elementos de tipo INTEGER, el contenido de cada una
de las casillas podrá cambiar, pero el tamaño máximo de este será siempre fijo (cinco
elementos enteros).
• Si declaramos una variable REAL puede cambiar su contenido, pero no el tamaño
asignado por el compilador para ella (el necesario para un único valor de tipo REAL).
Sin embargo, hay muchas situaciones en las que no sólo debe cambiar el contenido o valor de
una variable, sino también su tamaño. La estructura debe ir creciendo y decreciendo mientras se
ejecuta el programa. En esos casos la cantidad de memoria necesaria para almacenar cierta
información no se puede determinar en tiempo de compilación, sino que hay que esperar a que
el programa se esté ejecutando (tiempo de ejecución).
La técnica usada para manejar estas situaciones es la asignación dinámica de memoria.
Con esta técnica tendremos variables dinámicas, que se caracterizan porque pueden crearse
o destruirse en tiempo de ejecución. Veremos que estas estructuras dinámicas son
extremadamente flexibles, aunque es muy fácil cometer errores cuando son programadas.
El siguiente ejemplo muestra la necesidad de esta técnica: supongamos que se desea escribir un
programa que lea una cantidad de números reales variable desde teclado, calcule la media de
todos ellos y, por último, muestre por pantalla aquellos números leídos cuyos valores sean
mayores a la media. La cantidad total de números se leerá al principio del programa.
Si nos dicen que el número máximo de valores que se puede leer es MAX, podemos definir un
array de reales con este tamaño. Sin embargo, esta aproximación tiene un problema. Si MAX es
grande, estaremos desperdiciando bastante memoria si el total introducido luego es bastante
menor.
Otro problema más difícil de resolver es que no se sepa de antemano el número máximo de
números a leer. En este caso la solución del Array no es válida, ya que, sea cual sea el valor que
demos a MAX, siempre podemos quedarnos cortos. Una gran cantidad de problemas reales para
los que se utilizan los ordenadores tiene esta característica: no se conoce la cantidad máxima de
datos a tratar.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 3

Para resolver el problema anterior, necesitamos que el lenguaje de programación permita:


1) Ir creando nuevas variables cada vez que las necesitemos (cada vez que vayamos a leer
un número en nuestro ejemplo).
2) Acceder a las variables creadas.
3) Destruir variables cuando ya no sean necesarias.
Conseguiremos las dos primeras características mediante instrucciones de asignación y
liberación dinámica de memoria. La tercera se consigue mediante las variables de tipo
puntero.
Mediante la asignación dinámica de memoria conseguiremos estructuras de datos cuyo tamaño
será variable. Es importante observar que el tamaño de estos datos solo estará acotado por el
tamaño máximo de la memoria del ordenador en la cual se esté ejecutando el programa, pero no
por un valor máximo determinado a priori.

12.2 El tipo POINTER


Una variable de tipo puntero no contiene un dato, como los tipos estudiados hasta ahora,
sino que contiene la dirección (o posición) que ocupa en la memoria del ordenador otra
variable. Se dice que la variable puntero apunta a esta última variable. A la variable puntero se
la llama también variable referencia. A la variable apuntada se la llama variable
referenciada o anónima.
Cuando se declara una variable puntero es necesario indicar el tipo de la variable apuntada. El
siguiente ejemplo declara una variable, cuyo nombre es Ptr, como una variable de tipo puntero
a un valor entero:
VAR
Ptr : POINTER TO INTEGER
;

Esto significa que el valor de la variable Ptr es una posición en la memoria del ordenador en la
cual hay situada un dato de tipo INTEGER:

Ptr ...
14000 13996
13998
14000 57
14002
...

En el ejemplo, la variable Ptr contiene como valor la posición de memoria 14000. En esta
dirección de memoria está situado un valor de tipo INTEGER (57).
Normalmente, las direcciones de memoria concretas almacenadas en las variables puntero no
son importantes, por lo que la situación anterior la representamos como:

Ptr 57

Es decir, Ptr apunta a una posición de memoria en la cual se almacena una variable de tipo
INTEGER cuyo valor es 57. En este ejemplo la variable referencia es Ptr, mientras que la
referenciada es la casilla en la que se encuentra el valor 57. Esta última se llama también
anónima porque no tiene nombre.

Facultad de Ciencias (Matemáticas) Informática


4 12.3 Procedimientos NEW y DISPOSE

El único modo de acceder a la variable anónima es mediante el puntero que la referencia. Si


queremos asignar el valor 100 a la variable anónima, la instrucción correspondiente en Modula-
2 es:
Ptr^ := 100;

Esta instrucción se lee: asignar a la variable apuntada por Ptr el valor 100. Para indicar que lo
que queremos modificar es la variable anónima hay que escribir un signo ^ tras el nombre de la
variable referencia. (Obsérvese que Ptr^ se asemeja con partir de la variable Ptr en el dibujo y
seguir la flecha). Tras esta instrucción la situación es:

Ptr 100

Si queremos acceder a la variable anónima para leer su valor también hemos de utilizar el
nombre de la variable referencia y un signo ^. Así, para incrementar en dos el valor de la
variable anónima hemos de escribir:
Ptr^ := 2 + Ptr^;

con lo obtenemos:

Ptr 102

Una variable puntero puede almacenar también un valor especial: NIL. Este valor indica que el
puntero no apunta a nada (no contiene una dirección de memoria válida). Representaremos
gráficamente con una línea diagonal un puntero que contiene el valor NIL:

Ptr

Normalmente el valor NIL se utiliza para inicializar las variables de tipo puntero. Esta
inicialización no es automática por lo que debe ser realizada explícitamente por el programador
(las variables punteros no son una excepción a la regla en Modula-2, por lo que su valor
predeterminado es indeterminado).

12.3 Procedimientos NEW y DISPOSE


Cuando declaramos una variable de tipo puntero como por ejemplo
VAR Ptr : POINTER TO INTEGER;

la variable referencia Ptr se crea estáticamente, en tiempo de compilación, pero la variable


referenciada o anónima debe crearse posteriormente, en tiempo de ejecución. La forma de crear
una variable anónima es mediante el procedimiento de asignación dinámica de memoria NEW.
Este procedimiento toma como argumento una variable de tipo puntero, crea una nueva variable
del tipo al cual apunta el argumento y hace que el puntero argumento apunte a la variable
dinámica creada. El valor previo que tuviese la variable puntero se pierde. Tras ejecutar la
instrucción
NEW(Ptr);

la situación es la siguiente:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 5

Ptr ¿...?

Obsérvese que se ha creado una nueva variable anónima y que Ptr apunta a ella, pero que la
variable anónima no toma ningún valor por defecto. El programador debe darle un valor
explícitamente:
Ptr^ := 15;

Ptr 15

Con este procedimiento podemos crear tantas variables (dinámicamente) como necesitemos
durante la ejecución del programa, y podemos referenciarlas mediante punteros, con lo que
solucionamos el problema de partida.
Al crear una variable dinámica la cantidad de memoria libre en el ordenador disminuye. Las
variables dinámicas, una vez creadas, siguen ocupando espacio de memoria hasta que se
destruyan explícitamente. Cuando se destruye una variable dinámica el espacio que ocupaba
vuelve a quedar disponible. Para destruir una variable dinámica se utiliza el procedimiento
DISPOSE. Este procedimiento toma como argumento un puntero a una variable dinámica, libera
la memoria que la variable dinámica ocupa y pone el puntero a valor NIL para indicar que ya no
apunta a nada. Tras ejecutar
DISPOSE(Ptr);

la situación es la siguiente:

Ptr 15

Una vez destruida, la variable dinámica no existe, por lo que es ilegal intentar acceder a ella. La
memoria libre del sistema aumenta al destruir una variable dinámica.
En realidad, NEW y DISPOSE son solo dos modos más sencillos de escribir llamadas a dos
subprogramas más complicados ALLOCATE y DEALLOCATE que se encuentran en la librería
Storage. Por esto, cuando un programa va a utilizar NEW y DISPOSE, debe importar
ALLOCATE y DEALLOCATE:
FROM Storage IMPORT ALLOCATE, DEALLOCATE;

Nótese que, curiosamente, no es válido importar NEW y DISPOSE directamente.

12.4 Operaciones con Punteros


Las operaciones que se pueden realizar con variables de tipo puntero son básicamente tres:
1) Derreferenciación.
2) Asignación
3) Comparación
La derreferenciación es la operación mediante la cual accedemos a la variable anónima
apuntada por el puntero, añadiendo para ello el operador ^ tras el nombre de la variable puntero.
A modo de ejemplo, supongamos las siguientes declaraciones de tipos y variables:
TYPE

Facultad de Ciencias (Matemáticas) Informática


6 12.4 Operaciones con Punteros

COMPLEJO = RECORD
Real, Imag : REAL
END;
PCOMPLEJO = POINTER TO COMPLEJO;

VAR
Ptr1, Ptr2 : PCOMPLEJO;

Para comenzar a trabajar con estas variables, crearemos una variable dinámica y la
referenciaremos mediante la variable Ptr1.
NEW(Ptr1);

Real Imag
Ptr1 ¿...? ¿...?

La nueva variable dinámica es un registro que cuenta con dos campos (Real e Imag) ambos de
tipo REAL. Sin embargo, los valores de estos campos están indefinidos. Para asignar valores a
estos campos es necesario derreferenciar la variable puntero:
Ptr1^.Real := 5.0;
Ptr1^.Imag := 1.2;

Real Imag
Ptr1 5.0 1.2

El mismo resultado puede ser obtenido usando la sentencia WITH:


WITH Ptr1^ DO
Real := 5.0;
Imag := 1.2
END;

La asignación es la operación que permite asignar a una variable puntero otra variable del
mismo tipo. El efecto conseguido al asignar dos variables puntero es que ambas apuntan a la
misma variable dinámica. Si en la situación actual ejecutamos la sentencia
Ptr2 := Ptr1;

las variables quedan:


Real Imag
Ptr1
5.0 1.2
Ptr2

Es decir, podemos acceder a la variable anónima por dos caminos: Ptr1^ o Ptr2^. En este caso
decimos que la variable anónima está múltiplemente referenciada. Como no hay dos copias de
la variable anónima, si se modifica su contenido mediante un camino, al acceder mediante el
otro veremos el cambio. Por ejemplo, el siguiente código produce como salida por pantalla 8.0:
Ptr1^.Real := 8.0;
WrReal(Ptr2^.Real, 1, 0);

ya que el estado de la memoria es:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 7

Real Imag
Ptr1
8.0 1.2
Ptr2

Un fallo muy común a la hora de trabajar con referencias múltiples es que se libere la variable
dinámica mediante una de las referencias pero no se ponga a NIL la otra:
DISPOSE(Ptr1);
WrReal(Ptr2^.Real, 1, 0); (* ERROR !!! *)

produciría un error ya que la variable anónima ha sido destruida, por lo que no se debe acceder
a ella a través de Ptr2:
Real Imag
Ptr1
8.0 1.2
Ptr2

Por otro lado, a una variable anónima siempre se le puede asignar el valor NIL, sea cual sea el
tipo de la variable anónima. Éste es el segundo tipo de asignación disponible para variables
punteros
Ptr2 := NIL;

Es un error intentar derreferenciar un puntero cuyo valor sea NIL. En este caso, el programa
finaliza inmediatamente con el consiguiente error. Este es uno de los errores más comunes que
se comenten al programar con punteros. Es MUY IMPORTANTE asegurarnos de que un
puntero no vale NIL antes de derreferenciarlo.
Por último, las variables de tipo puntero pueden compararse entre sí si tienen el mismo tipo o
con NIL (siempre). El resultado de la expresión Ptr1 = Ptr2 es TRUE si las dos variables
apuntan a la misma variable anónima (referencias múltiples). Sin embargo, si las dos variables
punteros apuntan a dos variables anónimas distintas el resultado será FALSE, aunque el
contenido de las dos variables anónimas sea idéntico. Así, el siguiente ejemplo
NEW(Ptr1);
NEW(Ptr2);
WITH Ptr1^ DO
Real := 15.0;
Imag := 20.0
END;
WITH Ptr2^ DO
Real := 15.0;
Imag := 20.0
END;
IF Ptr1 = Ptr2 THEN
WrStr(“Iguales”)
ELSE
WrStr(“Distintas”)
END

produce por pantalla la salida “Distintas”. Si lo que se desea es comprobar que los
contenidos de las dos variables son iguales, se debe usar la derreferenciación Ptr1^ = Ptr2^,
que será TRUE si el contenido de la variable anónima apuntada por Ptr1 es igual al de la
variable anónima apuntada por Ptr2, aunque dichas variables no sean las mismas.

Facultad de Ciencias (Matemáticas) Informática


8 12.5 Listas enlazadas

12.5 Listas enlazadas


Lo que realmente hace de los punteros una herramienta potente es que pueden apuntar a
variables que a su vez contienen punteros (típicamente registros en los cuales al menos uno de
los campos es un puntero). Gracias a esta característica, podemos crear estructuras de datos
dinámicas (el tamaño crece y decrece).
Una de estas estructuras es la lista enlazada. Se trata de una estructura que almacena una
cantidad variable de datos, pero con la restricción de que todos tienen el mismo tipo (tipo base).
La lista se representa mediante un puntero que apunta a un registro. Este registro contiene dos
campos: el primero almacena el primer elemento de la lista(un dato del tipo base) y el segundo
un puntero a otro registro (que corresponde al siguiente dato). De este modo, cada dato contiene
la dirección del siguiente elemento. Cada uno de los registros que forman parte de la lista se
llaman nodos. El campo puntero del último nodo de la lista enlazada contiene el valor NIL. El
siguiente ejemplo muestra una lista de tres datos:
L
1 2 4

Cuando la lista está vacía (no contiene ningún dato), el valor del puntero inicial debe ser NIL:
L

El acceso a los elementos de una lista no es directo (como ocurría en los arrays). Para acceder a
un elemento partimos del puntero inicial y hemos de ir pasando por todos los nodos anteriores a
él. Además, solo es posible pasar de un nodo a su siguiente, pero no al previo.
Las declaraciones de tipo para la estructura lista (con tipo base INTEGER) son:
TYPE
ELEMENTO = INTEGER;
PNODO = POINTER TO NODO;
NODO = RECORD
Elem : ELEMENTO;
Sig : PNODO
END;
LISTA = PNODO;

Obsérvese que el tipo NODO aparece en la definición de PNODO antes de estar declarado. En
Modula-2, solo se puede utilizar un tipo aún no definido en la definición de otro, cuando se está
definiendo un tipo puntero. Por supuesto, el tipo no definido debe definirse posteriormente
(como ocurre en el ejemplo).
La estructura lista es una estructura recursiva, ya que es un puntero a un registro de dos
componentes, donde la segunda es a su vez una lista.
Junto a la definición del tipo para las listas, es necesario definir una serie de operaciones para
su manejo. Podemos hacer esto mediante un módulo de biblioteca. El módulo de definición
correspondiente puede ser el siguiente:
DEFINITION MODULE Lista;
TYPE
ELEMENTO = INTEGER;
PNODO = POINTER TO NODO;
NODO = RECORD
Elem : ELEMENTO;
Sig : PNODO
END;
LISTA = PNODO;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 9

PROCEDURE Crear () : LISTA;


PROCEDURE InsertarInicio (VAR L : LISTA; Elem : ELEMENTO);
PROCEDURE InsertarFinal (VAR L : LISTA; Elem : ELEMENTO);
PROCEDURE InsertarEnPosicion (VAR L : LISTA; Elem : ELEMENTO;
Pos : CARDINAL);
PROCEDURE Eliminar (VAR L : LISTA; Elem : ELEMENTO);
PROCEDURE EliminarEnPosicion (VAR L : LISTA; Pos : CARDINAL);
PROCEDURE ElementoEnPosicion (L : LISTA; Pos :CARDINAL) : ELEMENTO;
PROCEDURE Longitud (L : LISTA) : CARDINAL;
PROCEDURE WrLista (L : LISTA);
PROCEDURE Destruir (VAR L : LISTA);

END Lista.

El significado de cada una de las operaciones es:


• Crear: devuelve una lista vacía (sin ningún elemento). Se usa para inicializar variables
del tipo LISTA.
• InsertarInicio: inserta un nuevo elemento en la primera posición de una lista.
• InsertarFinal: inserta un nuevo elemento en la última posición de una lista.
• InsertarEnPosición: inserta un nuevo elemento dentro de la lista en la posición
indicada por el tercer parámetro. Los nodos de la lista comienzan a numerarse por uno.
• Eliminar: elimina la primera aparición del elemento Elem de la lista. Si el elemento
está repetido varias veces dentro de la lista solo se elimina la primera (más a la
izquierda) ocurrencia.
• EliminarEnPosicion: elimina de una lista el elemento que ocupa la posición dada por
el segundo argumento.
• ElementoEnPosicion: devuelve el valor del elemento que ocupa la posición dada por
el segundo argumento, pero no lo elimina de la lista.
• Longitud: devuelve el número de elementos en una lista.
• WrLista: escribe el contenido de una lista por pantalla.
• Destruir: elimina todos los elementos de una lista, liberando la memoria que
ocupaban.
Obsérvese que en las operaciones donde la lista se ve modificada es necesario pasar el
correspondiente argumento por referencia (VAR).
La implementación de cada operación aparecerá en el correspondiente módulo de
implementación. Es necesario importar ALLOCATE y DEALLOCATE ya que usaremos NEW y
DISPOSE. También definimos un par de constantes que representan la lista vacía y el valor del
puntero final de una lista (ambos son NIL):
IMPLEMENTATION MODULE Lista;

FROM Storage IMPORT ALLOCATE, DEALLOCATE;


FROM IO IMPORT WrStr, WrInt, WrChar;

CONST
LISTAVACIA = NIL;
FINLISTA = NIL;

...
END Lista.

Facultad de Ciencias (Matemáticas) Informática


10 12.5 Listas enlazadas

La operación Crear es inmediata. Basta con devolver NIL ya que por convenio las listas vacías
se representan con este valor.
PROCEDURE Crear () : LISTA;
BEGIN
RETURN LISTAVACIA
END Crear;

Supongamos que queremos insertar el elemento 3 al principio de la lista


L
1 2

Para ello hemos de seguir los siguientes pasos:


1) Creamos un nuevo nodo (lo referenciaremos mediante la variable puntero PtrNuevo):
L
1 2

PtrNuevo ? ?

NEW(PtrNuevo);

2) Copiamos el valor a insertar en el campo Elem


L
1 2

PtrNuevo 3 ?

PtrNuevo^.Elem := 3;

3) Enlazamos el nuevo nodo con el principio de la lista


L
1 2

PtrNuevo 3

PtrNuevo^.Sig := L;

4) Por último, la lista comienza ahora por el nuevo nodo:


L
1 2

PtrNuevo 3

L := PtrNuevo;

Podemos unir todo lo anterior en el siguiente subprograma:


PROCEDURE InsertarInicio (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrNuevo : PNODO;
BEGIN
NEW(PtrNuevo);
PtrNuevo^.Elem := Elem;
PtrNuevo^.Sig := L;
L := PtrNuevo
END InsertarInicio;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 11

La variable PtrNuevo será destruida al salir del subprograma, ya que es una variable local a
éste.
Para que el subprograma anterior pueda ser considerado correcto es necesario comprobar que
también funciona en el caso de que la lista inicial sea vacía:
L

PtrNuevo ? ?

NEW(PtrNuevo);

PtrNuevo 3

PtrNuevo^.Elem := 3;
PtrNuevo^.Sig := L;

PtrNuevo 3

L := PtrNuevo

El orden en que se realizan las operaciones debe ser exactamente el indicado en el


subprograma, ya que si invirtiésemos el orden de las dos últimas instrucciones, perderíamos la
referencia al antiguo primer nodo de la lista. A la hora de trabajar con listas enlazadas es muy
importante no dejar nunca ningún nodo inaccesible. Para evitar esto se usarán variables
punteros auxiliares siempre que sea necesario.
También es importante observar que el parámetro L pasa por referencia ya que su valor se ve
modificado dentro del subprograma.
Para insertar un elemento nuevo al final de una lista creamos un nuevo nodo, copiamos el valor
del elemento y ponemos el puntero del campo Sig a FINLISTA (ya que este nodo ocupará la
última posición de la lista). A continuación consideramos dos casos:
• Si la lista está vacía enlazamos L con este nodo.
• En otro caso hay que recorrer la lista hasta encontrar el último nodo. Esto se suele hacer
utilizando una variable de tipo puntero (Ptr en nuestro caso) que se desplaza hasta
alcanzar un nodo cuyo campo Sig sea FINLISTA. Una vez localizado el último nodo lo
enlazamos con el nuevo.
PROCEDURE InsertarFinal (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrNuevo, Ptr : PNODO;
BEGIN
NEW(PtrNuevo);
PtrNuevo^.Elem := Elem;
PtrNuevo^.Sig := FINLISTA;
IF L = LISTAVACIA THEN
L := PtrNuevo
ELSE
Ptr := L;
WHILE Ptr^.Sig <> FINLISTA DO
Ptr := Ptr^.Sig
END;

Facultad de Ciencias (Matemáticas) Informática


12 12.5 Listas enlazadas

Ptr^.Sig := PtrNuevo
END
END InsertarFinal;

El siguiente ejemplo muestras los pasos seguidos para insertar un elemento de valor 4 en una
lista con dos elementos (1 y 2):
L
1 2

Ptr

L
1 2

Ptr

L
1 2 4

Ptr PtrNuevo

Este algoritmo también se puede describir más elegantemente de modo recursivo. Para insertar
un elemento en una lista vacía basta con crear un nodo y enlazarlo como en InsertarInicio.
En otro caso hacemos una llamada recursiva, pero con el segmento de lista que comienza por el
segundo elemento:
PROCEDURE InsertarFinal (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrNuevo : PNODO;
BEGIN
IF L = LISTAVACIA THEN
NEW(PtrNuevo);
PtrNuevo^.Elem := Elem;
PtrNuevo^.Sig := FINLISTA;
L := PtrNuevo
ELSE
InsertarFinal(L^.Sig, Elem)
END
END InsertarFinal;

Por ejemplo, si queremos insertar un 4 en la lista mostrada se producen dos llamadas recursivas.
El valor de L en la primera llamada recursiva es L(1 y en la segunda L(2. En la última llamada,
como L(2 es LISTAVACIA, se crea el nuevo nodo y se enlaza.
1) Llamada inicial: InsertarFinal (L, 4)
L
1 2

2) Primera Llamada recursiva: InsertarFinal (L(1, 4)


L L(1
1 2

3) Segunda Llamada recursiva: InsertarFinal (L(2, 4)

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 13

L L(2
1 2 4

PtrNuevo

Las operaciones relativas a listas enlazadas suelen ser fáciles de implementar de modo
recursivo ya que la estructura es recursiva. En las operaciones recursivas suele ser necesario
considerar siempre dos casos: la lista está vacía (caso base) o no. La llamada recursiva suele
aproximarse al caso base tomando la lista que se obtiene saltando el nodo inicial (L^.Sig).
Para insertar un nuevo elemento en una posición dada de una lista se contemplan tres casos:
• Si la posición es cero, se produce un error ya que la primera posición válida es uno que
indica insertar al comienzo de la lista.
• Si la posición es uno llamamos al subprograma InsertarInicio.
• En otro caso utilizamos Ptr para localizar el nodo previo a donde se ha de insertar. Para
ello se parte del comienzo de la lista y se avanza Pos-2 veces (cuidando no alcanzar el
final de la lista). Si al intentar localizar el nodo previo se alcanzó el final de la lista es
que la posición indicada para insertar no es válida (es mayor al número de elementos en
la lista). En otro caso, creamos un nuevo nodo y lo enlazamos tras el nodo localizado.
PROCEDURE InsertarEnPosicion (VAR L : LISTA; Elem : ELEMENTO;
Pos : CARDINAL);
VAR PtrNuevo, Ptr : PNODO;
Avanzados : CARDINAL;
BEGIN
IF Pos = 0 THEN
WrStr("Error: InsertarEnPosicion fuera de rango");
HALT
ELSIF Pos = 1 THEN
InsertarInicio(L, Elem)
ELSE (* Pos >= 2 *)
Avanzados := 0;
Ptr := L;
WHILE (Ptr<>FINLISTA) AND (Avanzados < Pos-2) DO
Ptr := Ptr^.Sig;
INC(Avanzados)
END;

IF Ptr = FINLISTA THEN


WrStr("Error: InsertarEnPosicion fuera de rango");
HALT
ELSE
NEW(PtrNuevo);
PtrNuevo^.Elem := Elem;
PtrNuevo^.Sig := Ptr^.Sig;
Ptr^.Sig := PtrNuevo
END
END
END InsertarEnPosicion;

Veamos el algoritmo en funcionamiento si queremos insertar un elemento de valor 7 en la


posición cuarta de la siguiente lista:
L
1 2 5 9

1) Avanzar dos nodos (4-2 = 2):

Facultad de Ciencias (Matemáticas) Informática


14 12.5 Listas enlazadas

L
1 2 5 9

Ptr

L
1 2 5 9

Ptr

L
1 2 5 9

Ptr

2) Crear y enlazar el nuevo nodo:


L
1 2 5 9

Ptr 7

PtrNuevo

Obsérvese el orden seguido al enlazar el nuevo nodo: primero se enlaza el nuevo nodo con el
siguiente nodo al que apunta Ptr y por último se enlaza el nodo apuntado por Ptr con el nuevo.
Este orden es importante, ya que si lo hiciésemos en orden inverso perderíamos la referencia al
último nodo.
El algoritmo InsertarEnPosicion escrito de modo recursivo es:
PROCEDURE InsertarEnPosicion (VAR L : LISTA; Elem : ELEMENTO;
Pos : CARDINAL);
PROCEDURE InsertarAux (VAR L : LISTA; Elem : ELEMENTO;
Pos : CARDINAL);
VAR PtrNuevo : PNODO;
BEGIN
IF Pos = 1 THEN
NEW(PtrNuevo); (* Se puede usar InsertarInicio(L,Elem) *)
PtrNuevo^.Elem := Elem;
PtrNuevo^.Sig := L;
L := PtrNuevo
ELSIF L = LISTAVACIA THEN
WrStr("Error: InsertarEnPosicion fuera de rango");
HALT
ELSE
InsertarAux(L^.Sig, Elem, Pos-1)
END
END InsertarAux;
BEGIN
IF Pos = 0 THEN
WrStr("Error: InsertarEnPosicion fuera de rango");
HALT
ELSE
InsertarAux (L, Elem, Pos)
END
END InsertarEnPosicion;

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 15

Describimos ahora un algoritmo para eliminar la primera ocurrencia de un elemento dado de


una lista enlazada:
PROCEDURE Eliminar (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrBorrar, PtrPrev : PNODO;
BEGIN
PtrBorrar := L;
PtrPrev := NIL;
WHILE (PtrBorrar<>FINLISTA) AND (PtrBorrar^.Elem<>Elem) DO
PtrPrev := PtrBorrar;
PtrBorrar := PtrBorrar^.Sig
END;
IF PtrBorrar = FINLISTA THEN
WrStr("Error: Eliminar, elemento no encontrado");
HALT
ELSE
IF PtrPrev = NIL THEN
L := L^.Sig
ELSE
PtrPrev^.Sig := PtrBorrar^.Sig;
END;
DISPOSE(PtrBorrar)
END
END Eliminar;

Para ello localizaremos con PtrBorrar el primer nodo cuyo elemento coincida con el valor que
se quiere eliminar. Como Modula-2 utiliza evaluación en cortocircuito de expresiones
booleanas, si PtrBorrar alcanza en algún momento el final de la lista (porque el elemento
buscado no se encuentre en ella) la segunda parte de la expresión AND no llega a evaluarse, y
gracias a esto, en ese caso no se llega a derreferenciar PtrBorrar (lo cual sería un error ya que
el valor de esta variable en ese punto sería NIL).
Se utiliza la técnica del puntero retrasado. PtrPrev siempre apuntará al nodo previo a
PtrBorrar o será NIL si PtrBorrar está situado sobre el primer nodo de la lista. Es necesario
comprobar en todo momento que no hemos alcanzado el final de la lista. Tras el bucle, si hemos
localizado el nodo a eliminar (PtrBorrar <> FINLISTA) contemplamos dos casos:
1) El nodo a eliminar es el primero de la lista. En este caso PtrPrev valdrá NIL. Para
eliminar el nodo avanzamos L y como PtrBorrar apunta al primer nodo llamamos a
DISPOSE(PtrBorrar)para liberar la memoria que ocupaba.
2) El nodo a eliminar no es el primero de la lista. Enlazamos el nodo previo al eliminado
con el siguiente al eliminado. De nuevo PtrBorrar apunta al nodo a eliminar y
devolvemos su memoria.
Veamos como funciona el algoritmo si queremos eliminar la primera ocurrencia del valor 4 de
la siguiente lista:
L
1 2 4 4

1) Localizar el nodo a borrar


L
1 2 4 4

PtrPrev PtrBorrar

Facultad de Ciencias (Matemáticas) Informática


16 12.5 Listas enlazadas

L
1 2 4 4

PtrPrev PtrBorrar

L
1 2 4 4

PtrPrev PtrBorrar

2) Desenlazarlo de la lista
L
1 2 4 4

PtrPrev PtrBorrar

3) Liberar memoria
L
1 2 4 4

PtrPrev PtrBorrar

El alumno debería verificar que el algoritmo descrito también funciona si el nodo que se
elimina es el primero de la lista o el último.
De modo recursivo tenemos:
PROCEDURE Eliminar (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrBorrar : PNODO;
BEGIN
IF L = LISTAVACIA THEN
WrStr("Error: Eliminar, elemento no encontrado");
HALT
ELSIF L^.Elem = Elem THEN
PtrBorrar := L;
L := L^.Sig;
DISPOSE(PtrBorrar);
ELSE
Eliminar (L^.Sig, Elem)
END
END Eliminar;

El siguiente procedimiento elimina el nodo que ocupa la posición indicada de una lista. El
algoritmo es muy similar al anterior salvo que en vez de avanzar PtrBorrar hasta encontrar el
elemento se avanza Pos-1 veces:
PROCEDURE EliminarEnPosicion (VAR L : LISTA; Pos : CARDINAL);
VAR PtrBorrar, PtrPrev : PNODO;
Avanzados : CARDINAL;
BEGIN
IF Pos = 0 THEN
WrStr("Error: EliminarEnPosicion fuera de rango");
HALT

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 17

ELSE
PtrBorrar := L;
PtrPrev := NIL;
Avanzados := 0;
WHILE (Avanzados<Pos-1) AND (PtrBorrar<>FINLISTA) DO
PtrPrev := PtrBorrar;
PtrBorrar := PtrBorrar^.Sig;
INC(Avanzados)
END;

IF PtrBorrar = FINLISTA THEN


WrStr("Error: EliminarEnPosicion fuera de rango");
HALT
ELSE
IF PtrPrev = NIL THEN
L := L^.Sig
ELSE
PtrPrev^.Sig := PtrBorrar^.Sig;
END;
DISPOSE(PtrBorrar)
END
END
END EliminarEnPosicion;

El algoritmo recursivo es:


PROCEDURE EliminarEnPosicion (VAR L : LISTA; Pos : CARDINAL);
PROCEDURE EliminarAux(VAR L : LISTA; Pos : CARDINAL);
VAR PtrBorrar : PNODO;
BEGIN
IF L = LISTAVACIA THEN
WrStr("Error: EliminarEnPosicion fuera de rango");
HALT
ELSIF Pos = 1 THEN
PtrBorrar := L;
L := L^.Sig;
DISPOSE(PtrBorrar);
ELSE
EliminarAux(L^.Sig, Pos-1)
END
END EliminarAux;
BEGIN
IF Pos = 0 THEN
WrStr("Error: EliminarEnPosicion fuera de rango");
HALT
ELSE
EliminarAux(L, Pos)
END
END EliminarEnPosicion;

Para devolver el valor del elemento que se encuentra en una posición dada dentro de la lista,
localizamos el nodo correspondiente avanzando Ptr Pos-1 posiciones en la lista y devolvemos
el valor almacenado en el nodo apuntado por Ptr. Es importante comprobar si hemos alcanzado
el final de la lista:
PROCEDURE ElementoEnPosicion (L:LISTA; Pos:CARDINAL) : ELEMENTO;
VAR Ptr : PNODO;
Avanzados : CARDINAL;
Resultado : ELEMENTO;
BEGIN
IF Pos = 0 THEN
WrStr("Error: ElementoEnPosicion fuera de rango");
HALT
ELSE

Facultad de Ciencias (Matemáticas) Informática


18 12.5 Listas enlazadas

Ptr
Avanzados :=
:= L;
0;
WHILE (Ptr <> FINLISTA) AND (Avanzados < Pos-1) DO
Ptr := Ptr^.Sig;
INC (Avanzados)
END;

IF Ptr = FINLISTA THEN


WrStr("Error: ElementoEnPosicion fuera de rango");
HALT
ELSE
Resultado := Ptr^.Elem
END;

RETURN Resultado
END
END ElementoEnPosicion;

Recursivamente:
PROCEDURE ElementoEnPosicion (L:LISTA; Pos:CARDINAL) : ELEMENTO;
PROCEDURE ElementoAux(VAR L:LISTA; Pos:CARDINAL) : ELEMENTO;
BEGIN
IF L = LISTAVACIA THEN
WrStr("Error: ElementoEnPosicion fuera de rango");
HALT
ELSIF Pos = 1 THEN
RETURN L^.Elem
ELSE
RETURN ElementoAux(L^.Sig, Pos-1)
END
END ElementoAux;
BEGIN
IF Pos = 0 THEN
WrStr("Error: ElementoEnPosicion fuera de rango");
HALT
ELSE
RETURN ElementoAux(L, Pos)
END
END ElementoEnPosicion;

Para calcular la longitud de una lista recorremos todos sus nodos con la variable Ptr hasta
encontrar el fin de la lista. Utilizamos un contador que inicializamos a cero y que
incrementamos cada vez que avanzamos un nodo:
PROCEDURE Longitud (L : LISTA) : CARDINAL;
VAR Ptr : PNODO;
LaLongitud : CARDINAL;
BEGIN
LaLongitud := 0;
Ptr := L;
WHILE Ptr <> FINLISTA DO
Ptr := Ptr^.Sig;
INC(LaLongitud)
END;
RETURN LaLongitud
END Longitud;

De modo recursivo, basta observar que la longitud de la lista vacía es cero y en otro caso
devolvemos uno más la longitud de la lista que empieza en el nodo siguiente:
PROCEDURE Longitud (L : LISTA) : CARDINAL;
BEGIN
IF L = LISTAVACIA THEN

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 19

RETURN 0
ELSE
RETURN 1 + Longitud(L^.Sig)
END
END Longitud;

Escribiremos una lista entre corchetes y separando los elementos por comas. Es necesario
comprobar para cada elemento si el último de la lista, ya que en dicho caso no se escribe la
coma correspondiente:
PROCEDURE WrLista (L : LISTA);
VAR Ptr : PNODO;
BEGIN
WrChar('[');
Ptr := L;
WHILE Ptr <> FINLISTA DO
WrInt(Ptr^.Elem, 0);
IF Ptr^.Sig <> FINLISTA THEN
WrChar(',')
END;
Ptr := Ptr^.Sig
END;
WrChar(']')
END WrLista;

La solución recursiva también es fácil, aunque necesitamos un subprograma auxiliar:


PROCEDURE WrLista (L : LISTA);
PROCEDURE WrAux(L : LISTA);
BEGIN
IF L <> LISTAVACIA THEN
WrInt(L^.Elem, 0);
IF L^.Sig <> FINLISTA THEN
WrChar(',')
END;
WrAux(L^.Sig)
END
END WrAux;
BEGIN
WrChar('[');
WrAux(L);
WrChar(']')
END WrLista;

Por último necesitamos una operación que deje la lista vacía liberando toda la memoria
ocupada por los nodos. En este caso necesitamos recorrer la lista utilizando la técnica del
puntero retrasado. Los elementos apuntados por el puntero retrasado son los que se destruyen:
PROCEDURE Destruir (VAR L : LISTA);
VAR PtrPrev, Ptr : PNODO;
BEGIN
Ptr := L;
PtrPrev := NIL;
WHILE Ptr <> FINLISTA DO
PtrPrev := Ptr;
Ptr := Ptr^.Sig;
DISPOSE(PtrPrev)
END;
L := LISTAVACIA
END Destruir;

Gráficamente, si destruimos la lista

Facultad de Ciencias (Matemáticas) Informática


20 12.5 Listas enlazadas

L
1 2 4

los pasos seguidos son

1 2 4

PtrPrev Ptr

2 4

PtrPrev Ptr

4
L

PtrPrev Ptr

Nótese que L queda a NIL finalmente (lo cual es adecuado, ya que la lista está vacía).
El algoritmo recursivo correspondiente es más breve:
PROCEDURE Destruir (VAR L : LISTA);
BEGIN
IF L <> LISTAVACIA THEN
Destruir(L^.Sig);
DISPOSE(L)
END
END Destruir;

Los pasos seguidos por el algoritmo recursivo son los siguientes:


L
1 2 4

L L(1
1 2 4

L L(2
1 2 4

L L(3
1 2 4

L L(2
1 2 4

L L(1
1 2

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 21

L
1

En cada momento, el número en el superíndice de L indica el anidamiento de la llamada


recursiva que se está ejecutando.
Un programa que usa la biblioteca definida para leer una cantidad de números no conocida a
priori y mostrarlos es:
MODULE Demo;
FROM Lista IMPORT Crear, InsertarFinal, WrLista, Destruir,
LISTA, ELEMENTO;
FROM IO IMPORT RdInt, RdCard, RdLn, WrStr;

VAR
Lista : LISTA;
Numero : ELEMENTO;
Total, i : CARDINAL;

BEGIN
WrStr(“Dame el total de números a introducir: “);
Total := RdCard(); RdLn;

Lista := Crear();

FOR i := 1 TO Total DO
WrStr(“Dame un número: “);
Numero := RdInt(); RdLn;
InsertarFinal(Lista, Numero)
END;

WrStr(“Los números introducidos fueron: “);


WrLista(Lista);

Destruir(Lista)

END Demo.

Es importante liberar la memoria que ocupa la lista (en este caso mediante la operación
Destruir) cuando ésta ya no es necesaria.

12.5.1 Listas ordenadas


Una lista ordenada (en orden ascendente, aunque se pueden definir de modo similar en orden
descendente) es una lista en la cual el valor de un nodo es siempre menor o igual al nodo que le
sigue.
Las operaciones de búsqueda de elementos dentro de la lista pueden ser implementadas de un
modo más eficiente que si se usan listas no ordenadas. A la hora de localizar un elemento no
hay que recorrer toda la lista para asegurarnos que no existe. Basta con llegar a un nodo cuyo
valor sea mayor al buscado. Esta mejora puede ser bastante considerable, especialmente si la
lista tratada tiene una longitud grande.
Por otro lado, una lista ordenada también se usa si se desea obtener un listado ordenado de los
datos almacenados.

Facultad de Ciencias (Matemáticas) Informática


22 12.5 Listas enlazadas

En el caso de las listas ordenadas, a la hora de insertar un elemento, no se especifica la posición


que éste debe ocupar. Solo existe una operación InsertarOrdenada que toma un nuevo dato y
lo coloca en su posición adecuada dentro de la lista, de modo que la lista resultante sigue
estando ordenada. Se mantiene la operación para eliminar por posición dentro de la lista y la de
eliminación de la primera ocurrencia de un dato dado.
El siguiente módulo de definición corresponde a una biblioteca de listas ordenadas. De las
operaciones que aparecen en el módulo, solo InsertarOrdenado y Eliminar son distintas a
las presentadas en el apartado anterior.
DEFINITION MODULE ListaOrd;
TYPE
ELEMENTO = INTEGER;
PNODO = POINTER TO NODO;
NODO = RECORD
Elem : ELEMENTO;
Sig : PNODO
END;
LISTA = PNODO;

PROCEDURE Crear () : LISTA;


PROCEDURE InsertarOrdenada (VAR L : LISTA; Elem : ELEMENTO);
PROCEDURE Eliminar (VAR L : LISTA; Elem : ELEMENTO);
PROCEDURE EliminarEnPosicion (VAR L : LISTA; Pos : CARDINAL);
PROCEDURE ElementoEnPosicion (L : LISTA; Pos :CARDINAL) : ELEMENTO;
PROCEDURE Longitud (L : LISTA) : CARDINAL;
PROCEDURE WrLista (L : LISTA);
PROCEDURE Destruir (VAR L : LISTA);

END ListaOrd.

Para insertar un nuevo dato hemos de buscar su posición adecuada dentro de la lista. Para ello
buscamos con PtrSig el primer nodo cuyo contenido sea mayor o igual al que estamos
insertando. Habrá que insertar el nuevo nodo justo delante del nodo al cual apunte PtrSig.
Para ello utilizamos la técnica del puntero retrasado, de modo que cuando PtrSig encuentra el
nodo siguiente, PtrPrev apunta al nodo anterior y solo hay que enlazar el nuevo nodo entre
estos. Previamente es necesario comprobar si PtrSig no llegó a avanzar ni un solo nodo (en ese
caso PtrPrev = NIL) ya que en ese caso el nuevo nodo iría colocado al principio de la lista:
PROCEDURE InsertarOrdenada (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrNuevo, PtrSig, PtrPrev : PNODO;
BEGIN
NEW(PtrNuevo);
PtrNuevo^.Elem := Elem;

PtrSig := L;
PtrPrev := NIL;
WHILE (PtrSig<>FINLISTA) AND (PtrSig^.Elem<Elem) DO
PtrPrev := PtrSig;
PtrSig := PtrSig^.Sig
END;

PtrNuevo^.Sig := PtrSig;
IF PtrPrev = NIL THEN
L := PtrNuevo
ELSE
PtrPrev^.Sig := PtrNuevo;
END
END InsertarOrdenada;

El alumno debería comprobar que el algoritmo también funciona si el nuevo nodo va a parar
justo al final de la lista (o sea, es mayor que cualquiera de los nodos en la lista).

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Tema 12. Estructuras de datos dinámicas 23

La función que elimina un dato es muy similar a la de las listas enlazadas no ordenadas, pero en
este caso basta con alcanzar un nodo con valor mayor al buscado para concluir que el nodo no
está en la lista:
PROCEDURE Eliminar (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrBorrar, PtrPrev : PNODO;
BEGIN
PtrBorrar := L;
PtrPrev := NIL;
WHILE (PtrBorrar<>FINLISTA) AND (PtrBorrar^.Elem<Elem) DO
PtrPrev := PtrBorrar;
PtrBorrar := PtrBorrar^.Sig
END;

IF (PtrBorrar=FINLISTA) OR (PtrBorrar^.Elem<>Elem) THEN


WrStr("Error: Eliminar elemento no encontrado");
HALT
ELSE
IF PtrPrev = NIL THEN
L := L^.Sig
ELSE
PtrPrev^.Sig := PtrBor
rar^.Sig;
END;
DISPOSE(PtrBorrar)
END
END Eliminar;

De modo recursivo:
PROCEDURE InsertarOrdenada (VAR L : LISTA; Elem : ELEMENTO);
VAR PtrNuevo : PNODO;
BEGIN
IF (L = LISTAVACIA) OR (Elem <= L^.Elem) THEN
(* Crear el nodo nuevo *)
NEW(PtrNuevo);
PtrNuevo^.Elem := Elem;

(* Enlazarlo *)
PtrNuevo^.Sig := L;
L := PtrNuevo
ELSE
InsertarOrdenada(L^.Sig, Elem)
END
END InsertarOrdenada;

(* Elimina el primer elemento en la lista que coincide con Elem *)

PROCEDURE Eliminar (VAR L : LISTA; Elem : ELEMENTO);


VAR PtrBorrar : PNODO;
BEGIN
IF (L = LISTAVACIA) OR (L^.Elem > Elem) THEN
WrStr("Error: Eliminar, elemento no encontrado");
HALT
ELSIF L^.Elem = Elem THEN
(* Enlazar con el siguiente y liberar memoria *)
PtrBorrar := L;
L := L^.Sig;
DISPOSE(PtrBorrar);
ELSE
Eliminar (L^.Sig, Elem)
END
END Eliminar;

Facultad de Ciencias (Matemáticas) Informática


24 12.5 Listas enlazadas

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 12) 25

Relación de Problemas (Tema 12)


1. Responder verdadero o falso teniendo en cuenta las siguientes declaraciones:
TYPE
PUNTERO = POINTER TO NODO;
NODO = RECORD
Info : INTEGER;
Siguiente : PUNTERO
END;
VAR
Ptr : PUNTERO;
UnNodo : NODO;

a) El espacio que ocupa la variable Ptr se asigna dinámicamente en tiempo de


ejecución
b) El espacio que ocupa Ptr^ se asigna dinámicamente en tiempo de ejecución.
c) Ptr^ está inicialmente indefinido.
d) Después de la sentencia NEW(Ptr), Ptr^.Siguiente vale NIL.
e) Ptr ocupa la misma cantidad de espacio en memoria que un registro del tipo NODO.
f) La declaración de UnNodo es sintácticamente incorrecta porque los registros tipo
NODO sólo pueden ser asignados dinámicamente.
2. Dadas las declaraciones
TYPE
CADENA = ARRAY[1..20] OF CHAR;
ELEMENTO = RECORD
Nombre : CADENA;
ID : INTEGER
END;
PUNTERO = POINTER TO NODO;
NODO = RECORD
Datos : ELEMENTO;
Anterior,Siguiente : PUNTERO
END;
VAR
Lista, Ptr : PUNTERO;

identificar el tipo de cada una de las siguientes expresiones como un puntero, un registro,
un carácter, una cadena de caracteres o un entero.
a) Lista^.Siguiente
b) Lista^.Siguiente^
c)Lista^.Datos.Nombre
d) Ptr^.Anterior^.Datos.ID
e) Ptr^.Anterior
f) Ptr^.Datos.Nombre[1]
3. Dadas dos listas enlazadas ordenadas escribir un procedimiento o función que genere una
nueva lista mezcla ordenada de ambas. Las dos listas originales deben quedar intactas (los
nodos que constituyen la lista mezcla deben crearse). Escribir una versión iterativa y otra
recursiva del subprograma.

Facultad de Ciencias (Matemáticas) Informática


26 Relación de Problemas (Tema 12)

4. Diseña una biblioteca (módulo de definición e implementación) donde se defina un nuevo


tipo CONJUNTO. Las variables de este tipo representarán conjuntos de números reales. Para
ello cada conjunto debe representarse como una lista enlazada. Incorpora al módulo las
operaciones normales predefinidas sobre conjuntos (Crear, Incluir, Excluir,
Pertenece, EsVacio, Cardinal, Union, Interseccion y Diferencia).
NOTA: En la lista que represente un conjunto no deben aparecer elementos repetidos.
5. Dadas dos listas enlazadas que almacenan números enteros escribir un programa que
genere todas las combinaciones posibles de parejas de números, uno de cada lista, cuya
suma es menor o igual que un número N introducido por teclado.

6. Un polinomio puede ser representado por una lista enlazada en la que cada nodo contiene
un coeficiente (de tipo REAL), el valor del exponente (un valor CARDINAL) y un puntero
hacia el siguiente elemento. Escribir un módulo de biblioteca en el que se defina el tipo
correspondiente y subprogramas que permitan:
a) Crear polinomios.
b) Añadir términos al polinomio.
c) Leer polinomios de teclado.
d) Escribir polinomios en pantalla.
e) Sumar dos polinomios.
f) Restar dos polinomios.
g) Calcular el producto de dos polinomios.
h) Calcular la derivada de un polinomio dado.
i) Evaluar el polinomio para un valor de la variable independiente también de tipo
REAL
NOTA: Las listas enlazadas que representan los polinomios estarán ordenadas en forma
ascendente por el grado de los nodos. Por ejemplo, el polinomio x4 + 7.5x + 2 estaría
representado por la lista:
P
2.0 7.5 1.0
0 1 4

Las operaciones de suma, resta, producto y derivada deben generar listas nuevas (los
nodos que las forman deben crearse)
7. Las listas doblemente enlazadas son listas en las cuales cada nodo consta de un campo
para almacenar un dato y dos campos de enlace o punteros que apuntan respectivamente al
nodo anterior y posterior al actual. Estas listas pueden recorrerse en dos sentidos: hacia
delante y hacia atrás. Las listas se representan mediante un registro de dos punteros
(Inicio y Fin) que apunten hacia el primer y el último nodo respectivamente.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 12) 27

L
Fin 8 15 4
Inicio

Escribir un módulo de biblioteca para implementar esta estructura y operaciones para su


manejo.
8. Una pila es un tipo especial de lista en la cual la inserción y eliminación de elementos se
realiza sólo por un extremo que se denomina cima o tope. Los elementos sólo pueden
eliminarse en orden inverso al que se insertan. El último elemento que se insertó en la pila
es el primero que se puede sacar. A este tipo de estructuras se las llama LIFO (siglas de la
expresión inglesa Last-In, First-Out).
Las operaciones asociadas a una pila son
1) Crear: devuelve una pila vacía.
2) Insertar: añade un elemento a la pila
3) Sacar: extrae el último elemento insertado en la pila. Es un error extraer de una
pila vacía.
4) Cima: devuelve el valor del último elemento insertado pero no lo extrae. Es un
error obtener la cima de una pila vacía.
5) EsVacia: devuelve TRUE si la pila está vacía (no contiene ningún elemento).
Un ejemplo de uso de una pila de enteros es el siguiente:
P := Crear(); Insertar(P,1); Insertar(P,2); Extraer(P);

Cima 2
1 1 1
Cima Cima

Escribir un módulo de biblioteca donde se defina el tipo pila de valores enteros y las
operaciones asociadas.
NOTA: Una pila se representará mediante una lista enlazada. La operación de inserción
en la pila consiste en insertar al inicio de la lista, mientras que la de extracción en
eliminar el primer elemento de la lista.
9. Escribe un subprograma que tome como argumento una lista simplemente enlazada y
escriba su contenido por pantalla en orden inverso.
10. Escribe un subprograma Filtro que tome como argumento una lista de valores enteros y
una función que toma un entero y devuelve un valor booleano. Esta función debe devolver
una nueva lista consistente en los argumentos de la lista original para los que la función se
evalúa a TRUE. Los nodos de la lista resultante deben crearse. Escribe un programa que,
utilizando esta función, lea una serie de valores enteros, los almacene en una lista, calcule
una nueva lista donde queden solo los pares y por último escriba esta lista por pantalla.
11. Dadas las definiciones de datos vistas en el tema para una lista enlazada de valores
enteros, escribe procedimientos o funciones para:
1) Duplicar una lista enlazada (crear otra lista enlazada con los mismos elementos

Facultad de Ciencias (Matemáticas) Informática


28 Relación de Problemas (Tema 12)

que el argumento).
2) Borrar el nodo que contiene el máximo valor de una lista enlazada.
3) Intercambiar el valor n-ésimo con el m-ésimo de la lista.
4) Dada la siguiente cabecera:
PROCEDURE Busca (L:LISTA; e:ELEMENTO): PNODO;
devuelva el puntero al primer nodo de L cuyo valor es e o NIL si no existe ningún
nodo con dicho valor.
5) Dada la siguiente cabecera:
PROCEDURE InsBusca(VAR L:LISTA; e:ELEMENTO):PNODO;
añada e a la lista L, si no está en ella, y siempre devuelva un puntero al nodo que
contiene a e
12. Una lista enlazada circular es una lista enlazada donde el puntero del último nodo no es
NIL, sino que apunta al primer nodo:

L L L
1 1 2

Listas circulares de cero, uno y dos elementos.

Escribe un módulo de biblioteca donde definas el tipo para listas circulares y operaciones
para insertar al principio, insertar al final y escribir una lista por pantalla, entre otras.

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 12) 29

Relación Complementaria (Tema 12)


13. Una cola es un tipo especial de lista en la cual las eliminaciones de datos se realizan al
principio de la lista (frente) y las inserciones se realizan en el otro extremo (final) (es
como la cola del autobús). Las colas se utilizan para almacenar datos que deben ser
procesados en el orden de llegada. El primer elemento que entra es el primero que sale. A
este tipo de estructuras se las llama FIFO (First-In, First-Out).
Las operaciones asociadas a una cola son
1) Crear: devuelve una cola vacía.
2) Insertar: añade un elemento al final de la cola
3) Sacar: extrae el primer elemento en la cola. Es un error extraer de una cola vacía.
4) Primero: devuelve el valor del primer elemento en la cola pero no lo extrae. Es un
error obtener el primer elemento de una cola vacía.
5) EsVacia: devuelve TRUE si la cola está vacía (no contiene ningún elemento).
Un ejemplo de uso de una cola de enteros es el siguiente:
C := Crear(); Insertar(C,1); Insertar(C,2); Extraer(C);

1 2 1 2

Primero Primero Primero

Escribir un módulo de biblioteca donde se defina el tipo cola de valores enteros y las
operaciones asociadas.
14. Escribe un subprograma que tome como argumento dos listas simplemente enlazadas y
construya una nueva lista que contiene tanto los nodos de la primera como los de la
segunda. Los nodos de la lista resultante deben crearse.
15. Escribe un subprograma Map que tome como argumento una lista de valores enteros y una
función que toma un entero y devuelve otro entero. Esta función debe devolver una nueva
lista cuyos valores son los obtenidos al aplicar la función argumento a cada uno de los
valores en la lista original. Escribe un programa que usando esta función lea una serie de
valores en una lista, calcule una nueva lista con sus cuadrados y la escriba por pantalla.
16. Diseña un procedimiento Purga que elimine los elementos duplicados de una lista
enlazada que tome como parámetro.
17. Escribe procedimientos recursivos e iterativos que a partir de una lista enlazada de enteros
1) Calcule el mayor elemento de la lista.
2) Calcule la suma de los elementos de la lista.
3) Calcule la longitud de la lista.
18. Una implementación más eficiente de una cola se obtiene usando una lista enlazada
circular. Si el puntero exterior del anillo apunta siempre al último elemento de la cola,
podemos siempre acceder al primero en un solo paso. Implementa una cola mediante una
lista enlazada circular utilizando esta idea.
19. Un modo de almacenar un diccionario de palabras es mediante una tabla hash. Esta

Facultad de Ciencias (Matemáticas) Informática


30 Relación de Problemas (Tema 12)

estructura consiste en un array cuyo índice es [’a’..’z’] y donde cada posición del
array es un puntero a una lista enlazada. Cada nodo de las listas enlazadas poseerá el
nombre de una palabra y su definición (cadenas de caracteres)

‘a’ ala extremidad de un ave atar ...


‘b’
.
.

‘z’ zapato ...

A la hora de almacenar una palabra se crea un nuevo nodo con el nombre y la definición y
se enlaza en la lista correspondiente a la primera letra de la palabra. La estructura es
bastante eficiente, ya que para localizar una palabra basta con mirar en la lista
correspondiente a su primera letra.
Escribe un módulo de biblioteca donde se defina el tipo TABLAHASH y operaciones para
• Insertar una palabra y su correspondiente definición en una tabla.
• Dada una palabra, buscar su definición en una tabla.
• Dada una palabra, eliminarla de una tabla.
• Mostrar todas las palabras y las definiciones asociadas en una tabla.
20. Una matriz dispersa es una matriz donde muchos de sus elementos tienen valor cero. En
predicción meteorológica, por ejemplo, es necesario manejar matrices de este tipo con
grandes dimensiones. Si utilizamos un array bidimensional estamos desperdiciando una
gran cantidad de memoria. Una solución a este problema es utilizar listas enlazadas. Una
matriz se representa por un registro de tres campos. El primero es el número total de filas
de la matriz, el segundo el de columnas y el tercero una lista enlazada vertical con tantos
elementos como filas tenga la matriz. Cada nodo de la lista vertical es un puntero a una
lista enlazada con los nodos no nulos de la fila. Estos nodos tienen dos campos además
del puntero: la columna a la cual corresponde el dato y el dato. Las listas horizontales
están ordenadas de menor a mayor según el contenido del primer campo (la columna a la
cual corresponde el dato). Por ejemplo la siguiente matriz

0 6 0 3 0
1 0 0 0 0
 
0 0 0 0 0
 
0 8 0 0 10
es representada como:

José E. Gallardo Ruiz Dpto. Lenguajes y Ciencias de la Computación - UMA


Carmen M. García López
Relación de Problemas (Tema 12) 31

Filas 4
Cols 5

2 6 4 3

1 1

2 8 5 10

Escribe un módulo de biblioteca donde se defina el tipo MATRIZ y las siguientes


operaciones:
• Crear: toma como argumentos el número total de filas y columnas de una matriz y
devuelve una matriz vacía.
• Asignar: toma como argumentos una matriz, una fila, una columna y un valor.
Asigna este valor a la correspondiente posición de la matriz. Para ello hay que
buscar si existe un nodo en la matriz correspondiente a la posición. Si es así, se
asigna el nuevo valor. En otro caso se crea un nuevo nodo con los valores
adecuados y se enlaza en su posición correspondiente.
• Elemento: toma una matriz, una fila y una columna y devuelve el valor del
elemento que ocupa dicha posición en la matriz. Si no existe el correspondiente
nodo en la matriz, se devolverá cero.
• WrMatriz: escribe una matriz que toma como argumento por pantalla.
• RdMatriz: permite crear una matriz y leer sus componentes desde teclado.
Como aplicación de la biblioteca, escribe un programa que lea dos matrices del teclado,
calcule su producto y lo escriba por pantalla.

Facultad de Ciencias (Matemáticas) Informática

You might also like