3 r a . E D I C I Ó N
J U N I O D E 2 0 0 2
Incluye Manejo de
Punteros y Memoria Lic. Gustavo López
Dinámica,
y Unidades de
Augusto Vega
Biblioteca. Matías Gavinowich
Enrique Calot
Pág.
Capítulo V: Registros 67
Introducción a Pascal
INTRODUCCIÓN A PASCAL
Las versiones más populares y extendidas en el mundo del lenguaje Pascal son dos: Pascal
estándar, que recoge la versión de Wirth normalizada por las organizaciones ANSI e ISO ,
y Turbo Pascal, producto desarrollado y totalmente depurado de la casa BORLAND, que
sin lugar a dudas, es la versión más utilizada tanto en el campo de la enseñanza como en el
mundo profesional y de investigación.
La principal razón para que las personas aprendan lenguajes de programación es utilizar la
computadora como una herramienta para la resolución de problemas. Dos fases pueden ser
identificadas en el proceso de resolución de problemas ayudados por computadora.
Las dos primeras fases conducen a un diseño detallado escrito en forma de algoritmo.
Durante la tercera etapa (codificación) se implementa1 el algoritmo en un código escrito en
un lenguaje de programación, reflejando las ideas desarrolladas en las fases de análisis y
diseño.
La fase de compilación traduce el código fuente a código máquina mediante el empleo de
intérpretes o compiladores y en la fase de ejecución se corre el programa sobre la
computadora. En las fases de verificación y depuración el programador busca errores de
las etapas anteriores y los elimina. Podrá comprobar que mientras más tiempo gaste en la
fase de análisis y diseño menos tiempo invertirá en la fase de verificación y depuración.
Por último, se debe realizar la importante fase de documentación del programa, con objeto
de que cualquier persona ajena al mismo pueda entender qué hace y cómo lo hace.
Antes de conocer las tareas a realizar en cada fase, vamos a considerar el concepto y
significado de la palabra algoritmo.
Un algoritmo es un método para resolver un problema mediante la combinación de una
serie de pasos precisos, definidos y finitos. Un algoritmo es preciso en el sentido que los
pasos que lo componen deben realizarse en un determinado orden y no en otro. Que sea
definido implica que si se ejecuta varias veces el mismo algoritmo sobre el mismo conjunto
de datos de entrada, siempre se obtienen los mismos datos de salida, es decir, el algoritmo
es unívoco. Finalmente, que sea finito implica que debe finalizar después de un número
finito de pasos.
Un algoritmo debe producir un resultado en un tiempo finito. Los métodos que utilizan
algoritmos se denominan métodos algorítmicos, en oposición a los métodos que implican
algún juicio o interpretación que se denominan métodos heurísticos. Los métodos
1
El Diccionario de la Real Academia Española toma entre los nuevos términos aceptados: implementar.
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 5
algorítmicos se pueden implementar en computadoras; sin embargo, los procesos
heurísticos no han sido convertidos fácilmente en las computadoras. En los últimos años
las técnicas de inteligencia artificial han hecho posible la implementación del proceso
heurístico en computadoras.
Entre las herramientas de que dispone el programador para una representación gráfica de un
algoritmo destacan los ordinogramas, diagramas de flujo de Nassi-Schneiderman y
últimamente se utiliza con más frecuencia la herramienta del pseudocódigo. Esta última
representación es la más utilizada en lenguajes de programación estructurados como Turbo
Pascal.
Dado que se busca una solución se debe examinar cuidadosamente el problema a fin de
identificar qué tipo de información se necesita producir. A continuación el programador
debe identificar aquellos elementos de información dados en el problema que puedan ser
útiles para obtener la solución. Finalmente, un procedimiento para producir los resultados
deseados a partir de los datos, y que será el algoritmo.
Esta fase es muy importante y no debe tomarse a la ligera. Si no conoce exactamente lo
que desea como salida del programa, puede producirle sorpresa lo que su programa realice,
por ejemplo si el programa es para el cálculo de un interés bancario, debe conocer no sólo
la tasa de interés y el capital, sino también el período de tiempo de depósito del capital.
Análisis del
problema
1. Programar un módulo
2. Comprobar el módulo
3. Si es necesario, depurar el módulo
4. Combinar el módulo con los módulos anteriores
El proceso que convierte los resultados del análisis del problema en un diseño modular con
refinamientos sucesivos que permitan una posterior traducción a un lenguaje se denomina
diseño del algoritmo.
Módulo principal
1. Entrada de datos
2. Cálculo del interés (proceso)
3. Salida de resultados
VERIFICACIÓN DE ALGORITMOS
Una vez escrito el algoritmo es necesario asegurarse de que éste realiza las tareas para las
que ha sido diseñado, y que, por lo tanto, produce el resultado correcto y esperado.
El modo más normal de comprobar un algoritmo es mediante su ejecución manual usando
datos significativos que abarquen todo el posible rango de valores y anotando en una hoja
de papel los valores que van tomando en las diferentes fases, los datos de entrada o
auxiliares y, por último, los valores de los resultados. Este proceso se conoce como prueba
del algoritmo o prueba de escritorio.
CODIFICACIÓN
DOCUMENTACIÓN
2
Un identificador es una combinación de caracteres alfabéticos y dígitos que se utiliza para poner nombre a
los distintos componentes del programa (variables, constantes, funciones, tipos de datos, etc.).
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 8
La importancia de una adecuada documentación interna se manifiesta todavía en mayor
grado cuando los programas son complejos y tienen varios cientos de líneas y es muy difícil
su seguimiento, incluso por el programador si ha pasado mucho tiempo desde que lo
codificó.
La documentación es vital cuando se desean corregir posibles errores o bien modificar el
programa. Tales cambios se denominan mantenimiento del programa. Después de cada
cambio la documentación debe ser actualizada para facilitar cambios posteriores. Es
práctica corriente numerar las sucesivas versiones del programa así como indicar el nombre
de los sucesivos programadores que han intervenido tanto en la concepción del programa
inicial como las distintas modificaciones posteriores.
Asimismo, es conveniente una adecuada presentación e indentación3 en las distintas líneas
del programa, así como líneas en blanco que separen los distintos módulos, de forma que
éste sea más legible. Hay que tener en cuenta que para el programa traductor el código
fuente es un archivo de texto, es decir, una sucesión de caracteres y que la traducción a
código ejecutable es independiente de su presentación y formato, pero no así para la
persona que tiene que leer el código o modificarlo.
Así, por ejemplo, el código:
Program Misterio; var r, v: real; begin Write (‘Introduzca dato: ‘); Readln (r);
v:= 4.1888 * r * r * r; Writeln (‘ Respuesta = ‘, v) end.
COMPILACIÓN Y EJECUCIÓN
3
con indentación nos referimos a la sangría o margen que dejamos al codificar el programa y que permite que
este sea más entendible.
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 9
es capaz de entender y ejecutar. El encargado de realizar esta función es un programa
traductor (compilador o intérprete). Si tras la compilación se presentan errores de
compilación es preciso modificar la codificación del programa de forma que ésta se adapte
a las reglas de sintaxis del lenguaje elegido, con el fin de corregir los errores. Este proceso
continúa hasta que ya no se producen errores, con lo que el compilador proporciona el
programa objeto, aún no ejecutable directamente. A continuación se procede al montaje o
enlace (link) y producir finalmente el programa ejecutable. Una vez obtenido el programa
ejecutable se pone en funcionamiento con sólo teclear su nombre (en el caso de DOS) o un
simple clickeo sobre su nombre (en el caso del explorador de Windows). Suponiendo que
no existen errores durante su ejecución (llamados errores en tiempo de ejecución4 ) se
obtendrá la salida de los resultados del programa.
Las instrucciones u órdenes para compilar y ejecutar un programa pueden variar según el
tipo de compilador. Así, por ejemplo, Turbo Pascal compila y ejecuta con una sola orden,
mientras que otros compiladores clásicos de Pascal siguen un proceso similar a lo expuesto
anteriormente: compilar, enlazar y ejecutar.
VERIFICACIÓN
TIPOS DE DATOS
Los diferentes objetos de información con los que un programa Pascal trabaja se conocen
en conjunto como datos. Todos las datos tienen un tipo asociado a ellos. Un dato puede ser
un simple caracter, tal como 's', un valor entero tal como 70 o un número real tal como
312,53.
Una operación de suma no tiene sentido con caracteres, sólo con números. Por
consiguiente, si el compilador detecta una operación de suma de dos caracteres,
4
Intento de división por cero, raíces cuadradas de números negativos, intentos de apertura de archivos
inexistentes, dispositivos periféricos no conectados, ...
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 10
normalmente producirá un error, incluso entre tipos numéricos diferentes. La operación de
suma se almacena de modo distinto en cada caso, ya que los números enteros y reales se
almacenan de modo diferentes. A menos que el programa conozca el tipo de datos, si es un
valor entero a real, no puede ejecutar correctamente la operación de suma.
La asignación de tipos a los datos tiene dos objetivos principales:
Los datos utilizados deben tener sus tipos declarados explícitamente ya que el lenguaje
limita la mezcla de tipos en las expresiones. Pascal detecta muchos errores de programación
antes que el programa se ejecute.
El tipo de un dato determina la naturaleza del conjunto de valores que puede tomar una
variable. Otro concepto importante a tener en cuenta es la representación interna de los
números, o al menos el espacio de memoria ocupado por una variable de un tipo dado.
La unidad de medida de la capacidad de memoria es el byte (octeto): un byte se compone
de ocho cifras binarias (bits) que pueden tomar cada una el valor 0 ó 1.
TIPOS DE ENTEROS
TIPO RANGO
Byte 0 .. 255
Integer -32768 .. 32767
Longint -247483648 .. 24748367
Shortint -128 .. 127
Word 0 .. 65535
El tipo char es un tipo de datos que puede contener un solo caracter y cada uno de estos
caracteres puede ser expresado gracias al código ASCII ampliado.
Ejemplo:
equivale a
#65 chr(65) se traduce en A
#26 o ^Z cierre del archivo
#27 tecla ESC
#13 tecla ENTER
El tipo lógico (boolean) es al igual que el tipo caracter, parte del ISO Pascal estándar. El
tipo lógico puede tomar sólo dos valores posibles: true (verdadero) y false (falso).
Al igual que el tipo char, el tipo boolean es un tipo ordinal, lo que significa que tiene un
número fijo de valores posibles que existen en un orden definido. Una variable lógica
ocupa sólo un byte en memoria. Los valores lógicos son de tipo ordinal, y sus relaciones
son:
Todos los tipos de datos estudiados hasta ahora son de tipo simple, predefinidos por Turbo
y listos para utilizar. Sin embargo, uno de los aspectos más potentes de Turbo Pascal es su
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 12
capacidad para crear estructuras de datos a partir de estos datos simples. Los datos
estructurados aumentan la legibilidad de los programas y simplifican su mantenimiento.
Un tipo string (cadena) es una secuencia de caracteres, pudiendo ser cero o más caracteres
correspondientes al código ASCII, escrito en una línea sobre el programa y encerrada entre
apóstrofos.
El tratamiento de cadenas es una característica muy potente de Turbo Pascal que no tiene el
ISO Pascal estándar, aunque tiene mucha similitud con su tipo packed array.
Ejemplos:
- Una cadena sin nada entre los apóstrofos se llama cadena nula o cadena vacía.
- La longitud de una cadena es el número de caracteres encerrados entre los apóstrofos.
CONSTANTES
Una constante es un valor que no puede cambiar durante la ejecución del programa, recibe
un valor en el momento de la compilación del programa y este valor no puede ser
modificado.
Las constantes pueden ser:
· constantes literales
· constantes con nombres o declaradas
· constantes de tipos (tipeadas)
Las constantes deben ser declaradas antes de su utilización y pueden ser enteras o reales,
caracteres o cadenas de caracteres, conjuntos o arrays, e inclusive de tipo enumerado.
const
identificador = valor;
Ejemplos:
const
Pi = 3.141592;
DosPi = 2 * Pi;
caracter = 'B';
cuenta = 32;
VARIABLES
Las variables son objetos cuyo valor puede cambiar durante la ejecución del programa. El
cambio se produce mediante sentencias ejecutables.
- Todas las variables de un programa Pascal deben ser declaradas antes de ser usadas.
Declaraciones:
var
variable1: tipo1;
variable2: tipo2;
.........................
.........................
variableN: tipoN;
Ejemplos:
NumeroEmpleado : Integer; { número de empleado }
Horas : real; { horas trabajadas }
Tasas : real; { tasa horaria }
Edad : Integer; { edad del empleado }
Apellidos : string [30]; { apellidos del empleado }
Letra1, Letra2, Letra3 : char;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 14
Num1, Num2 : integer;
SENTENCIAS
Las sentencias describen las acciones algorítmicas que deben ser ejecutadas En general las
sentencias se clasifican en: ejecutables, que son las que especifican operaciones de cálculos
aritméticos y entradas / salidas de datos, y en no ejecutables que no realizan acciones
concretas, pero ayudan a la legibilidad del programa y no afectan a la ejecución del mismo.
Las sentencias ejecutables aparecen en el cuerpo del programa a continuación de la palabra
reservada
Begin
Cuidado!!!
El tipo de expresión debe ser del mismo tipo que el de la variable.
Contador:
Un contador es una variable que se incrementa, cuando se ejecuta, en una unidad o en una
cantidad constante.
Pepe := 9
Pepe := Pepe + 1;
Si
Acum = 15
Cant = 20
Entonces ahora Acum valdría 35
& Reglas de las expresiones o prioridades: se respeta las mismas prioridades del
álgebra.
Los datos se pueden almacenar en la memoria de tres formas diferentes: asociados con
constantes, asignados a una variable con una sentencia de asignación o una sentencia de
lectura. (Ya se vieron las dos primeras formas)
La tercera forma es la sentencia de lectura, que es la más indicada si se desea manipular
datos diferentes cada vez que se ejecute el problema. Además, la lectura de los datos
permite asignar valores desde dispositivos y de archivos externos (por ejemplo, un teclado
o una unidad de disco), denominándose operación de entrada o de lectura.
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 16
A medida que se realizan los cálculos en un programa, se necesitan visualizar los resultados
Esta operación se conoce como operación de salida o de escritura. En los algoritmos las
instrucciones de entrada / salida escritas en pseudocódigo son:
leer (listas de variables entrada) leer (v. z, x)
escribir (listas de variables salida) escribir (a, b, c)
En Pascal todas las operaciones de entrada / salida se realizan ejecutando unidades de
programa especiales denominadas procedimientos de entrada / salida que forman parte del
compilador Pascal y sus nombres son identificadores estándar:
Procedimiento WriteLn
El propósito de WriteLn es escribir (visualizar) información en la pantalla
Formato: WriteLn (ítem, ítem..);
ítem es el objeto que desea visualizar: un valor literal (entero, real, un carácter una cadena,
o un valor lógico (True o False), una constante con nombre, una variable, o una llamada a
una función.
Cuando se ejecuta el procedimiento WriteLn, se visualizan todos los elementos en el orden
dado y en la misma línea. Al terminar de visualizar toda la línea, el cursor avanza (salta) al
comienzo de la línea siguiente.
Procedimiento Write
Como se ha dicho, después de ejecutar el procedimiento WriteLn, el cursor avanza (salta) al
comienzo de la siguiente línea. Si se desea que el cursor quede en la misma línea se debe
utilizar el procedimiento Write.
Formatos de salida Turbo Pascal permiten controlar, en cierta medida las instrucciones de
salida que presentan resultados, ya que es posible especificar el número de posiciones del
campo de escritura y para los números reales es posible precisar el número de decimales
deseado.
Se pueden utilizar especificadores de formato de campo para definir dicho ancho.
x := 265.7892
WriteLn(x :10 :4); 265.7892
WriteLn(x :10 :2); 265.79
WriteLn(x :6 :4); *********
X := 14;
X := ‘AB’
WriteLn(x :4); _ _ AB
Otra diferencia importante surge del siguiente análisis: supongamos que se quieren leer
desde teclado una serie de números enteros y reales, mediante las sentencias:
readln(int1, re1);
readln(int2);
donde int1 es una variable tipo entero (integer), y re1 es una variable tipo real (real).
Y supongamos que el usuario ingresa los siguientes datos:
Cuando la computadora lee 73, lo coloca en la variable int1, luego lee el 98.45 (todo en la
misma línea, separados por un espacio) y lo coloca en re1. Pero como se trata de un readln
(read line) ignora todo el resto que esté en la misma línea (en este caso ignora al 62), y hace
un avance de carro (es decir, pasa a la línea siguiente). Al leer la línea siguiente coloca al
45 en int2. El resultado es:
int1 = 73
re1 = 98.45
int2 = 45
Ahora supongamos que tenemos los mismos datos de entrada, pero con las siguientes
sentencias:
read(int1, re1);
readln(int2);
En este caso lee el 73 y lo coloca en int1, luego lee el 98.45 y lo coloca en re1. Como se
trata de un read, no hace avance de línea, y pasa a ejecutar la sentencia siguiente (o sea el
readln). Al ejecutar el readln siguiente carga en int2 al número 62, porque el read anterior
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 19
no había hecho avance de línea, y por consiguiente el número 45 no se almacena en
ninguna variable. El resultado es:
int1 = 73
re1 = 98.45
int2 = 62
ESTRUCTURAS DE SELECCIÓN
& SELECTIVAS
Las estructuras selectivas se utilizan para tomar decisiones lógicas; de ahí que se suelan
denominar estructuras de decisión o alternativas. Son dos: la sentencia if y la sentencia
case.
If
Esta sentencia, es considerada de alternativa doble (si se cumple cierta condición entonces
... , sino .... / If ...... then ...... else..... ).
if <condición>
then < acción S1>
else < acción S2>
Cuidado!!!
Cuando alguna de las opciones tiene más de una instrucción, dicha opción deberá llevar un
begin con su correspondiente end. Debe notarse que el end del then no tiene puntuación y
en cambio el del else si lleva punto y coma (;). Esto se debe que este end es el último del if.
Otro caso para tener en cuenta es cuando la variable por la que se consulta es del tipo
Boolean (sólo puede valer True o False). En este caso no es necesario especificar en la
pregunta si la variable es verdadera, ya que Pascal lo toma como predeterminado.
Case
Case <variable> of
Opción1 < acción 1>
Opción2 < acción 2>
Opción n < acción n>
else < acción m>
Case A of
1 Imprimir “UNO”
2 Imprimir “DOS”
case A of
1 : writeln ( 'U n o ');
2 : writeln ( 'D o s ');
3 : writeln ( 'T r e s ');
4 : writeln ( 'C u a t r o ');
else writeln ( 'C i n c o ');
end;
& REPETITIVAS
Las estructuras repetitivas (llamadas también bucles, lazos o ciclos) se utilizan para realizar
varias veces el mismo conjunto de operaciones. Dentro de ellas se encuentran aquellas
donde la cantidad repeticiones se manejan por un número y las que se realizan hasta que se
cumple cierta condición.
For
Esta sentencia, es un bucle controlado por un contador, denominado variable de control o
índice.
for variable cont = inicial to final do
begin
< acción 1>
< acción 2>
......................
end
While
En este ciclo la pregunta de condición se hace cada vez antes de comenzar el ciclo.
Ejemplo:
Se pide un programa donde se ingresan una serie de números positivos, el último es un
valor negativo. Se pide calcular el promedio de los valores positivos (no incluir el último
valor), e imprimir los valores y su promedio.
Repeat
Esta solución se utiliza cuando el ciclo debe incluir el último valor ingresado, ya que la
pregunta se hace al final del ciclo.
Ejemplo
Se ingresan una serie de números distintos, el último es el valor 12. Se pide un programa
que calcule el promedio de los valores (incluir el último valor), e imprimir los valores y su
promedio.
S := 0;
N := 0;
repeat
write ( 'Ingrese un valor ');
read (A);
writeln ( 'El valor es : ',A );
writeln ;
S := S + A;
N := N + 1;
until A = 12;
* ENCABEZAMIENTO
* BLOQUE DE DECLARACIONES:
- Declaración de rótulos
- Declaración de constantes
- Declaración de tipos
- Declaración de variables
- Declaración de subprogramas
Begin
<acción 1>;
<acción 2>;
.
<acción n>
End.
ACCIONES:
* DE SECUENCIA:
- asignación
- invocación a procedimientos (Read, Write)
- sentencia with
* DE SELECCIÓN:
* DE REPETICIÓN:
- con contador:
For <vble.ctrl.> := <exp.1> to <exp.2> do <acción>
For <v.c.> := <e.1> downto <e.2> do <acc.>
- ciclo con condición a la entrada:
While <exp. lógica> do <acción>;
- ciclo con condición a la salida:
Repeat <acción 1>;
<acción 2>
until <exp. lógica>;
Procedimientos y Funciones
PROCEDIMIENTOS Y FUNCIONES
Los programas Pascal pueden escribirse en forma modular, permitiendo así que un
problema general se descomponga en una secuencia de subproblemas individuales.
La modularización proporciona dos importantes ventajas:
1) Para tareas que deben efectuarse más de una vez puede definirse un módulo que sea
escrito una vez y pueda ser llamado desde distintos puntos del programa.
2) La claridad lógica resultante de la descomposición de un programa en módulos concisos
e individuales, donde cada módulo representa una parte bien definida del problema total.
PROCEDIMIENTOS
Ejemplo:
Un programa lee tres cantidades enteras y determina luego qué cantidad es la mayor.
Procedure Maximo;
Var max: integer;
Begin
If a > b then max:= a else max := b;
If c > max then max := c;
writeln (“El máximo es:”, max)
End;
PROCEDURE Dos;
Var a: char;
b: boolean;
BEGIN
.
END;
Obsérvese que varios de los nombres se usan para variables distintas en los procedimientos
y en el bloque principal. En particular, a se redefine dentro de cada procedimiento, y b y d
se redefinen cada una en un procedimiento. Las definiciones locales tendrán precedencia
dentro de sus respectivos procedimientos. Dentro del bloque principal, sin embargo, las
variables se interpretarán como en las declaraciones originales.
Observe que el uso de las variables locales dentro de sus respectivos ámbitos no altera los
valores que se asignan a las variables globales. El hecho de que las variables locales y
globales compartan los mismos nombres no tiene ningún efecto.
Parámetros
Muchos programas precisan que se intercambie información entre un procedimiento y el
punto en que el procedimiento fue invocado. Un modo de conseguir esto es emplear
variables globales. El uso de parámetros ofrece un método mejor para el intercambio de
información entre un procedimiento y su punto de referencia.
Cada dato se transfiere entre un parámetro actual, incluido dentro de la referencia al
procedimiento, y un parámetro formal correspondiente, definido dentro del propio
procedimiento. Cuando se invoca un procedimiento, los parámetros actuales reemplazan a
los parámetros formales, creando así un mecanismo de intercambio de información entre el
procedimiento y su punto de referencia. La manera en que se intercambia la información
depende, sin embargo, de la manera en que se definan y utilicen los parámetros.
Las variables x e y son parámetros formales de tipo real definidos dentro del procedimiento
flash. Los parámetros actuales son las variables a, b, c y d.
Un procedimiento puede contener cuatro clases diferentes de parámetros formales. Son los:
parámetros-valor, parámetro-variable, parámetro-procedimiento y parámetro-función.
Parámetro - Valor:
Son considerados parámetros de entrada a sus respectivos procedimientos. Implica una
transferencia de valor más que una verdadera sustitución de parámetro. Cuando se
transfiere información entre un parámetro actual y un parámetro-valor, el valor del
parámetro actual se asigna al parámetro-valor. Este valor puede ser procesado entonces por
el procedimiento (referenciando el parámetro-valor). Sin embargo los valores
representados por parámetro-valor no pueden ser transferidos en dirección opuesta, esto es,
desde el procedimiento al punto de referencia del programa. Es por esto que los
parámetros-valor se consideran parámetros de entrada.
Se declaran incluyendo simplemente el nombre y los tipos de datos correspondientes dentro
de la cabecera del procedimiento, sin ningún prefijo (tal como VAR). La ausencia de tal
prefijo es lo que identifica automáticamente a esta clase de parámetro. El PROCEDURE
flash del PROGRAM muestra, hace uso de este tipo de parámetros.
Debe quedar bien claro que cualquier alteración en el valor de un parámetro-valor dentro
del procedimiento no afectará al valor de ninguno de los parámetros actuales (recuérdese
que los parámetros-valor se consideran parámetros de entrada). Son fáciles de usar en
situaciones que permitan una transferencia de información en un sólo sentido.
Son convenientes para operar en situaciones donde la información se transfiere sólo desde
la referencia al procedimiento.
Parámetro - Variable:
FUNCIONES
Obsérvese que la función factorial es llamada sólo una vez, en la última sentencia Writeln
del bloque principal. Al ejecutarse el programa debe tenerse cuidado con el valor que se le
Analicemos esta otra versión, que en principio parece más atrayente que la original, pero no
es válida, ya que altera el valor de factorial después de su asignación inicial, cuando n es
mayor que 1.
Ejercicio 1
Se dispone de un registro diario de lluvias de un mes en una localidad. Los registros, que se
leerán por teclado, traerán el día del mes y la cantidad llovida, y vendrán ordenadamente
por día. El primer registro a leer contendrá el número del mes y el año. Si llovió algún día
del mes, indicar cuál fue el día más lluvioso e informar si llovió dos días seguidos en el
mes. Si no llovió en todo el mes se debe emitir un mensaje en tal sentido.
PROGRAM LLUVIAS;
PROCEDURE Imprimir;
Begin
If Llovio then Begin
Writeln (‘Día más lluvioso del mes:’, DiaLluviaMax,
‘Lluvia caida:’, LluviaMax);
If Seguidos then Writeln (‘Llovió dos días seguidos’)
End
else Writeln (‘No llovió en todo el mes’)
End;
PROCEDURE ProcesarDia;
Begin
Readln (Dia, Lluvia);
If Lluvia > 0 then Begin
{llovió ese día}
Llovio := True;
If LluviaAnt > 0 then Seguidos := True;
{también llovió el día anterior}
If Lluvia > LluviaMax then Begin
LluviaMax := Lluvia;
DiaLluviaMax:= Dia
End;
End;
LluviaAnt := Lluvia
End;
Begin {ProcesarMes}
LluviaMax := 0;
LluviaAnt := 0;
Llovio := False;
Seguidos := False;
Repeat
ProcesarDia
until Dia = DiasMes;
End; {ProcesarMes}
Begin {Lluvias}
CantDias;
ProcesarMes;
Imprimir
End. {Lluvias}
Ejercicio 2
Dado un telegrama terminado en punto, contar cuántas veces aparece cada letra minúscula,
exceptuando la ‘ñ’.
PROGRAM Minusculas;
PROCEDURE Inicializar;
Var C : char;
Begin
For C := ‘a’ to ‘z’ do Cantidad [C] := 0
End;
PROCEDURE ArmarTabla;
Var C : char;
Begin
Read (C);
While C <> ‘.’ do Begin
If C in [‘a’ .. ‘z’] then Inc (Cantidad[C]);
Read (C)
End
End;
PROCEDURE Imprimir;
Var C : char;
Begin
Writeln (‘Letra ’ , ‘ Cantidad de apariciones’);
For C := ‘a’ to ‘z’ do Writeln (C, ‘ ‘: 20, Cantidad[C]: 3)
End;
Begin
Inicializar;
ArmarTabla;
Imprimir
End.
Ejercicio 3
Escribir un algoritmo que contenga una función que calcule la suma de los dígitos de la
representación decimal de un valor entero no negativo. (Ejemplo: Si número= 23996
entonces la suma de los dígitos es 29).
Análisis: La suma de los dígitos de un número entero se obtiene sumando la cifra de las
unidades y las cifras del número resultante de la división entera del número inicial por 10.
La cifra de las unidades se obtiene mediante la operación resto entero entre 10.
Si el número es menor que 10 obtenemos la condición de salida.
Begin
ClrScr;
Write (‘Introduzca un número entero: ’);
Readln (n);
Writeln (‘La suma de los dígitos de: ‘, n, ‘es ‘ , Suma (n) )
End.
Ejercicio 4
Programar en Pascal un algoritmo que solicite 2 números enteros, los sume, e imprima el
resultado en pantalla en la posición (38, 12). Utilizar procedimientos.
procedure carga_numeros;
begin
clrscr;
write('Ingrese un numero entero: ');
readln(num1);
write('Ingrese otro numero entero: ');
readln(num2);
end;
begin
resultado:=(num1 + num2);
end;
procedure muestra_resultado;
begin
clrscr;
gotoxy(38,12);
writeln('Resultado = ', resultado);
end;
uses crt;
begin
clrscr;
write('Ingrese un numero entero: ');
readln(x);
write('Ingrese otro numero entero: ');
readln(y);
end;
begin
result:=(a + b);
end;
begin
clrscr;
gotoxy(38,12);
writeln('Resultado = ', res);
end;
Ejercicio 5
uses crt;
var a: MiEntero;
procedure imprime;
var a: MiEntero;
procedure imprime;
var a: MiEntero;
var a: MiEntero;
begin
a:=0;
writeln(a);
end;
begin
a:=1;
imprime;
writeln(a);
end;
begin
a:=2;
imprime;
writeln(a);
end;
Ejercicio 6
program CalcularPotencia(input,output);
uses crt;
begin
resultado:=1; Falla cuando el
exponente es
for i:=1 to num2 do negativo
resultado:=(num1 * resultado);
calcula_potencia:=resultado;
end;
uses crt;
begin
resultado:=1;
if (num2<0) then
resultado:=(1/resultado);
calcula_potencia:=resultado;
end;
Ejercicio 7
Construir un programa en Pascal que lea un número de 3 cifras positivo por teclado, luego
emita un número de 3 cifras positivo al azar, y finalmente compare ambos números,
informando por pantalla si es menor, mayor ó igual.
uses crt;
begin
randomize;
numero_aleatorio:=random(1000); {Retorna un entero entre 0 y 999}
end;
begin
if (num1<num2) then
writeln(num1, ' es menor que ', num2)
else
if (num1>num2) then
writeln(num1, ' es mayor que ', num2)
else
begin
writeln(num1, ' es igual que ', num2);
writeln('Usted ha acertado!!!');
end;
end;
Vectores y Matrices
VECTORES
Los vectores, también conocidos como arreglos o arrays, son una de las estructuras más
útiles en programación. Dicho en forma sencilla, un vector es una variable estructurada que
permite almacenar varios datos, y presenta las siguientes características:
Los vectores están caracterizados por un nombre, un tipo y por su cantidad de elementos.
Gráficamente, puede visualizárselos de la siguiente manera:
Notas
Supongamos para este ejemplo que en un curso
1 6 de 10 alumnos se desea almacenar, para cada
2 4 estudiante, las calificaciones de su primer
parcial. Para ello, definiremos un vector, al que
3 2 llamaremos Notas, que contendrá 10 elementos
4 7 del tipo entero (integer).
Notemos aquí que, tal como se indicó
5 5 anteriormente, tenemos una estructura en la que
6 6 todos los datos son del mismo tipo, en la que
podemos conocer un dato con sólo especificar
7 1 su posición, y que es claramente lineal.
8 8 Cabe aclarar que no necesariamente el arreglo
debe empezar con el subíndice 1, podría hacerlo
9 4 por el 0, o por cualquier otro valor. En este caso
10 9 en particular, resultaría práctico hacer coincidir
la nota de cada alumno con su lugar en la lista
(que empieza por el 1).
Para poder hacer uso de una variable estructurada en forma de vector, debemos en primer
lugar crear un tipo (en la sección de tipos del programa) de la siguiente manera:
Type [nombre del tipo] = array[primer subíndice .. último subíndice] of [tipo de los datos que se almacenarán]
Luego de definido el tipo, podemos crear una variable de dicho tipo, según la siguiente
sintaxis:
Para el ejemplo:
También es posible (aunque menos recomendable por hacer más difícil de entender el
programa) escribir solamente:
De esta forma obtendremos una variable estructurada de nombre Notas, que en cada
posición contendrá una variable atómica. Para invocar un dato del vector, por ejemplo el de
la posición 3, debemos utilizar la forma Notas [3], lo que arrojaría el valor entero 2 (según
nuestro ejemplo, el tercer alumno obtuvo una calificación de 2). Es decir, se debe
especificar entre corchetes el subíndice de Notas al que se desea acceder. Es importante
recordar que mientras Notas es una variable estructurada, Notas[3] es una variable
atómica, ya que su tipo es integer.
Program notas_alum;
Uses crt;
Var i :integer;
Begin
Writeln('Notas de los alumnos:');
For i:=1 to ALUM do
Writeln (i: 3,’: ‘,vector[i]);
End;
Begin
Clrscr;
toma_datos(Notas);
writeln;
muestra_datos(Notas);
readkey;
End.
Puede notarse en el ejemplo que se pasa el vector como parámetro, y que dicho pasaje se
hace por referencia. Esto es así por dos razones: en primer lugar se desea que el
procedimiento toma_datos modifique el contenido del vector Notas, y no de una copia
local como sucedería si se pasara el vector por valor; y en segundo lugar, aunque pudiera
trabajarse con una copia (por ejemplo, porque sólo se quisieran mostrar los contenidos del
vector y no modificar su contenido) como podría hacerse en el caso del procedimiento
muestra_datos, debe tenerse en consideración que las estructuras grandes consumen
recursos (ocupan memoria) haciendo poco recomendable su duplicado. Por esta razón al
pasar por parámetros una variable estructurada grande, se acostumbra hacerlo por
referencia (si bien en este ejemplo en particular no trabajamos con una estructura tan
grande, no resulta inapropiado tener esto en cuenta, especialmente en el caso de las
matrices, que son arreglos multidimensionales que se verán a continuación).
Finalmente, vale aclarar que los subíndices no tienen siquiera que ser números, basta con
que sean de un tipo ordinal. De hecho, podríamos hacer una declaración de la siguiente
forma: type tVector = array [‘A’..’D’] of integer y una variable miVector del tipo
tVector tendría 4 elementos enteros: miVector[‘A’], miVector[‘B’], miVector[‘C’] y
miVector[‘D’]. Notar incluso que pueden utilizarse constantes, como en el ejemplo dado
(en el que se usa ALUM). No puede, sin embargo, utilizarse variables, ya que los arreglos
son estructuras estáticas (su número de elementos no puede variar mientras se ejecuta el
programa).
Solución propuesta:
Program vectores;
Begin
Suma:=0;
For i:=1 to 20 do
Begin
Writeln(‘Elemento’,i,’:’);
Readln (vector[i]);
Suma:= Suma + vector[i];
End;
Strings
El lenguaje Pascal incluye un tipo predefinido string que permite almacenar cadenas de
caracteres. Al declarar una variable de este tipo, se hace de la forma miVariable: String
[max] donde max es la cantidad máxima de caracteres que tendrá la cadena. Puede omitirse
la sección de los corchetes y se asignará por defecto el valor 255.
A esta altura, estamos en condiciones de visualizar que dicho tipo de datos es básicamente
un array de caracteres. De hecho, si se dispone de una variable string de nombre miCadena
que se declaró mediante var miCadena: string [4]; y se asigna durante el programa
miCadena:=’hola’; , la sentencia writeln (miCadena [2]); escribirá la letra o en pantalla,
es decir el segundo elemento de la cadena. En Pascal, el tipo string admite hasta una
longitud de 255 caracteres. En rigor de verdad, Pascal trata ligeramente distinto al tipo
string que le es propio, por un lado, y a la popular forma de definir cadenas como arrays de
char terminados por un carácter especial (null), por otro. Esta última definición es la que
usan muchas de las funciones incluidas en la librería estándar Strings. De todas formas, es
sencillo convertir entre ambas definiciones.
Hasta ahora hemos visto arreglos unidimensionales. Es decir, que podíamos imaginar la
estructura como “una línea de variables una encima de la otra”. No obstante, podríamos
también imaginar estructuras rectangulares, o incluso cúbicas. Veamos el caso de un tablero
e Ta-Te-Ti:
1 2 3
1 X X
2 X O
3 O
Vemos aquí una estructura de dos dimensiones, de 3x3, donde cada posición puede ser
determinada unívocamente proporcionando 2 subíndices (uno para las filas y otro para las
columnas). A esta estructura se la denomina matriz (de 2 dimensiones).
Las matrices, al ser una extensión de los vectores, responden a las propiedades ya
enunciadas para los arreglos. Recordemos brevemente: todos los datos que se almacenarán
en la matriz son del mismo tipo (homogeneidad), la cantidad de elementos no cambia
durante la ejecución del programa (ni el tipo) por lo que se dice que es estática, y es una
estructura de acceso directo (basta con dar una posición, en este caso en la forma de dos
subíndices, para acceder a un dato específico) aunque en vez de ser lineal es rectangular,
cúbica o de las dimensiones correspondientes.
Las matrices, análogamente respecto de los arreglos, tienen un nombre, un tipo de
elementos que almacenan y un tamaño.
Al igual que hicimos con los arreglos, para hacer uso de una variable estructurada en forma
de matriz, crearemos primeramente un tipo (en la sección de tipos del programa) de la
siguiente manera:
Type [nombre del tipo] = array [1ro..último, 1ro..último] of [tipo de los datos que se almacenarán]
Luego de definido el tipo, podemos crear una variable de dicho tipo, según la sintaxis ya
vista:
Para el ejemplo:
También es posible (aunque menos recomendable por hacer más difícil de entender el
programa) escribir solamente:
Así obtendremos una variable estructurada de nombre Tablero, que en cada posición
contendrá una variable atómica, en nuestro caso del tipo carácter, que será X, O, o bien un
espacio. Para invocar un dato de la matriz, por ejemplo el que se encuentra en el centro,
debemos utilizar la forma Tablero [2,2], y esto correspondería según nuestro juego a la
letra X. Es decir, se deben especificar entre corchetes los subíndices para la fila y la
columna, separados por una coma. Es importante recordar que mientras el Tablero es una
variable estructurada, Tablero [2,2] contiene un dato atómico, ya que su tipo es char.
Veamos un procedimiento sencillo para cargar todas las posiciones de la matriz Tablero
con espacios (estado inicial del partido de Ta-Te-Ti):
Var i, j: integer;
Begin
For i:=1 to 3 do
For j:=1 to 3 do
matriz [i, j]:=’ ‘;
End;
Var i, j: integer;
Begin
For i:=1 to 3 do
Begin
For j:=1 to 3 do
Write (matriz [i, j]:2);
Writeln; {Pasar a la siguiente fila}
End;
En este último caso no es estrictamente necesario pasar por referencia la matriz, aunque
para una estructura grande economiza memoria hacerlo de esa manera.
Ejemplo 1
Construir un programa Pascal que permita cargar una matríz (de enteros) de 2 dimensiones
M y N, donde M (filas) y N (columnas) son ingresados por el usuario. Luego, que imprima
la matríz en pantalla.
program matrices;
uses crt;
MiEntero = integer;
begin
clrscr;
for i:=1 to m do {Recorre las filas}
for j:=1 to n do {Recorre las columnas}
begin
write('Ingrese el valor [', i, ', ', j, '] = ');
readln(mat[i,j]);
end;
readkey;
end;
begin
clrscr;
for i:=1 to m do
begin
for j:=1 to n do
write(mat[i,j]:5);
writeln;
end;
readkey;
end;
NOTA: como tarea para el hogar, adaptar el programa anterior para cargar e imprimir
matrices de 3 dimensiones.
Ejemplo 2
Construir un programa Pascal que defina una matríz para ser utilizada como tablero de
ajedréz, y que además inicialize todas las posiciones del mismo.
program ajedrez;
uses crt;
const MAX_RANGO = 8;
begin
for i:=1 to MAX_RANGO do
for j:=1 to MAX_RANGO do
tablero[i,j]:='X'; {X = casilla desocupada}
readkey;
end;
begin
clrscr;
for i:=1 to MAX_RANGO do
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 56
begin
for j:=1 to MAX_RANGO do
write(tablero[i,j]:5);
writeln;
end;
readkey;
end;
Ejemplo 3
Construir un programa Pascal en base al ejemplo anterior que calcule, si puede, cuántas
semillas debería haberle dado el Rey Shirham a su ministro por la invención del juego de
ajedréz, e imprima por pantalla el tablero con la cantidad de semillas por casillero. Utilizar
para los cálculos el tipo LONGINT, y ver qué sucede. Luego utilizar el tipo DOUBLE, y
volver a ejecutar el programa. Analizar si los resultados devueltos son correctos.
program ajedrez;
uses crt;
const MAX_RANGO = 8;
begin
tablero[1,1]:=1;
for i:=1 to MAX_RANGO do
begin
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 57
for j:=2 to MAX_RANGO do
tablero[i,j]:= ( 2 * tablero[i,j-1] );
if (i<MAX_RANGO) then
tablero[i+1,1]:= ( 2 * tablero[i,MAX_RANGO]);
end;
end;
begin
clrscr;
for i:=1 to MAX_RANGO do
begin
for j:=1 to MAX_RANGO do
write(tablero[i,j]:10);
writeln;
end;
end;
begin
total:=0;
for i:=1 to MAX_RANGO do
for j:=1 to MAX_RANGO do
total:=total + tablero[i,j];
cant_semillas:=total;
end;
Métodos de Búsqueda
MÉTODOS DE BÚSQUEDA
La búsqueda es una operación que tiene por objeto la localización de un elemento dentro de
la estructura de datos. El elemento que se busca se caracteriza o distingue por el valor de
una clave o la combinación de varias si la clave es compleja. En lo sucesivo entenderemos
por clave el valor, ya sea simple o no, que caracteriza al elemento. Siendo el array de una
dimensión o lista una estructura de acceso directo (búsqueda binaria) y a su vez de acceso
secuencial (búsqueda secuencial).
Búsqueda secuencial
PROGRAM BUSQUEDAS;
CONST
N = 100;
TYPE
TipoDato = integer;
TipoIndice = 1 .. N;
TipoPosicion = 0 .. N;
TipoArray= array [TipoIndice] of TipoDato;
VAR
Datos : TipoArray;
Clave : TipoDato;
Posicion: TipoPosicion;
Repeat
P := P + 1
Until (V[P] = Valor) or (P=N);
Ejemplo:
Repeat
P := P + 1
Until (V[P] >= Valor) or (P=N);
Búsqueda binaria
PROGRAM BUSCAR;
CONST
Inferior = 1;
Superior = 10;
TYPE
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 61
Indice = Inferior .. Superior;
Lista = Array[Indice] of Integer;
VAR
A: Lista;
Clave: Integer;
Posición: Indice;
Repeat
Central := (Bajo + Alto) div 2;
If Clave = A [Central] then Posicion := Central
else If Clave < A [Central] then Alto
:= Central-1
else Bajo
:= Central+1
until (Posicion <> 0) or (Bajo > Alto);
MÉTODOS DE ORDENACIÓN
CONST
N = 100;
TYPE
TipoArray = Array [1 .. N] of Integer;
El número de recorridas del array es N-1, pues en la última pasada se colocan los dos
elementos más grandes en la parte superior del array.
MEZCLA DE LISTAS
VAR
I1, Y2, K : Integer;
BEGIN
I1 := 1; *Se toma el primer elemento de cada uno de
I2 := 1; los arrays ordenados*
K := 1;
Registros
REGISTROS
Ejemplo:
TYPE
Tipo1 = ...;
Tipo2 = ...;
TipoRegistro = Record
Campo1 : Tipo1;
Campo2 : Tipo2
End;
VAR
Registro : TipoRegistro;
Ejemplo:
With Alumno do
Begin
Write (‘Introduzca Número de Padrón:’);
Readln (Padron); *Readln (Alumno.Padron)*
Materia := 7502 *Alumno.Materia := 7502*
End;
La única operación que se puede hacer con una variable tipo registro como tal es la
asignación, es decir, se pueden copiar todos los campos de una variable registro a otra
variable registro del mismo tipo, utilizando la sentencia de asignación.
Si Persona y Cliente son variables de mismo tipo registro, definidas en la declaración:
VAR
Persona, Cliente: Empleado;
Persona := Cliente;
Un registro puede ser pasado como parámetro a una función o procedimiento como
parámetro actual, siempre y cuando el correspondiente parámetro formal sea del mismo
tipo.
El procedimiento LeerEmpleado se puede utilizar para leer los campos de una variable
registro de tipo Empleado. La variable registro se pasa por variable y sólo se necesita un
parámetro en lugar de tres.
Registros jerárquicos
Los campos de los registros pueden ser de cualquier tipo definido por el usuario, incluso
también registros. Un registro con uno o más campos de tipo registro se denomina registro
jerárquico. Por ejemplo, un registro que contenga el nombre y fecha de cumpleaños de una
persona puede ser declarado de la siguiente forma:
TYPE
Fecha = Record
Dia : 1..31;
Mes : 1..12;
Anno : Integer
End;
Info = Record
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 69
Nombre : String [30];
CumpleAnnos : Fecha
End;
VAR
Persona : Info;
With Persona do
With CumpleAnnos do
Writeln (‘Día:’, Dia, ‘Mes:’, Mes, ‘Año:’, Anno);
Registros variantes
En Pascal es posible definir tipos registro, que tengan algunos campos que son iguales para
todas las variables de ese tipo (parte fija) y algunos campos que pueden ser diferentes o
variables (parte variante). Por ejemplo se pueden incluir información adicional sobre un
empleado basado en su estado civil. Para todos los empleados casados se puede desear
conocer el nombre del cónyuge y el número de hijos; para todos los empleados
divorciados, se puede desear conocer la fecha del divorcio y para todos los empleados
solteros se puede desear conocer si viven solos o no.
Ejemplo:
TYPE
Empleado = Record
...
End;
Estado = (Casado, Divorciado, Soltero, Viudo);
Ejecutivo = Record
Datos : Empleado;
case Estado of
Casado: (NombreConyuge: String [30];
NumHijos: Integer);
Divorciado: (FechaDivorcio: Fecha);
*Fecha es otro
tipo
definido*
Soltero: (Solo: Boolean);
Viudo: () (*carece de
campos*)
End; (*del Record*)
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 70
Ejemplo:
TYPE
Forma = (Triangulo, Cuadrado, Circulo);
Tinta = (Rojo, Amarillo, Verde, Azul);
Figura = Record
Color : Tinta;
Case Forma of
Triangulo : (Base, Altura: Real);
Cuadrado : (Lado: Real);
Circulo : (Radio: Real);
End; (*record*)
La parte variante se especifica con las palabras reservadas CASE .. OF y debe declararse
inmediatamente después de la parte fija. Sólo se admite una parte variante y los campos de
la parte variante pueden ser a su vez variantes. Si alguna de las opciones o valores de la
parte variante carece de definición de campos se indicaría con un par de paréntesis vacíos
‘()’.
No debe confundirse el CASE ..OF de la definición de la parte variante con una estructura
o sentencia CASE..OF pues debe observarse la falta del END que la cierra.
Turbo Pascal permite, al contrario que Pascal estándar, definir constantes tipo registro.
Estas constantes son en realidad variables que se inicializan a los valores indicados en la
declaración correspondiente, pero que pueden ser modificadas durante la ejecución del
programa. La declaración de este tipo de constantes necesita de la declaración previa de su
tipo y su posterior inicialización para cada uno de sus campos, tal como se indica en el
siguiente ejemplo:
TYPE
Empleado = Record
Nombre : String [30];
Edad : Integer;
End;
CONST
NumReg : Empleado = (Nombre: ‘Mortimer’;
Edad : 44);
Ejemplo 1
En una empresa de turismo se desea saber cuál es el país más visitado por sus clientes y
cuál es el medio de transporte más elejido para visitar dicho país. Para ello se cuenta con un
vector con el listado de países, y cuántas veces se ha viajado a cada uno de ellos en
diferentes medios de transporte.
Se pide también un procedimiento que permita cargar los datos.
El vector no está ordenado por ningún campo en particular.
program turismo;
uses crt;
begin
n:=0;
repeat
begin
write('Pais de destino: ');
readln(destino.pais);
write('Medio de transporte: ');
readln(destino.medio);
var i: integer;
begin
destino:=lista[1];
for i:=2 to n do
if (lista[i].cantVeces>destino.cantVeces) then
destino:=lista[i];
end;
Ejemplo 2
- Padrón,
- Nombre y Apellido,
- Notas (del 1er., 2do. y 3er. parcial).
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 73
Se desea construir:
program alumnos;
uses crt;
tAlumno = record
padron: longint;
nomyap: string[50];
notas: tNotas;
end;
begin
li:=1;
ls:=n;
posicion:=0;
repeat
begin
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 74
medio:=(li+ls) div 2;
if (padron=lista[medio].padron) then
posicion:=medio
else
if (padron<lista[medio].padron) then
ls:=medio-1
else
li:=medio+1;
end;
until (posicion<>0) or (li>ls);
binaria:=posicion;
end;
begin
repeat
begin
write('Padron: ');
readln(alumno.padron);
write('Nombre y apellido: ');
readln(alumno.nomyap);
write('Nota 1er. parcial: ');
readln(alumno.notas.parcial1);
write('Nota 2do. parcial: ');
readln(alumno.notas.parcial2);
inc(n);
lista[n]:=alumno;
write('Otro alumno? (s/n): ');
readln(opcion);
end;
until upcase(opcion)='N';
end;
begin
for recorrido:=1 to n-1 do
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 75
for i:=recorrido+1 to n do
if (lista[recorrido].padron>lista[i].padron) then
begin
aux:=lista[recorrido];
lista[recorrido]:=lista[i];
lista[i]:=aux
end;
end;
var i: integer;
begin
for i:=1 to n do
begin
writeln('Padron: ', lista[i].padron);
writeln('Nombre y apellido: ', lista[i].nomyap);
writeln('Nota final: ', lista[i].notas.final:2:2);
end;
readkey
end;
var i: integer;
begin
for i:=1 to n do
lista[i].notas.final:=(lista[i].notas.parcial1+lista[i].notas.parcial2)/2;
end;
Ejemplo 3
Análisis:
El programa se resuelve utilizando una estructura alternativa múltiple, que según la opción
elegida bifurca hacia el procedimiento correspondiente. Como archivo de entrada de datos
utilizaremos el teclado.
PROGRAM GranAlmacen;
USES CRT;
CONST
Maximo= 100; (*máximo número de artículos*)
TYPE
Rango = 1..Maximo;
Rango0 = 0..Maximo;
Cadena10 = String [10];
Cadena6 = String [6];
Producto = Record
Nombre: Cadena10;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 77
Codigo: Cadena6;
Precio: Real;
Unidades: Integer
End;
Almacen = Array [Rango] of Producto;
VAR
Total : Rango0;
Opcion : Integer;
Productos : Almacen;
PROCEDURE MENU;
BEGIN
ClrScr;
Writeln (‘Menu’:42);
Writeln (‘0.-’: 30, ‘SALIDA’);
Writeln (‘1.-’: 30, ‘IMPRIMIR Almacen’);
Writeln (‘2.-’: 30, ‘BUSCAR POR CODIGO’);
Writeln (‘3.-’: 30, ‘ALTAS Y BAJAS’);
Writeln (‘4.-’: 30, ‘ORDENAR POR NOMBRE’);
Writeln (‘5.-’: 30, ‘ORDENAR POR CODIGO’);
END;
PROCEDURE PAUSA;
CONST
Caracteres = [#0.. #255];
VAR
Letra : Char;
BEGIN
GotoXY (2,23);
ClrEol;
Write (‘Pulse una tecla para continuar’);
Repeat
Letra := Readkey
until letra in Caracteres;
END;
Repeat
Write (‘Desea altas (a-A) o bajas (b-B)’);
Readln (Modo)
Until Upcase (Modo) in [‘A’, ‘B’];
Repeat
MENU;
Repeat
GotoXY (2, 23);
ClrEol;
Write (‘Introduzca Opción’);
Readln (Opcion);
Until Opcion in [0..5];
Case Opcion of
1: IMPRESION (Total, Productos);
2: BUSCARPORCODIGO (Total, Productos);
3: ACTUALIZAR (Total, Productos);
4: ORDENNOMBRE (Total, Productos);
5: ORDENCODIGO (Total, Productos);
End
Until Opcion = 0;
ClrScr;
Writeln (‘F I N D E P R O G R A M A’: 54);
Pausa
END.
Aclaraciones:
* GetDate (Var Dia, Mes, Anio, DiaSemana: Word) Se encuentra en la Unidad DOS.
Este procedimiento devuelve la fecha del día en números. También devuelve el día de la
semana como un número que va desde 0 a 6.
* GetTime (Var Horas, Minutos, Segundos, Centésimas: Word) Devuelve la hora del sist.
operativo. También se encuentra en la Unidad DOS.
Archivos
ARCHIVOS
Concepto general
Para declarar una variable archivo es necesario indicar el tipo de elemento que lo compone,
es decir, definir la naturaleza de sus registros.
Esta declaración puede hacerse directamente en la sección de declaración de variables, o
declarar previamente el tipo de archivo en la sección de tipos y posteriormente la variable o
variables correspondientes en la sección de variables, procedimiento éste más aconsejado.
Las dos declaraciones siguientes son equivalentes:
VAR
ENTEROS: File of Integer;
TYPE
NUMEROS = File of Integer
VAR
ENTEROS: NUMEROS;
El paso de parámetros de tipo archivo o archivo ha de hacerse siempre por referencia (por
variable). No se puede, en ningún caso, pasarlos por valor, por tanto, en la cabecera de
subprogramas deben ir siempre precedidos por la palabra reservada VAR.
Ejemplo:
Procedimiento ASSIGN
Ejemplo:
o bien
VAR
NomArch: String;
BEGIN
Write (‘Introduzca nombre DOS del archivo:’);
Readln (NomArch);
Assign (VarArch, NomArch);
.....
Procedimiento RESET
RESET (NomArch)
Procedimiento REWRITE
REWRITE (NomArch)
Procedimiento CLOSE
Una de las diferencias más acusadas en el tratamiento de archivos entre Pascal estándar y
Turbo Pascal es la necesidad de indicar con el procedimiento CLOSE el proceso de cierre
de archivos. En Pascal estándar los archivos se cierran cuando finaliza el programa,
mientras que en Turbo Pascal y otros compiladores es necesario el empleo explícito del
procedimiento CLOSE, porque de lo contrario los últimos datos se perderán.
El empleo del procedimiento CLOSE es necesario tanto en procesos de lectura como en
procesos de escritura sobre archivos.
La sintaxis o formato del procedimiento CLOSE es:
CLOSE (NomArch)
Procedimiento READ
WRITE escribe en un registro del archivo el contenido de una variable de memoria definida
del mismo tipo.
El formato del procedimiento es:
EOF (NomArch)
Dado que un archivo puede estar inicialmente vacío el tratamiento típico de cualquier
archivo sería:
donde Info es una variable registro del mismo tipo que los componentes del archivo y
Gustavo es el archivo que se procesa.
ARCHIVOS DE TEXTO
Los archivos de texto, también llamados archivos ASCII, están constituidos por secuencias
de caracteres de longitud variable separadas unas de otras por los códigos retorno de
carro/avance de línea (CR/LF o bien 13/10 en ASCII). Constituyen un tipo predefinido
en Pascal, que se designa por Text, tipo que no tiene que ser declarado de forma explícita
en la sección TYPE. Un archivo de texto se termina con una marca fin de archivo EOF
(^Z o el código ASCII 26).
VAR
NomArch: Text;
Turbo Pascal trata algunos dispositivos como archivos de texto, asignándoles nombres de al
menos tres caracteres. En la siguiente tabla se muestra una lista de dispositivos con los
identificadores asociados.
NOMBRE DESCRIPCIÓN
‘LPT1’ Impresora 1
‘LPT2’ Impresora 2
‘LPT3’ Impresora 3
‘PRN’ Impresora (LPT1)
Ejemplo:
PROGRAM FACULTAD;
VAR
Impresora: Text;
BEGIN
Assign (Impresora, ‘PRN’);
Rewrite (Impresora);
Writeln (Impresora, ‘Esta frase se imprime’);
Writeln (‘Esta frase no se imprime, se visualiza en pantalla’);
Close (Impresora)
END.
La impresora es un periférico que se puede tratar como un archivo de texto. Este periférico
puede asignarse a los dispositivos mencionados anteriormente o bien utilizar la unidad
Printer que proporciona Turbo Pascal y que asigna por defecto al dispositivo LPT1 el
nombre lógico de archivo LST.
Las sentencias READ y READLN leen caracteres de un archivo igual que lo hacen desde
teclado. Los formatos de las sentencias son:
Los procedimientos READLN y WRITELN sólo pueden utilizarse con archivos texto.
Sirven para generar en escritura (Writeln) y detectar en lectura (Readln) los caracteres de
control (CR/LF) que sirven para separar las distintas líneas de un archivo de texto. No
deben asignarse a variables de tipo char los caracteres fin de línea, ya que éstos no son más
que elementos para separar la información, y no información propiamente dicha.
Observación: Turbo Pascal, no así Pascal estándar, permite leer una cadena de caracteres
de un archivo de texto. Si el número de caracteres de una línea de texto es inferior a la
longitud de la cadena a leer, la lectura no continúa con los caracteres de la línea siguiente,
siendo, por tanto, la longitud lógica de la cadena el número de caracteres leídos.
Función EOLN
EOLN (NomArch) es una función lógica o booleana que se asocia a cada archivo de texto
para su procesamiento. Toma el valor True bajo dos condiciones: cuando el puntero de
lectura del archivo encuentra un fin de linea o cuando el puntero de lectura del archivo
alcanza el final del archivo. El uso típico de EOLN es detección en lectura de la marca fin
de línea
Procedimientos APPEND
Ejemplo:
VAR
f: Text;
BEGIN
Assign (f, ‘Frases.Dat’);
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 91
Append (f);
Writeln (f, ‘7502 Algoritmos y Programación I’);
Writeln (f, ‘Facultad de Ingeniería’);
Close (f)
END.
Ejemplo:
PROGRAM HacerCopia;
USES
DOS, CRT;
VAR
Origen, Copia: Text;
Car: Char;
BEGIN
ClrScr;
Assign (Origen, ‘C:\Datos\Texto.Dat’);
Reset (Origen);
Assign (Copia, ‘C:\Datos\Copiex.Dat’);
Rewrite (Copia);
While Not EOF (Origen) do
Begin
While Not EOLN (Origen) do
Begin
Read (Origen, Car);
Write (Copia, Car)
End; *se llegó a un fin de línea en archivo Origen*
Writeln (Copia); *se escribe fin de línea en Copia*
Readln (Origen) *siguiente línea de archivo Origen*
End;
Close (Origen);
Close (Copia)
END.
Están formados por registros del mismo formato y longitud por lo que permiten el acceso a
un registro específico mediante un número asociado al mismo, que se denomina su número
de registro lógico. El número asociado es de tipo LongInt (entero largo) y se asigna al
primer registro lógico el valor 0. Para que un archivo pueda ser tratado por
posicionamiento o acceso directo debe residir obligatoriamente en un dispositivo de
almacenamiento de ese tipo.
Apertura de un archivo
Tamaño de un archivo
Posicionamiento en un archivo
En realidad Pascal lo que hace es desplazar el puntero del archivo al registro número
NumReg y posicionarse en el mismo.
Los procedimientos utilizados para leer y escribir registros en un archivo de acceso directo
son READ y WRITE. La operación de lectura o escritura se realizará sobre el registro
actual. Para hacerlo sobre uno determinado hay que emplear previamente la función de
posicionamiento SEEK.
Cierre de un archivo
El cierre de un archivo de acceso directo se efectúa de igual modo que con cualquier otro
archivo.
CLOSE (Alumnos);
OBSERVACIÓN:
Turbo Pascal tiene predefinidos una serie de constantes y tipos enteros, que se
podrían definir en Pascal estándar del siguiente modo:
CONST
MaxInt = 32767;
MaxLongInt = 2147483647;
TYPE
Byte = 0 .. 255;
Ejemplo 1
NOTA: en este caso estamos utilizando la palabra ‘registro’ para referirnos a una variable
‘atómica’ (ya que los elementos del archivo son números enteros).
program enteros;
begin
assign(numeros,'ENTEROS.DAT'); {ENTEROS.DAT es el nombre fisico}
reset(numeros); {del archivo}
read(numeros,num);
write(num);
close(numeros);
end.
Ejemplo 2
Se pide un programa que cargue en un registro los datos de un solo alumno, y luego grabe
dicho registro en el archivo.
begin
assign(alum,'ALUMNOS.DAT');
{$I-}
reset(alum);
{$I+}
if (ioresult<>0) then
rewrite(alum);
alumno.nomyap:='Juan Perez';
alumno.dni:=23000460;
alumno.padron:=71000;
write(alum,alumno);
close(alum);
end.
Ejemplo 3
- Cree el archivo.
- Copie en el archivo la frase ‘Hoy es un lindo día’.
- Lea uno por uno los caracteres del archivo y los muestre por pantalla.
- Cierre el archivo.
program frase;
var i: integer;
begin
reset(arch);
for i:=1 to 19 do
write(arch,cadena[i]);
end;
begin
reset(arch);
while (not eof(arch)) do
begin
read(arch,caracter);
write(caracter);
end;
end;
Ejemplo 4
Llegado fin de mes, la empresa para la cual trabajamos nos solicita un programa que sea
capáz de liquidar los sueldos. Es decir, debe emitir un informe indicando de cada empleado
los siguientes datos:
Debe proveerse también de un procedimiento para la carga del archivo. Los datos para cada
empleado dentro de dicho archivo son:
program liquidacion;
uses wincrt;
begin
clrscr;
rewrite(arch);
repeat
begin
with empleado do
begin
write(‘Código de empleado: ‘);
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 99
readln(codigo);
write('Nombre y apellido: ');
readln(nomyap);
write('DNI: ');
readln(DNI);
write('Sueldo basico: ');
readln(Sueldo_basico);
write('Retenciones: ');
readln(reten);
write('Otro empleado? (s/n): ');
readln(opcion);
writeln;
end;
write(arch,empleado);
end;
until upcase(opcion)='N';
end;
begin
clrscr;
reset(arch);
while not eof(arch) do
begin
read(arch,empleado);
with empleado do
begin
writeln(‘Código de empleado: ‘, codigo);
writeln('Nombre y apellido: ', nomyap);
writeln('DNI: ', DNI);
writeln('Sueldo básico: ', sueldo_basico:5:2);
writeln('Retenciones: ', reten:5:2);
writeln('Sueldo neto: ', sueldo_basico - reten:5:2);
end;
write('Presione una tecla para continuar...');
readkey;
writeln;
end;
end;
Ejemplo 5
Crear un archivo secuencial Agenda cuyos registros contienen los campos: Nombre,
Apellido, Edad, Calle, Código Postal y Ciudad.
PROGRAM Crear_Archivo_Secuencial;
TYPE
Direcciones = Record
Nombre: String [20];
Apellido: String [20];
Edad: Integer;
Calle: String [20];
CodPostal: Integer;
Ciudad: String [20];
End;
VAR
Agenda : Archivo;
Articulo: Direcciones;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 101
MasDatos: Char;
BEGIN
Assign (Agenda, ‘C:\Datos\Personas.Dat’);
Rewrite (Agenda);
Repeat
With Articulo do
Begin
Write (‘Nombre:’);
Readln (Nombre);
Write (‘Apellido:’);
Readln (Apellido);
Write (‘Edad:’);
Readln (Edad);
Write (‘Calle:’);
Readln (Calle);
Write (‘C. Postal:’);
Readln (CodPostal);
Write (‘Ciudad:’);
Readln (Ciudad)
End;
Write (Agenda, Articulo);
Write (‘¿ Otra Dirección ? (S/N)’);
Readln (MasDatos);
Until Upcase (MasDatos) = ‘N’;
Close (Agenda)
END.
Ejemplo 6
Reset (Agenda);
While not Eof (Agenda) do
Begin
Read (Agenda, Articulo);
With Articulo do
Begin
Writeln (‘Nombre:’, Nombre);
Writeln (‘Apellido:’, Apellido);
Writeln (‘Edad:’, Edad);
Writeln (‘Calle:’, Calle);
Writeln (‘C. Postal:’, CodPostal);
Writeln (‘Ciudad:’, Ciudad);
End
End;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 102
Close (Agenda);
Ejemplo 7
ANALISIS:
Para encontrar el número más grande y el más chico se procede de la siguiente forma:
* Se abre el archivo para lectura.
* Si el archivo no está vacío, el primer registro o número será, momentáneamente, el mayor
y el menor elemento del archivo.
* Con una estructura repetitiva del tipo ‘Mientras no sea fin de archivo’ se van leyendo los
números del archivo y comparando con el mayor y menor número obtenido hasta el
momento. Si el número leído es mayor que el mayor actual, el mayor actual pasará a ser el
número leído. De la misma forma se procede para determinar si es menor que el menor
actual.
* Después de procesado el archivo se escriben los números Mayor y Menor.
PROGRAM Busqueda_Enteros;
USES
Dos, Crt;
TYPE
Archivo = File of Word;
VAR
Enteros: Archivo;
Mayor, Menor: Word;
BEGIN
ClrScr;
Assign (Enteros, ‘C:\Enteros.Dat’);
GenerarEnteros (Enteros);
MayorMenor (Enteros, Mayor, Menor)
END.
Ejemplo 8
ANALISIS:
El ejercicio consiste en realizar una búsqueda secuencial en un archivo de datos
desordenado. La búsqueda se terminará cuando se encuentre el dato buscado o cuando se
llegue a final de archivo.
PROGRAM DEPOSITO;
USES
Dos, Crt;
TYPE
Cadena30 = String [30];
Cadena6 = String [6];
RegArticulo = Record
Nombre: Cadena30;
Fabricante: Cadena6;
Precio: Real;
Cantidad: Byte
End;
VAR
Stock : Archivo;
Masconsultas : Char;
PROCEDURE Pausa;
CONST
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 105
Caracteres = [#0 .. #255];
VAR
Letra : Char;
BEGIN
GotoXY (2,23);
ClrEol;
Write (‘Pulse una tecla para continuar’);
Repeat
Letra := Readkey
until letra in Caracteres;
END;
BEGIN
ClrScr;
Assign (Stock, ‘C:\Stock.Dat’);
GenerarArchivo (Stock);
Repeat
Consultar (Stock);
GotoXY (2,23);
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 107
ClrEol;
Write (‘Mas consultas: (s-n)’);
Readln (MasConsultas)
Until Upcase (MasConsultas) <> ‘S’
END.
Ejemplo 9
Escribir un programa que permita crear un archivo inventario de los libros de una librería,
así como calcular e imprimir el valor total del inventario. Campos del registro: título, autor,
número de código, precio, cantidad.
ANALISIS:
El programa utiliza dos procedimientos: uno para generar el archivo de libros, y otro para
calcular el valor total de estos y mostrar sus datos. El procedimiento que genera los libros
de la librería llama a otro procedimiento para introducir los datos de cada uno de los
volúmenes. Tanto este procedimiento como el de valoración son procedimientos estándar
de tratamiento de archivos secuenciales.
PROGRAM GestionLibreria;
USES
Dos, Crt;
TYPE
Cadena30 = String [30];
Cadena6 = String [6];
Reglibro = Record
Titulo: Cadena30;
Autor: Cadena30;
Codigo: Cadena6;
Precio: Word;
Unidades: Byte
End;
BEGIN
ClrScr;
Assign (Libros, ‘C:\Libros.Dat’);
GenerarArchivo (Libros);
ValorTotal (Libros, Total);
Writeln (‘Valor Total Librería:’, Total)
END.
Ejemplo 10
ANALISIS:
Es un programa típico de procesamiento secuencial. El programa solicita del usuario si
mostrará todos los datos de la librería o los de un libro en particular. Para ello utiliza una
estructura alternativa doble, y dependiendo de la opción elegida bifurcará hacia un
procedimiento u otro.
Si la opción elegida es un listado general, se abre el archivo de libros para lectura, y
mientras no se alcance el final del archivo, se leerán los datos de un libro y se mostrarán en
pantalla.
Si la opción es obtener los datos de un libro, se procederá de forma análoga, procesándose
el archivo o bien hasta que se encuentre el libro buscado o hasta que se llegue el final del
archivo, ya que el archivo de libros no está ordenado por título o por otro campo.
PROGRAM GestionLibreria;
USES
Dos, Crt;
TYPE
Cadena30 = String [30];
Cadena6 = String [6];
RegLibro = Record
Titulo : Cadena30;
Autor : Cadena30;
Codigo : Cadena6;
Precio : Word;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 110
Unidades: Byte
End;
Libreria = File of RegLibro;
VAR
Libros : Libreria;
MasConsultas : Char;
PROCEDURE Pausa;
CONST
Caracteres = [#0 .. #255];
VAR
Letra : Char;
BEGIN
GotoXY (2,23);
ClrEol;
Write (‘Pulse una tecla para continuar’);
Repeat
Letra := Readkey
until letra in Caracteres;
END;
PROCEDURE MostrarLibro (Var L: RegLibro);
BEGIN
ClrScr;
With L do
Begin
Writeln (‘Titulo:’, Titulo);
Writeln (‘Autor:’, Autor);
Writeln (‘Codigo:’, Codigo);
Writeln (‘Precio:’, Precio);
Writeln (‘Unidades:’, Unidades);
End
END;
BEGIN
ClrScr;
Assign (Libros, ‘C:\Libros.Dat’);
Repeat
Consultar (Libros);
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 112
GotoXY (2, 23);
ClrEol;
Write (‘Mas consultas: (s-n)’);
Readln (MasConsultas)
Until Upcase (MasConsultas) <> ‘S’
END.
Ejemplo 11
El programa de la librería se desea ampliar para cerrar una base de datos, de modo que
pueda responder correctamente a las siguientes preguntas:
ANALISIS:
Programa similar a ejercicios anteriores, excepto que en el prceso secuencial de búsqueda
se cambian las condiciones de la misma, según el tipo de información requerida.
PROGRAM ConsultasLibreria;
USES
Dos, Crt;
TYPE
Cadena30 = String [30];
Cadena6 = String [6];
RegLibro = Record
Titulo : Cadena30;
Autor : Cadena30;
Codigo : Cadena6;
Precio : Word;
Unidades: Byte
End;
VAR
Libros : Libreria;
Autor : Cadena30;
Titulo : Cadena30;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 113
Maximo : Byte;
Minimo : Byte;
Mayor : Byte;
Ejemplares : Byte;
PROCEDURE Pausa;
CONST
Caracteres = [#0 .. #255];
VAR
Letra : Char;
BEGIN
GotoXY (2,23);
ClrEol;
Write (‘Pulse una tecla para continuar’);
Repeat
Letra := Readkey
until letra in Caracteres;
END;
Ejemplo 1
Crear un archivo directo llamado NUMEROS.DAT cuyos elementos sean enteros. Se pide
un programa que:
- Cree el archivo.
- Almacene en la posición 10 el número 250.
- Cierre el archivo.
program enteros;
begin
assign(numeros,'NUMEROS.DAT');
rewrite(numeros);
seek(numeros,10);
num:=250;
write(numeros,num);
close(numeros);
end.
Ejemplo 2
- Código (entero).
- Descripción (string).
- Precio unitario (real).
program mercaderias;
begin
assign(stock,'STOCK.DAT');
reset(stock);
Ejemplo 3
Una empresa nos solicita un programa que muestre en pantalla el listado de todos sus
empleados con antigüedad mayor a 10 años, indicando también el nombre y apellido.
Los registros del archivo de empleados contienen los siguientes campos:
Contamos además con otro archivo, cuyos registros contienen los siguientes campos:
program informe;
uses wincrt;
tAntiguedad = integer;
begin
clrscr;
rewrite(arch);
i:=0;
repeat
begin
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 119
writeln('Código de empleado: ', i);
write('Antigüedad: ');
readln(antiguedad);
write('Otra antigüedad? (s/n): ');
readln(opcion);
writeln;
write(arch,antiguedad);
inc(i);
end;
until upcase(opcion)='N';
end;
begin
clrscr;
reset(arch1);
reset(arch2);
while not eof(arch1) do
begin
read(arch1,empleado);
seek(arch2,empleado.codigo);
read(arch2,antiguedad);
if (antiguedad>10) then
begin
writeln('Nombre y apellido: ', empleado.nomyap);
writeln('Antigüedad: ', antiguedad);
write('Presione una tecla para continuar...');
readkey;
writeln;
end;
end;
end;
Ejemplo 1
program texto;
begin
repeat
begin
readln(linea);
writeln(arch,linea);
write('Otra linea? (s/n): ');
Ejemplo 2
program texto;
begin
reset(arch);
while (not eof(arch)) do
begin
while (not eoln(arch)) do
begin
read(arch,caracter);
write(caracter);
end;
writeln;
readln(arch);
end;
end;
Ejemplo 3
program palabras;
uses wincrt;
Ejemplo 4
program encripta_texto;
begin
reset(arch1); {En el caso de archivos de texto, Reset lo abre sólo para lectura.}
while not eof(arch1) do
begin
while not eoln(arch1) do
begin
read(arch1,car);
codigo:=ord(car);
write(arch2,codigo);
end;
readln(arch1);
writeln(arch2);
end;
end;
program estadistica;
uses wincrt;
begin
reset(arch);
letras:=0;
blancos:=0;
palabras:=0;
while not eof(arch) do
begin
anterior:=' ';
while not eoln(arch) do
begin
read(arch,car);
if (car<>' ') then
inc(letras)
else
begin
inc(blancos);
if (anterior<>' ') then
inc(palabras);
end;
anterior:=car;
end;
if (anterior<>' ') then
inc(palabras);
readln(arch);
end;
clrscr;
writeln('Cantidad de letras: ', letras);
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 125
writeln('Cantidad de blancos: ', blancos);
writeln('Cantidad de palabras: ', palabras);
readkey;
end;
Ejemplo 6
Dado que Turbo Pascal trata ciertos periféricos como archivos de texto, se desea construir
un programa que abra CUENTOS.TXT (que se supone que existe), y lo imprima completo
por impresora, respetando los avances de línea.
program impresion;
begin
while not eof(arch1) do
begin
while not eoln(arch1) do
begin
read(arch1,car);
write(arch2,car);
end;
readln(arch1);
writeln(arch2);
end;
end;
Para poder comprender el tema tratado en este apunte, es preciso pensar a los archivos
como conjuntos del álgebra. Después de todo no es una idea tan descabellada, ya que en
definitiva un archivo contiene elementos, conocidos como registros. Y dados dos ó más
archivos, puede suponerse que son conjuntos que contienen justamente registros, y aplicar
sobre ellos las operaciones ya conocidas del álgebra de conjuntos. Estas son:
- Apareo.
- Merge.
- Intersección.
- Unión.
Trataremos cada una de estas técnicas, pero enfatizando los temas apareo y merge.
Antes de desarrollar las operaciones anteriores es preciso hacer una salvedad. Cuando
hacemos referencia a conjuntos del álgebra nos vienen inmediatamente a la mente los
archiconocidos Diagramas de Venn. En estos diagramas, que representamos como
circunferencias, los elementos no están ordenados. Dentro de un archivo, en cambio,
podemos disponer a los registros ordenadamente, lo cuál es una gran ventaja. Por
consiguiente, en lo que sigue se supondrá que los archivos son binarios, y además
ordenados en forma ascendente (por algún campo en particular).
APAREO
El algoritmo que se utiliza para aparear archivos consiste en tomar el primer registro de
MAESTRO y el primer registro de NOVEDADES (los cuales son los más chicos, porque
los archivos están ordenados ascendentemente). Acá se presentan distintas posibilidades, a
saber:
Cuando alguno de los dos archivos (MAESTRO y NOVEDADES) queda vacío, entonces
deberá copiarse el resto del archivo que aún contiene registros en NUEVO MAESTRO.
Finalmente se destruyen los archivos MAESTRO y NOVEDADES.
El merge entre archivos es similar al merge (ó mezcla) aplicado a vectores, tema que ya
conocemos. La idea consiste en, a partir de dos ó más archivos ordenados ascendentemente,
obtener otro archivo que incluya a todos los anteriores, y que también esté ordenados
ascendentemente.
Suponiendo que contamos con dos archivos que llamaremos ARCHIVO1 y ARCHIVO2,
los cuales, como ya dijimos, están ordenados de menor a mayor, y un archivo de salida
llamado MEZCLA. El algoritmo es el siguiente:
Tomamos el primer registro de ambos archivos (que son los más chicos). Si éste se
encuentra en:
ARCHIVO1: se graba este registro en MEZCLA, y se avanza una posición (una vez más,
esto en Pascal se realiza automáticamente).
Como con conjuntos, la intersección de archivos consiste en, a partir de dos ó más archivos
ordenados ascendentemente, obtener otro archivo (ordenado) que contenga sólo aquellos
registros que son comunes a todos los archivos intersecados.
La intersección, al igual que la unión entre archivos, es una operación poco usada respecto
al apareo y al merge, con lo cual nos conformaremos con dar la definición.
A partir de dos ó más archivos, se pretende obtener uno nuevo que contenga todos los
registros de los archivos anteriores (igual que con el álgebra de conjuntos).
Si prestamos un poco de atención, podría parecernos que la unión es igual al merge de
archivos. Sin embargo, la diferencia radica en que en la unión no se admiten registros
repetidos en el archivo de salida, cosa que si está permitida en el merge.
Ejemplo 1 (apareo)
Se pide un programa que actualize el archivo MERC.DAT a partir del archivo NOVE.DAT,
y devuelva el resultado en el archivo NUEVO.DAT.
Todos los archivos están ordenados ascendentemente por Código de producto.
program apareo_archivos;
uses wincrt;
type tReg1=record
cod,unidades: integer;
descr: string;
end;
tReg2=record
cod: integer;
cantidad: integer; {cantidad>0 -> COMPRA ; cantidad<0 -> VENTA}
end;
tArch1=file of tReg1;
tArch2=file of tReg2;
begin
while not eof(arch2) do
begin
read(arch2,reg2);
encontrado:=false;
while (not eof(arch1) and not encontrado) do
begin
read(arch1,reg1);
if (reg1.cod=reg2.cod) then
begin
reg1.unidades:=(reg1.unidades)+(reg2.cantidad);
encontrado:=true
end;
write(arch3,reg1)
end
end;
while not eof(arch1) do
begin
read(arch1,reg1);
write(arch3,reg1)
end
end;
Se tiene dos archivos que contienen números enteros ordenados ascendentemente, llamados
NUMEROS1.DAT y NUMEROS2.DAT. Se pide un programa que realice la intersección
de ambos, y el resultado lo almacene en el archivo INTER.DAT.
program interseccion_archivos;
uses wincrt;
begin
reset(arch1);
reset(arch2);
lee1:=true;
lee2:=true;
while (not eof(arch1) and not eof(arch2)) do
begin
if (lee1) then
read(arch1,num1);
if (lee2) then
read(arch2,num2);
if (num1=num2) then
begin
write(arch3,num1);
lee1:=true;
lee2:=true
end
else
if (num1<num2) then
begin
lee1:=true;
lee2:=false
end
else
begin
lee1:=false;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 136
lee2:=true
end
end;
encontrado:=false;
if (eof(arch1) and not lee1) then
while (not eof(arch2) and not encontrado) do
begin
read(arch2,num2);
if (num1=num2) then
begin
write(arch3,num1);
encontrado:=true
end
else
if (num1<num2) then
encontrado:=true
end;
encontrado:=false;
if (eof(arch2) and not lee2) then
while (not eof(arch1) and not encontrado) do
begin
read(arch1,num1);
if (num1=num2) then
begin
write(arch3,num1);
encontrado:=true
end
else
if (num2<num1) then
encontrado:=true
end;
end;
Ejemplo 3 (apareo)
Ambos archivos están ordenados por Código de cliente, y se supone que cada cliente
realizó una única operación en un día.
program apareo_archivos;
uses wincrt;
type tCliente=record
cod: integer;
nomyap: string;
monto: real;
end;
tOperac=record
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 138
cod: integer;
fecha, hora: string[10];
operac: char;
monto: real;
end;
tArchClien=file of tCliente;
tArchCajero=file of tOperac;
procedure apareo(var arch1: tArchClien; var arch2: tArchCajero; var arch3: tArchClien);
begin
reset(arch1);
reset(arch2);
while not eof(arch2) do
begin
read(arch2,reg2);
encontrado:=false;
while (not eof(arch1) and not encontrado) do
begin
read(arch1,reg1);
if (reg1.cod=reg2.cod) then
begin
if (upcase(reg2.operac)='D') then {ES UN DEPOSITO}
reg1.monto:=reg1.monto+reg2.monto
else
if (upcase(reg2.operac)='E') then {ES UNA EXTRACCION}
reg1.monto:=reg1.monto-reg2.monto;
encontrado:=true
end;
write(arch3,reg1)
end
end;
Claves
CLAVES
Lo de unívocamente viene del hecho de que, dado un dato debería poder conocer su “algo
que lo identifica”, y dado el “algo que lo identifica” debería poder conocer el dato. La
relación es estrictamente uno a uno:
Ese “algo” que identifica a un dato es a lo que se denomina clave. Por lo tanto, podemos
dar una definición sencilla y básica diciendo que:
DATO ⇔ CLAVE
Una clave es para un dato lo que el D.N.I. es para las personas. En el ámbito de nuestra
Facultad, una buena clave es el número de padrón de cada alumno. Esto significa que si doy
un padrón determinado, pueden decirme a quién corresponde, y no hay posibilidad de que
dicho padrón corresponda a dos alumnos simultáneamente.
Las claves no necesariamente son datos atómicos. Es decir que, una clave, puede estar
formada por varios datos de un elemento en particular, siempre con la condición de que lo
identifiquen unívocamente. En este caso hablamos de claves compuestas. Supongamos que
las personas no tuvieran D.N.I., y quisiera hacer referencia a un tal Juan Pérez, que vive en
Av. Córdoba 1250, en el 4to. piso, departamento “B”, y tiene 24 años de edad. Primero doy
el nombre, o sea Juan Pérez. Pero pueden haber muchos Juan Pérez en el mundo (de hecho
Del conjunto de datos anterior tendríamos que elegir uno ó más campos que identifiquen de
manera única a cada registro de la tabla (es decir, a cada fila). En primera instancia
podríamos optar por el número de padrón, ya que todo alumno tiene número de padrón, y
además cada número de padrón corresponde a un único alumno. Otra posibilidad
interesante sería elegir el D.N.I. como clave de nuestro conjunto de datos. ¿Estaría bien
elegir como clave el nombre y apellido? ¡Absolutamente no! Por la sencilla razón de que
pueden haber dos alumnos (ó más) con igual nombre y apellido. Este mismo argumento se
aplica para la dirección y la edad, que tampoco pueden elegirse como claves. ¿Pero que
sucede si elijo los campos nombre y apellido, dirección y edad en conjunto? Bueno, la cosa
cambia. Si suponemos que en un domicilio en particular no viven dos personas con igual
nombre y apellido e igual edad, entonces podría ser una posible clave. ¿Se le ocurre alguna
otra clave para ese conjunto de datos?
De todas las claves que propusimos, particularmente prefiero la del número de padrón. ¿Por
qué? Bueno, primero porque es un dato numérico, y a la hora de elegir claves es una buena
regla optar por campos que sean números. En ese caso también podría haber elegido el
DNI, pero el padrón es más corto, y por consiguiente es más fácil de manejar. En segundo
lugar, se trata de un dato atómico, a diferencia de la clave nombre y apellido, dirección y
Clave candidata (candidate key): superclave tal que al quitarle alguno de sus campos deja
de ser clave. Es una clave mínima.
Clave primaria (primary key): es aquella clave candidata que hemos seleccionado para
representar a los registros de un conjunto de datos. Dicha selección debe hacerse bajo algún
criterio.
Clave secundaria (secundary key): uno ó más campos de datos que no necesariamente
representan unívocamente a cada registro, pero que permite acceder a un conjunto de ellos
que matchean dicha clave secundaria. Por ejemplo, todos los alumnos de la Facultad de
Ingeniería que viven en la calle Colombres.
Elegir como clave primaria aquella clave candidata con menor número de campos.
Este apunte constituye una introducción muy elemental al manejo de claves. El tema es por
demás de importante, ya que de la elección de una buena clave depende el funcionamiento
futuro de nuestra base de datos. Una buena costumbre sería ponerlo en práctica con
nuestros simples y pequeños vectores y archivos.
Ejemplo 1
program clinica;
uses wincrt;
type tPaciente=record
cod: integer;
nomyap,medico: string;
edad: byte; {0..255}
rubeola,hepatitis,pulmonia: boolean;
end;
tArch=file of tPaciente;
Ejemplo 2
Se tienen dos archivos binarios, uno secuencial y otro directo, con información sobre los
productos que se venden en un supermercado.
El archivo secuencial es muy pequeño, tal que entra completo en memoria. Cada registro de
este archivo tiene los siguientes campos:
- Descripción (string).
- Disponibilidad (integer);
- Código de proveedor (integer).
En este archivo, el índice que representa a cada registro del mismo cumple el papel de
Código de proveedor.
program supermercado;
uses wincrt;
const MAX=10;
type tProducto=record
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 147
descr: string;
disp, cod_prov: integer;
end;
tProveedor=record
razon, direccion, local, tel: string;
end;
tVector=array[1..MAX] of tProducto;
tArchProd=file of tProducto;
tArchProv=file of tProveedor;
begin
inf:=1;
sup:=n;
encontrado:=false;
repeat
begin
medio:=(inf+sup) div 2;
if (descr=tabla[medio].descr) then
encontrado:=true
else
if (descr<tabla[medio].descr) then
sup:=medio-1
else
inf:=medio+1
end
until (encontrado) or (inf>sup);
if (encontrado) then
pos:=medio
else
pos:=0
end;
begin
rewrite(arch1);
rewrite(arch2);
clrscr;
repeat
begin
write('Descripcion: ');
readln(producto.descr);
write('Disponibilidad: ');
readln(producto.disp);
write('Cod. proveedor: ');
readln(producto.cod_prov);
write(arch1,producto);
write('¿Otro producto? (s/n): ');
readln(opcion);
writeln
end
until upcase(opcion)='N';
repeat
begin
write('Razon social: ');
readln(proveedor.razon);
write('Direccion: ');
readln(proveedor.direccion);
write('Localidad: ');
readln(proveedor.local);
write('Telefono: ');
readln(proveedor.tel);
write(arch2,proveedor);
write('¿Otro proveedor? (s/n): ');
readln(opcion);
writeln
end
until upcase(opcion)='N'
end;
begin
repeat
begin
clrscr;
write('Ingrese la descripcion: ');
readln(descr);
writeln;
binaria(tabla,descr,n,posicion);
if (tabla[posicion].disp<10) then
begin
seek(arch,tabla[posicion].cod_prov);
read(arch,proveedor);
with tabla[posicion] do
begin
writeln('Descripcion: ', descr);
writeln('Disponibilidad: ', disp);
writeln('Cod. proveedor: ', cod_prov)
end;
with proveedor do
begin
writeln('Razon social: ', razon);
writeln('Direccion: ', direccion);
writeln('Local: ', local);
writeln('Telefono: ', tel)
end
end
else
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 150
writeln('Disponibilidad suficiente.');
writeln;
write('¿Otro producto? (s/n): ');
readln(opcion)
end
until upcase(opcion)='N'
end;
Índices
INDICES
Siempre que debamos consultar un libro en busca de algún tema en particular, el sentido
común nos lleva a utilizar el índice del mismo que, en general, forma parte de las primeras
ó de las últimas páginas, de manera tal que el lector pueda acceder fácilmente a él. Si es un
índice alfabético tendremos un conjunto de ‘palabras clave’ ordenadas alfabéticamente, y
localizando aquella de nuestro interés obtenemos un número de página, indicando en qué
parte del libro encontraremos el tema buscado. Esto suele representar una ventaja
significativa si tenemos en cuenta que un libro común puede contener muchas hojas.
Estos sencillos pasos nos facilitan la tarea, y evitan que debamos recorrer hoja tras hoja
para encontrar aquél capítulo o párrafo que estamos buscando. Pero... ¿alguien se ha puesto
a pensar qué pasaría si El Capital de Karl Marx no tuviese índice?
Mientras escribo esto, se me cruza una idea por la cabeza, la cuál no puedo dejar de
comentar. Pienso que podríamos evitar el uso de índices en los libros, simplemente
vendiendo libros cuyo contenido esté ordenado alfabéticamente (al mejor estilo de un
diccionario ó guía telefónica). Si, claro, ya sé que no entenderíamos nada, pero nos
evitamos el uso de índices. Como sé que mi idea no les satisface, vamos a establecer una
primer regla:
Regla Nro. 1
Un libro que se precie de tal debe poseer un índice.
En el siguiente esquema pueden apreciarse cuáles son los pasos a seguir para poder
consultar un determinado tema en un libro:
TEMA A
BUSCAR
Voy al índice
OBTENGO EL
N° DE PÁGINA
Voy al libro
ACCEDO A
LA PÁGINA
DETERMINADA
Pero no solamente los libros poseen índices. Supongamos que voy a una biblioteca a pedir
el libro El Capital. La bibliotecaria, muy amablemente, accedería a una carpeta en donde
tiene registradas todas las obras literarias, ordenadas por nombre, con lo cual rápidamente
encontraría el título El Capital, y obtendría el número de estante en donde se encuentra
dicha obra. Vuelvo al día siguiente, pero ahora quiero alguna obra de Karl Marx. Como en
esa biblioteca la gente es muy eficiente, disponen de otra carpeta en donde figuran todos los
libros, pero ordenados por nombre y apellido del autor. Nuevamente, encontraría
rápidamente la ó las obras de Karl Marx, y, junto a ellas, los números de estante
correspondientes. Digamos que esas carpetas en donde la biblioteca registra la información
y ubicación de cada uno de sus libros juegan el papel de índices, y por consiguiente surge la
siguiente regla:
Regla Nro. 3
Los índices no sólo sirven para acceder a una determinada ubicación en forma directa,
sino que también permiten imponerle un orden a ‘algo’, sin necesidad de ordenar ese
‘algo’.
Claro, imaginemos qué pasaría si la biblioteca a la cual concurro a menudo tuviese que
reordenar todos los tomos cada vez que alguien busca una obra en particular...
Bueno, hasta ahora muy linda la introducción, pero ¿dónde están los algoritmos? ¿cuándo
aparece Pascal?
Sucede que el tema índices no es propio de la informática, sino que esta disciplina ‘toma
prestado’ el concepto para aplicarlo en determinadas circunstancias, en esos momentos en
los que debemos lidiar con archivos de datos (sobre todo aquellos que contienen mucha
información).
Una aproximación al tema de archivos nos permite definirlos como conjuntos de datos, con
una cierta estructura, y cuyo almacenamiento se lleva a cabo en dispositivos de memoria
auxiliar ó secundaria (discos rígidos, disquetes, CD-ROMs, etc.). Esa ‘cierta estructura’
puede constituirse a partir del uso de registros (aunque es cierto que en muchas
oportunidades los archivos se organizan como ‘tiras’ de bytes). Lo cierto es que ellos
pueden crecer en gran medida, y dificultarnos un poco las tareas. En primer lugar, si un
archivo se expande, también crece el número de registros a consultar en el caso de una
búsqueda. Y, en segundo lugar, la memoria auxiliar ó secundaria no nos provee una
performance aceptable en lo que a entrada / salida de datos se refiere. Busquemos, pues,
La pregunta que surge es la siguiente: ¿Por qué las búsquedas en disco son ineficientes? ó
¿Por qué es mejor utilizar la memoria principal? Si después de todo, un acceso a disco
tarda “menos que un segundo”...
Es verdad que el hecho de ir a disco en busca de un dato insume un tiempo irrisorio (en
particular, un acceso a disco implica un tiempo del orden de los los milisegundos, la
milésima parte de un segundo!!!). Pero también es verdad que acceder a memoria principal
es bastante más rápido (algo así como 70 nanosegundos, es decir, una milmillonésima parte
de un segundo!!!). Pero ¿qué significa “bastante más rápido”? Veamos la siguiente
relación:
Si una operación en memoria principal insumiese 1 minuto, entonces esa misma operación
en un disco rígido de los más veloces tardaría aproximadamente 2 años. Y, sin exagerar, si
una determinada operación tardara en memoria principal un tiempo de 1 segundo, en un
moderno disco rígido insimuría más de 10 días. Sorpredente ¿no?...
Ahora vemos porqué es tan importante poder trabajar en memoria principal, accediendo lo
menos posible a las unidades de almacenamiento externo.
De este análisis surge la necesidad de implementar alguna estructura especial para buscar
un dato en disco, la cual haga uso de la memoria principal. Esa estructura tan ansiada es lo
que se conoce como índice.
Por lo visto hasta ahora, un índice contiene un conjunto de información, que podemos
clasificar de la siguiente forma:
§ Claves.
§ Referencias (al archivo).
Claves: en un índice, el conjunto de claves corresponde al ó los campos por los cuales se
realiza la búsqueda. Para el ejemplo de la biblioteca, el conjunto de claves para dicho índice
podría ser el título de la obra, nombre y apellido del autor, etc.
Referencias: el conjunto de referencias está constituido por las posiciones (offsets) de los
registros en el archivo de datos, para una clave dada. Volviendo al ejemplo de la biblioteca,
nuestras referencias serían los números de estante en donde se halla el libro buscado.
Existe una gran clasificación dentro de la teoría de índices: índices primarios e índices
secundarios, que tiene que ver con los tipos de claves empleadas para indexar un conjunto
de datos (recomendamos que antes de seguir con este tema se lea el capítulo sobre claves).
Índices primarios: este tipo de índices tiene como característica que la clave por la cual se
indexa es primaria, es decir unívoca (sin posibilidad de que se repita). Por ejemplo, si
quisiéramos indexar la base de datos de los alumnos de la Facultad de Ingeniería de la
UBA, una posibilidad sería hacerlo por número de padrón. Dicho campo es clave primaria,
y por lo tanto el índice construido a partir de ella se denomina índice primario. Debe
quedar claro que en un índice primario cada entrada tiene una única referencia al archivo de
datos, porque justamente no pueden existir dos ó más registros con igual clave.
Índices secundarios: en este caso se indexa por una clave denominada secundaria (es
decir que puede repetirse dentro del conjunto de datos). Una clave secundaria no es
unívoca, y por lo tanto es posible que en el índice hayan varias referencias para una misma
clave. Supongamos ahora que queremos indexar la base de datos de la Facultad de
Ingeniería, pero por nombre y apellido del alumno. Ya que pueden haber varios alumnos
con igual nombre y apellido, una entrada del índice puede “apuntar” a más de un registro de
la base de datos.
Resumen
Un índice sirve para:
§ Claves.
§ Referencias u offsets al archivo.
Se desea que los índices sean trabajados en memoria principal, para obtener una mayor
velocidad de acceso, aunque esto no es posible en muchos casos. Ante esta situación, parte
del índice se carga y se accede en memoria principal, y el resto se lo conserva en disco
(hasta que deba ser utilizado). Esta técnica se conoce como paginación.
Ejemplo 1
Construir un programa en Pascal que permita cargar un archivo, cuyos registros contienen
los siguientes campos:
program indices;
uses crt;
t_reg_indice = record
dni, posicion: longint;
end;
begin
aux:=reg1;
reg1:=reg2;
reg2:=aux;
end;
begin
for recorrido:=1 to MAX_INDICE-1 do
for i:=1 to MAX_INDICE-recorrido do
if (indice[i].dni>indice[i+1].dni) then
intercambiar(indice[i],indice[i+1]);
end;
begin
reset(arch);
pos := 0;
while not eof(arch) do
begin
read(arch,registro);
reg_indice.dni := registro.dni;
reg_indice.posicion := pos;
indice[pos+1] := reg_indice;
inc(pos);
end;
ordena(indice);
end;
begin
inf:=1;
sup:=MAX_INDICE;
i:=0;
repeat
medio:=(inf+sup) div 2;
if (dni=indice[medio].dni) then
i:=medio
else
if (dni<indice[medio].dni) then
sup:=medio-1
else
inf:=medio+1;
until (i<>0) or (inf>sup);
if (inf>sup) then
busqueda_binaria:=0
else
busqueda_binaria:=i;
end;
begin
pos_indice := busqueda_binaria(indice,dni);
pos_archivo := indice[pos_indice].posicion;
seek(arch,pos_archivo);
read(arch,registro);
writeln('Nombre y apellido: ', registro.nomyap);
writeln('Edad: ', registro.edad);
writeln('DNI Nro.: ', registro.dni);
readkey;
end;
Recursividad
RECURSIVIDAD
La singular importancia de ésta técnica tiene que ver con que en muchas ocasiones
debemos programar algo que es de naturaleza recursiva. Un ejemplo claro sería si
quisiéramos hacer un algoritmo que calcule el factorial de un número. Matemáticamente,
la expresión que permite hallar el factorial de N viene dada por:
1 si N = 0
Factorial(N) = N ! =
N * (N-1)! si N > 0
Esto significa que, si N=0, el factorial de N (o sea N!) es 1, pero si N>0 el factorial de N es
N*factorial(N-1). Es decir que, para calcular el factorial de un número debemos ir
calculando los factoriales de los números que lo anteceden. Por ejemplo, el factorial de 4
(que se expresa 4!) está dado por:
4! = 4*3! = 4*3*2! = 4*3*2*1! = 4*3*2*1*0! = 4*3*2*1*1 = 24
Para programar este algoritmo construimos una función que reciba como parámetro el
número N, y devuelva el resultado de calcular el factorial de N. El encabezado de dicha
función tendría la siguiente forma:
Cuando se le pasa un valor N por parámetro a la función anterior, ésta deberá analizar si N
es igual ó mayor que cero. En el primer caso, la función devuelve 1, y en el segundo caso la
función se invoca a si misma (desde dentro de su propio código), de la siguiente manera:
...
fact := factorial(N-1);
...
La pregunta que viene a la mente después de hacer este breve análisis es la siguiente: si las
llamadas recursivas de un subprograma se van anidando ¿cuándo se corta la ejecución de
esto que parecería ser un ciclo sin fin?
Indudablemente necesitamos algo que en un momento determinado “corte” ese ciclo de
autoinvocaciones. Ese “algo” que estamos buscando es lo que se denomina una condición
de corte. Es decir, una sentencia que en un momento opte por no autoinvocar al
subprograma. En el caso del cálculo del factorial, la condición de corte está dada por:
Factorial(N) = 1 si N = 0
Esto significa que, en algún momento del cálculo del factorial, el N pasado por parámetro
será cero, y en ese caso la función no se autoinvoca, sino que devuelve el valor 1.
A todo esto ya podemos brindar una versión más amplia del contenido de la función
factorial( ).
begin
if (n=0) then
aux:=1 {CONDICIÓN DE CORTE}
else
aux:=factorial(n-1)*n {LLAMADA RECURSIVA}
factorial:=aux;
end;
Vamos a hacer un breve seguimiento de dicha función. Para ello necesitamos dar una
noción de cómo se manejan las variables locales a la función durante las llamadas
recursivas.
Cuando se realizan sucesivas llamadas recursivas todo transcurre de la misma forma que
cuando invocamos subprogramas como lo hacemos normalmente. Es decir, en el momento
de la invocación se reserva lugar en memoria principal para todas las variables locales al
subprograma que estamos llamando. Esa reserva de memoria se administra como una pila,
llenándose de la base hacia arriba. Al tratarse de una pila sólo podemos sacar el dato (en
este caso la variable local) que está en la cima. En el caso de nuestra función factorial, lo
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 165
que se va apilando son las N. Es decir que, después de varias llamadas recursivas tengo una
pila, en cuya base está la N correspondiente a la primer llamada a la función, y en la cima
está la N correspondiente a la última llamada. Lo importante es destacar que todas las N
son distintas, y no crea conflicto el hecho que tengan el mismo nombre, porque son
variables locales, y como tales pueden tener el mismo nombre.
factorial(3);
La primera vez n es igual a 3, pero aux está indeterminado, porque antes de conocer su
valor es necesario hacer la llamada recursiva. Entonces la pila en el stack quedaría de la
siguiente forma:
Al hacer la llamada recursiva, nuestra nueva variable local n ahora vale 2, pero la nueva
variable local aux vuelve a estar indeterminada. El stack queda:
En este último caso, la nueva variable local n toma el valor 0, y por lo tanto, la nueva
variable aux toma el valor 1 (ver el código de la función). Ahora aux está bien determinada,
y además se cortan las llamadas recursivas. Entonces, la última invocación a la función
devuelve el valor de la variable local aux. ¿De cuál de todas las aux devuelve el valor? De
la que está en la cima de la pila (no puedo sacar cosas de la pila que no estén en la cima de
la misma). Entonces la última llamada a la función retorna el valor 1. Este valor lo “agarra”
la función que la había invocado. El stack que nos queda es el siguiente:
Sólo resta decir, para cerrar el apunte, que existen dos tipos de recursividad: directa e
indirecta.
La recursividad directa es la que ejemplificamos con la función factorial, y se da cuando el
subprograma se invoca a si mismo. La indirecta, en cambio, tiene lugar cuando un
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 168
subprograma llama a otro, y éste vuelve a invocar al primero. En ambos casos es
indispensable incluir una condición de corte.
También cabe aclarar que todo proceso recursivo puede implementarse de manera iterativa.
Es decir, podríamos haber calculado el factorial de un número sin necesidad de invocar a
otros subprogramas, y utilizando algún tipo de ciclo iterativo (por ejemplo un ciclo for).
Queda como tarea construir una función que calcule el factorial de N dentro de sí misma,
en forma iterativa (sin llamadas recursivas).
Aquí finaliza el apunte sobre recursividad. El tema puede ampliarse, y realmente tiene gran
aplicación. Otro ejemplo clásico es el cálculo de los números de Fibonacci.
Ejemplo 1
1 si N = 0
FACT(N) =
FACT(N-1) * N si N > 0
program fact;
uses wincrt;
begin
if (n=0) then
factorial:=1 {CONDICION DE CORTE}
else
factorial:=factorial(n-1)*n {LLAMADA RECURSIVA}
end;
Ejemplo 2
0 si N = 0
FIBONACCI(N) 1 si N = 1
FIBONACCI(N-2) + FIBONACCI(N-1) si N ≥ 2
program numeros_fibonacci;
uses wincrt;
begin
if (n>=2) then
fibonacci:=fibonacci(n-1)+fibonacci(n-2) {LLAMADA RECURSIVA}
else
fibonacci:=n; {CONDICION DE CORTE}
end;
begin
clrscr;
write('Ingrese un entero: ');
readln(numero);
writeln;
writeln('La serie de Fibonacci da como resultado: ', fibonacci(numero));
readkey;
end.
Ejemplo 3
program acertijo;
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 171
uses wincrt;
procedure XXX;
var c: char;
begin
c:=readkey;
write(c);
if (c<>'.') then
XXX;
if (c<>'.') then
write(c);
end;
begin
clrscr;
XXX;
readkey;
end.
Así como habíamos clasificado a las variables en atómicas y estructuradas, también se las
puede clasificar en estáticas y dinámicas.
Son las variables que conocemos hasta ahora. Las declaramos con var al principio del
procedimiento o programa que estamos haciendo. Estas se crean en la memoria antes de ser
ejecutado el programa. El espacio que ocupa es siempre fijo y puede ser calculado
simplemente leyendo las declaraciones. La memoria se divide en varios segmentos según
veremos; estas variables se encontrarían almacenadas dentro del segmento de datos (data
segment). Por ejemplo:
Type tCadena=string[30];
Var foo:tCadena;
bar:tCadena;
En este caso la memoria consumida por nuestro programa será de 60 caracteres, de los
cuales 30 le pertenecen a la variable foo y otros 30 a la variable bar.
Esto es bastante simple pero trae muchas desventajas. Por ejemplo si queremos almacenar
una cadena de 40 caracteres tenemos que recompilar el programa, es decir que -al trabajar
con variables estáticas- no es posible editar el tamaño de la memoria en tiempo de
ejecución, sino que tenemos que hacerlo en tiempo de diseño. Es decir que no se puede
agrandar el tamaño de un array o de un string mientras se está corriendo el programa.
Otra desventaja que acarrean las variables estáticas es el uso excesivo de memoria; es decir
que si a mi variable foo de tipo tCadena la voy a llenar con solo 10 caracteres, los 20
caracteres restantes estarían ocupando espacio no aprovechado en memoria. O lo mismo si
tengo un array de 10 000 enteros y solo uso los primeros 5, en este caso me quedarían 9 995
veces el tamaño de un entero sin utilizar. Es por eso que al trabajar con variables estáticas
como arreglos, registros o strings a los cuales se les puede definir un tamaño -las variables
estáticas son de todos los tipos, pero solo mencioné arreglos y strings porque el tamaño uno
lo elige en la declaración- es muy importante evaluar sobre el uso del programa para decidir
bien el tamaño de las variables.
Al ejecutarse el programa, como este no conoce el tamaño que tendrán las variables que le
serán ingresadas, reservará en memoria el tamaño fijado en las declaraciones de tipos y
variables. Esto puede ser muy ineficiente a la hora de ahorrar memoria.
Por suerte Pascal admite variables dinámicas, aunque estas tienen otro modo de ser
utilizadas. En tiempo de ejecución deben ser creadas y/o eliminadas. Esto hace que existan
dos procedimientos en Pascal indispensables para este tipo de variables. Además al no ser
declaradas, estas variables carecen de nombre: simplemente son un espacio de memoria que
se reserva y que luego se devuelve. Por eso pueden ser llamadas anónimas. Con este tipo de
variables se pueden formar estructuras de datos bastante complejas y modificables en
tamaño, capaces de ocupar la mínima cantidad de memoria que realmente necesitan para
funcionar –estas estructuras pueden ser árboles, listas, pilas, colas, grafos, etc-.
Cuando definimos un puntero, debemos especificar a qué tipo de datos apunta; esto se debe
a que cada tipo de datos puede ocupar un tamaño distinto en la memoria.
Al no poseer un nombre, para poder referirse a estas variables es necesario otro tipo de
variable llamada puntero. Este tipo -que puede ser a su vez dinámico o estático- es
atómico y contiene como dato la primera posición de memoria en la cual se encuentra la
variable dinámica a la que queremos acceder. De esta forma, no sabemos el nombre de
nuestra variable, pero sí sabemos donde se encuentra y por lo tanto como accederla. Este es
el concepto de puntero, apuntador o pointer, sin embargo no vamos a ver en este curso
como se calculan posiciones de memoria ni nada por el estilo, puesto que el mismo Pascal
asigna la posición al puntero siendo transparente para el programador.
Es muy importante recordar que al terminar de utilizar una variable dinámica, su espacio en
memoria debe ser liberado, ya que de lo contrario queda asignado ocupando memoria que
no puede ser utilizada por otros procesos.
El lugar de la memoria en el cual se almacenan estas variables no es el mismo que el que
utilizan las variables estáticas para almacenarse. Las variables dinámicas se almacenan en
el heap o pila dinámica.
El siguiente esquema representa un puntero estático llamado punt (almacenado en el data
segment), apuntando a una porción de memoria dinámica (almacenada en el heap), cuya
dirección es 1003.
HEAP
1003 HOLA, MUNDO.
STACK
PUNT 1003
DATA
SEGMENT
CODE
SEGMENT
punt^
Declaración de punteros
En el uso de memoria dinámica aparecen en escena dos variables: una es el puntero -que
contiene la posición de memoria- y la otra es variable apuntada -lo que hay en la posición
de memoria. El tipo puntero se declara mediante un acento circunflejo (^) seguido por el
tipo al cual apunta. Prácticamente todo tipo de dato puede ser apuntado por un puntero,
incluidos los mismos punteros; las únicas excepciones son los tipos relacionados a
archivos, es decir file of y text. Por ejemplo:
En este ejemplo, foo es una variable de tipo puntero capaz de apuntar a cualquier posición
que contenga una variable del tipo tCadena. La cadena que se encuentra en la posición de
memoria a la cual apunta foo es representada por foo^.
Procedimiento New
Sintaxis:
new(foo);
Este procedimiento asigna al puntero foo una posición de memoria, reservándola para que
almacene una variable del tipo especificado en la declaración de foo.
Procedimiento Dispose
Sintaxis:
dispose(foo);
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 176
Libera la posición de memoria ocupada por la variable dinámica a la que apuntaba foo y se
la devuelve al heap. A partir de ahora, dicha posición queda disponible para que pueda ser
utilizada por otros procesos.
Es importante saber diferenciar cuando se asigna una variable puntero y cuando se asigna el
dato al cual apunta. En la asignación de punteros uno está asignando solamente la dirección
de memoria dejando el dato intacto, mientras que cuando uno asigna la variable a la cual
apunta está cambiando el dato en el heap. Todo valor “apuntado” por un puntero puede ser
accedido mediante el nombre del puntero seguido del circunflejo. Supongamos en los
siguientes ejemplos que foo es una variable de tipo puntero que apunta al puntero bar, la
cual también es una variable de tipo puntero, pero que apunta a un string de tamaño 30
cuyo valor es ‘hola mundo’.
En el ejemplo que sigue foo es una variable tipo puntero que apunta a un registro que
contiene un string apellido y un longint padrón.
writeln(foo^.nombre);
writeln(foo^.padron);
Asignación de valores
Sintaxis:
foo^ := 'hola';
Sintaxis:
foo := bar;
HEAP
STACK
BAR 1003
FOO 1003
CODE
SEGMENT
dispose(foo);
foo := bar;
El siguiente gráfico ilustra el caso en que se realiza la asignación, sin antes liberarse la
memoria apuntada por foo.
STACK STACK
foo := bar;
CODE CODE
SEGMENT SEGMENT
Existe en Pascal un valor que puede tomar un puntero de cualquier tipo y que no
corresponde a una posición de memoria. Es una constante predefinida llamada nil y se suele
utilizar en punteros que no apuntan a ningún lado.
Aplicación de punteros
A esta altura del apunte ya se deben preguntar para qué declarar una variable que apunte
hacia cierto lugar, cuando es posible declararla directamente en tiempo de edición. Pero la
razón es que los punteros permiten generar estructuras de datos capaces de variar mientras
el programa se ejecuta (en tiempo de ejecución); estas son compuestas principalmente por
un registro que contiene uno o varios punteros adentro que apuntan a registros del mismo
tipo. Por ejemplo:
Ejemplo 1
program punteros;
uses crt;
begin
new(a);
new(b);
new(c);
a^.info:=1;
b^.izquierda:=a;
c^.derecha:=b;
a^.izquierda:=c;
b^.derecha:=c;
c^.info:=3;
a^.derecha:=b;
b^.info:=5;
c^.izquierda:=a;
a^.izquierda:=b^.derecha;
a^.izquierda^.izquierda:=a;
a^.derecha:=b^.izquierda^.derecha;
c^.derecha:=b^.izquierda^.izquierda;
a:=b;
c^.izquierda^.info:=4;
clrscr;
writeln(a^.info,b^.info,c^.info);{Imprime en pantalla
553}
readkey;
program punteros;
uses crt;
Eso crea dos tipos de datos: Tpuntero y nodo. Tpuntero es un puntero a un elemento de tipo
nodo. El tipo nodo es un registro que contiene un campo info, de tipo integer, y dos
campos, izquierda y derecha, que su vez son punteros a otros elementos.
Un detalle a destacar de esto es que estamos definiendo Tpuntero como un puntero a
“nodo” antes de definir “nodo”. Esta excepción es permitida debido a casos como este,
donde es necesario que un elemento apunte a otro elemento. Por ejemplo, en una lista, cada
elemento tiene que a puntar al siguiente.
begin
new(a);
new(b);
new(c);
Eso crea tres espacios en memoria que pueden contener elementos de tipo nodo, y que a su
vez están apuntados por los punteros a, b y c.
Si a esos espacios los llamamos 1, 2 y 3 respectivamente, entonces podríamos representar
lo que queda en la memoria de esta forma:
a^.info:=1;
Eso carga un 1 en el campo info del elemento apuntado por a. La memoria queda así:
Sigamos:
b^.izquierda:=a;
c^.derecha:=b;
a^.izquierda:=c;
b^.derecha:=c;
c^.info:=3;
a^.derecha:=b;
b^.info:=5;
c^.izquierda:=a;
a^.izquierda:=b^.derecha;
a^.izquierda^.izquierda:=a;
a^.derecha:=b^.izquierda^.derecha;
c^.derecha:=b^.izquierda^.izquierda;
Esto, a la dirección derecha de c, le asigna la dirección izquierda del elemento que está en
la dirección izquierda de b, con lo cual la memoria queda así:
Sigamos:
a:=b;
Notemos que ahora nadie apunta al bloque de dirección 1. Eso hace que dicho bloque de
memoria quede inaccesible. Es decir, ese sector de la memoria seguirá conteniendo la
información, pero ya no se podrá acceder a él. Es decir, queda ocupando un sector de la
memoria, pero no podemos usarlo (para ver cómo hacer para que no queden sectores
inaccesibles, ver la explicación de la instrucción dispose).
Sigamos:
c^.izquierda^.info:=4;
clrscr;
writeln(a^.info,b^.info,c^.info);
readkey;
end.
Eso escribe las info de los bloques apuntados por a, b y c en ese orden.
Entonces se imprime:
553
Se aloca un puntero a entero, luego se le asigna el número 15, y finalmente se libera esa
memoria.
program EjemploPunteros;
uses crt;
begin
clrscr;
new(PInteger);
PInteger^ := 15;
writeln('El numero apuntado por p es = ', PInteger^);
dispose(PInteger);
readkey;
end.
Ejemplo 3
Se aloca un puntero a entero (PInteger), luego se le asigna el número 15. Se tiene otro
puntero (PInteger2) que “apunta” al mismo bloque de memoria que PInteger. Se modifica
lo apuntado por PInteger2 (que es lo mismo que lo apuntado por PInteger). Luego se libera
la memoria.
program EjemploPunteros;
uses crt;
begin
clrscr;
new(PInteger);
PInteger^ := 15;
writeln('El numero apuntado por PInteger es = ',
PInteger^);
PInteger2 := PInteger;
PInteger2^ := 20;
writeln('El numero apuntado por PInteger es = ',
PInteger^);
writeln('El numero apuntado por PInteger2 es = ',
PInteger2^);
Ejemplo 4
program EjemploPunteros;
uses crt;
begin
clrscr;
PChar := NIL;
PChar^ := 'A';
writeln('El numero apuntado por PChar es = ', PChar^);
dispose(PChar);
readkey;
end.
Ejemplo 5
En este ejemplo se muestra un “error típíco” cuando se comienza a trabajar con punteros.
Alocamos memoria para un puntero (PChar), y luego le asignamos NIL, con lo cual
perdemos la referencia a la memoria reservada, y por consiguiente no podremos liberarla
nunca más (referencia “colgada”).
program EjemploPunteros;
uses crt;
readkey;
end.
Ejemplo 6
PString es un puntero a string, y por lo tanto nos permite reservar memoria para una cadena
de caracteres en tiempo de ejecución. Le asignamos una cadena de caracteres (‘HOLA’), y
finalmente lo liberamos.
program EjemploPunteros;
uses crt;
begin
clrscr;
readkey;
end.
Unidades de Biblioteca
UNIDADES DE BIBLIOTECA
A medida que los desarrollos que necesitamos realizar cobran mayor complejidad, se hace
necesario contar con “bibliotecas” de código ya realizado, para usar en las ocasiones que
sea necesario. Aún más, pueden conseguirse soluciones desarrolladas por terceros, a fines
de ser utilizadas en nuestros programas.
Es en auxilio de estas necesidades que aparecen las llamadas “unidades de biblioteca” o
“units”. En el siguiente texto intentaremos contestar tres preguntas fundamentales: ¿Qué
son?, ¿Para qué sirven?, ¿Cómo se hacen?
Definiciones preliminares:
Veamos el camino que sigue un código fuente, que no utiliza unidades de biblioteca, hasta
llegar a ser un archivo ejecutable.
El código escrito en el lenguaje de programación (en nuestro caso Pascal) es tomado por el
compilador quien lo traduce a código objeto. Luego, el linkeador se encarga de traducirlo a
lenguaje de máquina, para la plataforma en la que se esté trabajando.
Veamos que sucede al utilizar unidades de biblioteca con un gráfico similiar al anterior:
usa Unid1
usa Unid2
Unid1
(.TPU)
Unid2
(.TPU)
Puede verse que lo que sucede es que se une (linkea) al programa con las unidades que éste
utiliza, generando el archivo ejecutable (.exe).
Una unidad de biblioteca permite contar con soluciones a necesidades de código listas para
usar, ya testeadas y adaptables al programa que se encuentra en desarrollo. Existen
numerosas ventajas en la utilización de units, que se desciben a continuación.
2. Justamente, al contar con módulos independientes listos para usar, los mismos
pueden ser invocados desde varios programas.
Definiciones preliminares:
Por ejemplo:
Por ejemplo:
Una unidad de biblioteca cuenta con cuatro secciones principales, que conforman su
estructura.
Sección de CABECERA
Sección de INTERFAZ
Sección de IMPLEMENTACION
Sección de INICIALIZACION
Sección de cabecera:
Esta corta sección consta solamente de la palabra reservada unit seguida del nombre que se
da a la unidad.
Por ejemplo:
Unit cuentas;
Aclaración: el nombre del archivo en que se grabará la unit debe coincidir con el de la
misma, es decir que el código fuente de esta unidad deberá estar en un archivo cuentas.pas.
Esto impone una restricción ya que, al estar Pascal basado en DOS, este nombre no podrá
superar los 8 caracteres.
Sección de interfaz:
Esta sección es “visible” desde fuera de la unidad. Esto quiere decir que el usuario de la
unidad puede invocar lo que figura y sólo lo que figura aquí. La sección de interfaz puede
contener constantes, tipos, variables, procedimientos y funciones. Debe destacarse sin
embargo que en el caso de procedimientos y funciones lo que figura es la declaración ya
que, como se verá, luego se definen en la implementación. Es decir, en esta sección se
encuentra todo lo que se exporta hacia el exterior. La sección de interfaz comienza con la
palabra reservada interface y se extiende hasta la sección de implementación.
Interface
Sección de implementación:
Esta sección también contiene constantes, variables, tipos, funciones y procedimientos, con
la salvedad que no son vistos desde afuera. Aquí se encuentran todas las definiciones de los
procedimientos y funciones, de los cuales sólo podran ser invocados aquellos que figuren
en la sección de interfaz. La sección de implementación comienza con la palabra reservada
implementation.
Ejemplo:
implementation
Con esta sección de implementación, el usuario podrá realizar las operaciones de suma,
resta, multiplicación y división entre números enteros. Debe notarse que las sección en rojo
no se encuentra en la interfaz. Por ende el usuario no podrá usar la función esCero, sino que
sólo los procedimientos y funciones de la unidad pueden hacerlo.
Sección de inicialización:
Esta sección es análoga a un programa principal. Comienza con begin y termina con end.
Aunque si está vacía puede ponerse sólo end. El código de esta sección se ejecuta una sola
vez, antes que cualquier instrucción del programa que llama a la unidad.
En nuestro ejemplo será:
end.
Compilación de la unidad
La unidad de biblioteca escrita en código fuente debe ser compilada. Para ello, en nuestro
caso le indicaremos al entorno Turbo Pascal que deseamos compilar a disco, y luego
compilaremos. Se generará un archivo en código objeto, de extensión .tpu (cuentas.tpu) en
el directorio que se haya indicado en las opciones de configuración.
La unidad generada puede ser invocada desde un programa, o bien desde unidades,
externos. Debe indicarse en la cláusula uses que se usará dicha unidad, junto con todas las
otras que se utilizarán. En nuestro caso:
suma(a, b)
Ejemplo:
program hace_cuentas;
var a, b: integer;
division: real;
begin
clrscr;
writeln('Ingrese los valores de los numeros');
readln(a, b);
writeln('Suma: ',suma(a, b));
writeln('Resta: ',resta(a, b));
writeln('Producto: ', multi(a, b));
division:=divi(a,b);
if division<>ERROR then
writeln('Cociente: ', division:7:2)
else
writeln('Error al dividir');
readkey;
end.
Al indicar que se usará una unidad de biblioteca, ésta debe encontrarse en el directorio
actual, en el que se encuentra el archivo turbo.tpl o en el definido para las mismas en las
opciones de configuración, de manera que el programa pueda saber dónde encontrar el
archivo .tpu correspondiente a la unidad.
Esto no fue necesario en nuestro ejemplo, pero sería imperativo si se tuviera una unidad
cuentas1 y otra cuentas2, una para enteros y otra para reales, y ambas tuvieran funciones
suma, resta, etc. De todas formas, aporta claridad incluirlo aunque no sea estrictamente
indispensable.
donde archivo es el nombre del archivo (sin ruta ni extensión) y nombre el nombre de la
unidad.
Referencias circulares
Se ha mencionado que la unidad se compila antes que el programa o unidad que la utiliza.
Por lo tanto, dos unidades no pueden utilizarse entre si, ya que no sería posible determinar
cuál compilar primero.
unit lista;
interface
{PRE: Ninguna.
POST: Crea una nueva lista.}
procedure CreaLista(var Lista: tLista);
begin
Lista := PUNTERO_NULO;
end;
begin
ListaVacia := (Lista = PUNTERO_NULO);
end;
begin
PrimerNodo := Lista;
end;
begin
SiguienteNodo := NodoActual^.Siguiente;
end;
begin
PtrAux := Lista;
while ( (PtrAux^.Siguiente <> NodoActual) and
(PtrAux^.Siguiente<>PUNTERO_NULO) ) do
begin
PtrAux := PtrAux^.Siguiente;
end;
begin
if ( ListaVacia(Lista) ) then
UltimoNodo := PUNTERO_NULO
else
begin
PtrAux := Lista;
while (PtrAux^.Siguiente <> PUNTERO_NULO) do
begin
PtrAux := PtrAux^.Siguiente;
end;
UltimoNodo := PtrAux;
end;
end;
begin
Dato := Nodo^.Dato;
end;
begin
new(PtrAux);
PtrAux^.Dato := Dato;
PtrAux^.Siguiente := Lista;
Lista := PtrAux;
end;
begin
new(PtrAux);
PtrAux^.Dato := Dato;
PtrAux^.Siguiente := NodoActual^.Siguiente;
NodoActual^.Siguiente := PtrAux;
end;
begin
if (Nodo = PrimerNodo(Lista) ) then
{Se quiere eliminar el primer nodo}
Lista := Nodo^.Siguiente
else
begin
PtrAux := AnteriorNodo(Lista,Nodo);
PtrAux^.Siguiente := Nodo^.Siguiente;
end;
dispose(Nodo);
end;
begin
if ( Lista <> PUNTERO_NULO ) then
begin
EliminaLista(Lista^.Siguiente);
dispose(Lista);
Lista := PUNTERO_NULO;
end
end;
Pseudocódigo
RESOLUCIÓN DE PROBLEMAS USANDO ALGORITMOS.
PSEUDOCÓDIGO
Esquema 1
Situación
no
Se pone a prueba la situación aprobada
Situación aprobada
Repetir
Acción
Hasta que
Se cumpla la condición
Esquema 2
Situación Situación no
Aprobada Aprobada
Acción Uno Acción Dos
Si condición
Entonces Acción Uno
Sino Acción Dos
Fin Si
A efectos de formalizar algunos conceptos básicos con los que trabajaremos, enunciaremos
los siguientes axiomas:
& Una acción no primitiva debe ser descompuesta en acciones primitivas, para un
procesador dado.
& Una condición es una afirmación lógica sobre el estado del problema que puede ser
cierta o falsa, en el momento de la observación.
& Un algoritmo es una secuencia ordenada de acciones primitivas que pueden ser
ejecutadas por un procesador y que lleve a la solución de un problema dado.
Como aclaramos al inicio, la resolución de un problema está constituída por cuatro fases.
Dos de ellas son:
Ejemplo:
Tipo “caja” : {caja de cartón, caja de metal, caja de madera}
Esta forma de describir un tipo, no siempre es cómoda. Por ejemplo, los objetos “un
número” y el “9” son ambos de tipo numérico, y es evidente que no podemos describir
el tipo numérico enumerando todos los números posibles. Describimos el tipo numérico
como el conjunto de todos los números que el procesador es capaz de manipular.
75.40 Algoritmos y Programación I Cátedra Lic. Gustavo López Página 208
Por último, otra característica de los objetos es el valor. En cada instante, todo objeto del
ambiente tiene un valor, para algunos objetos, este valor puede cambiar luego de la
ejecución de una acción. Es importante recalcar, en relación con el cambio de valor de un
objeto, que en tanto no se guarde el valor precedente, éste es imposible de recuperar.
Decimos entonces, que cada nuevo valor destruye el anterior.
Así como mencionamos arriba que, para algunos objetos, su valor puede cambiar, para
otros, su valor nunca cambia.
Para resumir, podemos imaginarnos a los objetos de un ambiente, como cajas con tapa en
donde aparece adherida una etiqueta (el nombre), además, la caja tiene una cierta forma
(el tipo) y contiene una información (el valor).
Ejemplo
Se tiene un objeto de nombre NUMERO, de tipo numérico, tal que su valor, es un número
natural. Se quiere desarrollar un algoritmo que determine el producto de los n primeros
números naturales (el factorial de n = n! ).
Así planteado el problema, debemos ahora describir con precisión el ambiente con el cual
deberá trabajar el procesador.
El ambiente consiste del objeto ya descripto de nombre NUMERO. El valor inicial de este
objeto está bien determinado y va a servir para control del cálculo.
Si n es 4, se calculará: 1 * 2 * 3 * 4 = 24
Si n es 6, se calculará: 1 * 2 * 3 * 4 * 5 * 6 = 720; etc.
Dentro del ambiente debemos crear otro objeto cuyo valor final, será el resultado del
cálculo: lo llamaremos FACTORIAL y será también de tipo numérico.
Los número naturales que intervienen en el producto (1, 2, 3, ..., n) también deben ser
definidos dentro del ambiente. Vamos a crear un solo objeto, que llamaremos I, de tipo
1! = 1
2! = 2 * 1 = 2 * 1!
3! = 3 * 2 * 1 = 3 * 2!
.
.
n! = n * (n-1) * ...... * 1 = n * (n-1)!
Algoritmo “Factorial de n”
Repetir
T2 Multiplicar el valor de I por el valor de Factorial y asignar el resultado
como el nuevo valor de Factorial
T3.1 Incrementar en 1 el valor de I y asignar el resultado como el nuevo valor de I
Hasta que (T3.2) el valor de I sea igual al valor de Número aumentado en 1
Las acciones T1.1; T1.2; T2; T3.1 y T3.2 o bien son primitivas, o bien composición
de primitivas. Por ejemplo, la acción:
T2: Multiplicar el valor de I por el valor de FACTORIAL y asignar el resultado como el
nuevo valor de FACTORIAL está formada por las siguientes acciones primitivas:
En la acción:
& Una variable es un objeto cuyo valor puede variar y que posee además los
siguientes atributos:
Un nombre que lo designa
Un tipo que designa el uso de la variable
Programación estructurada
En ciertos lenguajes clásicos (como APL o las formas primitivas de FORTRAN y BASIC)
se emplea con frecuencia la instrucción GOTO de transferencia incondicional, que permite
pasar la ejecución del programa a otra parte del mismo, señalada por una etiqueta. En otros
(como C o Pascal) se utiliza un estilo diferente de programar (la programación
estructurada) en la que la instrucción GOTO está prohibida o, al menos, desaconsejada. Por
ello, la programación estructurada se llama a veces programación sin GOTO.
En la programación estructurada se utilizan sólo tres estructuras de control básicas:
Ø El bloque de instrucciones consecutivas.
Ø La instrucción condicional. En PASCAL existen dos tipos principales:
1. La sentencia SI-ENTONCES (if-then): Si la condición se cumple, se ejecuta la
instrucción 1. En caso contrario, se ejecuta la instrucción 2.
2. La sentencia según:
- Si la variable tiene el valor1, se ejecuta el bloque de instrucciones 1.
- Si tiene el valor2, se ejecuta el bloque de instrucciones 2. Y así sucesivamente.
- Si no tiene ninguno de los valores indicados, se ejecuta el bloque de instrucciones n.
Ø El bucle: En PASCAL existen tres tipos principales:
Programación modular
Nombres de variables
Existen algunas reglas simples que rigen la denominación de las variables, las cuales
difieren ligeramente, dependiendo del lenguaje de programación que se utilice.
En general se exigirá que la variable tenga un nombre que comience con una letra; después
de este primer caracter, los siguientes pueden seleccionarse de un conjunto de caracteres
Diagramas de Flujo
DIAGRAMAS DE FLUJO
Los diagramas de flujo (flowcharts) son esquemas gráficos que representan el flujo de
distintas estructuras de control en un lenguaje de programación. Debe quedar claro que, al
igual que el pseudocódigo, los diagramas de flujo son independientes del lenguaje de
programación que se esté utilizando.
Ante todo es preciso definir alguna nomenclatura que se utilizará para construir tales
diagramas. Las siguientes son algunas formas básicas, aunque existen muchas más:
Entrada de datos
Salida de datos
Decisión
Acción de secuencia
CONDICIÓN
SI NO
K1 K2 ... Kn
NO
CONDICIÓN
SI
SI
CONDICIÓN
NO