Enlace Comandos Turbo Pascal
Enlace Comandos Turbo Pascal
Enlace Comandos Turbo Pascal
txt
http://www.nachocabanes.com/tutors/cupas1.htm
Program nombre_programa;
BEGIN
(* comandos *)
END.
uses
label
const
type
var
procedure
function
La función "uses" la explicaré más adelante, pero básicamente sirve
para incluir
comandos extra a Turbo Pascal, para darle más funcionalidad y ampliar
sus capacidades.
Habitualmente se usa de la siguiente manera:
Program n;
uses CRT;
...
Label 1, 2, 3, a, b, c, hola;
Const PI = 3.141593;
LetraA = 'a';
Se usa así:
Por ejemplo:
2. COMANDOS BÁSICOS
OK, ya sabemos como hacer las declaraciones en Pascal, pero ¿de qué
sirve si no sabemos
como usarlas? Bueno, para eso están los comandos en Pascal.
Program mensaje;
(* no vamos a usar variables, así que no hay declaraciones *)
BEGIN
Write ('Yo me llamo Z');
Writeln (4);
Write ('2+2=');
Writeln (2+2);
END.
Eso entrega:
Yo me llamo Z4
2+2=4
Fíjense que el texto va entre comillas simples (''), mientras que los
números no. Las
comillas hacen que Pascal considere todo eso como una palabra y no
como un número.
También puede ir, como se ve, una operación matemática en Write y
Writeln. P. ej.:
Writeln (3*(4+5)-1);
Resultado: 26
Program Multiplicacion;
Var
a,b : Real; (* vamos a usar las variables a y b, así que las
declaramos *)
BEGIN
Readln (a);
Readln (b);
Writeln (a*b);
END.
Si queremos, podemos en el intertanto asignar el resultado a otra
variable para guardarlo,
si necesitamos usarlo otra vez. Esto se hace con el operador
Asignación ( := ). Y no, no
es una carita. La asignación se hace de la siguiente manera:
NombreVariable := Valor
Program Promedio;
Var
a,b,prom : Real;
BEGIN
Readln (a,b);
prom := a+b;
Writeln (prom);
Writeln (prom/2);
END.
Readln (a);
Readln (b);
Readln (c);
o, simplemente:
3.14000000000000E+00
Writeln (variable:1:n);
3. TIPOS DE VARIABLES
Recuerda que NO PUEDE HABER DOS VARIABLES CON EL MISMO NOMBRE. Repito:
NO PUEDE HABER DOS VARIABLES CON EL MISMO NOMBRE
NO PUEDE HABER DOS VARIABLES CON EL MISMO NOMBRE
NO PUEDE HABER DOS VARIABLES CON EL MISMO NOMBRE
Var
nombrearray: Array [a..b] of tipo;
Type
Vector = Array [a..b] of tipo;
Var
nombrearray: Vector;
Type
Vector = Array [1..5] of Integer;
Writeln (X[3]);
y para modificarlo:
X[3] := 4;
Type
Matriz = Array [a..b][x..y] of tipo;
...
y para ocuparlas:
CualquierMatriz[3][5] := algo;
Type
Matriz = Array [1..10]['a'..'z'] of Integer;
lo que nos dejaría algo parecido a Excel (con una letra y un número)
Aqui vemos algo nuevo ("Type"). Este comando declara un tipo especial
de variable.
Por ejemplo, los arrays. Luego, en la sección Var, puedes simplemente
declarar el nombre
del tipo especial que creaste. Otro ejemplo sería:
Type
CadenaCorta = String[16];
Mes = 1..12;
Var
Palabra: CadenaCorta;
Actual: Mes;
Type
Estaciones = (primavera, verano, otoño, invierno);
Este ejemplo lo saqué de lapolitecnica.net. Para el computador,
Primavera será como el 0, Verano
como el 1, etc.
4. CONDICIONES
¿Qué pasa si quieres que el programa decida algo por su cuenta? Para
eso usamos las condiciones,
que son consultas o comparaciones que hace el computador, y devuelven
un valor verdadero o falso.
Hay dos tipos de condiciones: IF y CASE OF.
A. Condición IF
If (condición) then
begin
(* instrucciones *)
end;
Program Mayorque5;
var
a : byte;
BEGIN
Write ('Escriba un número: ');
Readln (a);
If (a>5) then
Writeln ('Es mayor que 5');
END.
If (condición) then
begin
(* esto solo se hará si la condición es verdadera *)
end
Else
begin
(* esto solo se hará si la condición es falsa *)
end;
Program Mayoromenorque5;
Var
a : byte;
BEGIN
Write ('Escriba un número: ');
Readln (a);
If (a>5) then
Writeln ('Es mayor que 5')
Else
Writeln ('Es igual o menor que 5');
END.
B. Condición CASE OF
(* estado civil *)
If (Codigo=1) then
Estado:='Soltero';
If (Codigo=2) then
Estado:='Casado';
If (Codigo=3) then
Estado:='Viudo';
If (Codigo=4) then
Estado:='Divorciado';
...
Si tenemos que ver por 30, 40 o 50 valores, no creo que alguien tenga
la paciencia como para
escribir IF, THEN, etc., 30, 40 o 50 veces. En vez de eso es mejor
usar Case Of de la siguiente
forma:
Case (variable) Of
(valor1): begin
(* instrucciones *)
end;
(valor2): begin
(* instrucciones *)
end;
...
(valorn): begin
(* instrucciones *)
end
End;
Case (variable) Of
(valor1): begin
(* instrucciones *)
end;
(valor2): begin
(* instrucciones *)
end;
...
Else
begin
(* instrucciones *)
end
End;
Nótese que el penúltimo End no lleva punto y coma. Esto ocurre porque
hay dos instrucciones
End juntas.
Viendo esto, podemos cambiar el ejemplo del estado civil que vimos
anteriormente por:
Case Codigo of
1: Estado:='Soltero';
2: Estado:='Casado';
3: Estado:='Viudo';
4: Estado:='Divorciado';
End;
Case n of
1,2,3: Writeln ('Menor que 4');
4: Writeln ('Igual que 4');
Else
Writeln ('Mayor que 4');
End;
5. CICLOS
Writeln (5*1);
Writeln (5*2);
Writeln (5*3);
Writeln (5*4);
Writeln (5*5);
Writeln (5*6);
Writeln (5*7);
Writeln (5*8);
Writeln (5*9);
Writeln (5*10);
Writeln (5*11);
Writeln (5*12);
...
Para evitar eso se usan los ciclos, que repiten una instrucción o
grupo de instrucciones
tantas veces como se necesita. Los 3 ciclos básicos son los ciclos
While, Repeat y For.
Comencemos por lo más fácil:
A. Ciclo FOR
Esto hará que se repitan las instrucciones hasta que la variable tenga
un valor igual o mayor
a valorfinal. Irá aumentando de 1 en 1 o disminuyendo de 1 en 1
dependiendo de si se usa To o
DownTo, a partir del valor valorinicial. Por ejemplo, las
instrucciones Writeln que vimos
anteriormente podrían ser resumidas en:
For i:=1 to 12 do
Writeln (5*i);
(Recuerda que si hay una sola instrucción podemos omitir begin y end)
Program factorial;
Label 1, 2;
Var N, i, f : LongInt;
resp : Byte;
BEGIN
1:
Write ('Ingrese el número deseado');
Readln(N);
f := 1;
For i := 1 to N do
f := f * i;
Writeln (N,'! = ',f);
2:
Write ('¿Desea ejecutar nuevamente el programa? [1=Sí/2=No]');
Readln (resp);
If (resp<1) or (resp>2) then
Goto 2;
If (resp=1) then
Goto 1;
END.
For i := 1 to N do
por
For i := N DownTo 1 do
While (condición) Do
begin
(* instrucciones *)
end;
Repeat
(* instrucciones *)
Until (condición);
Program Adivina;
Uses CRT;
Var
Azar, Resp, Intentos : Integer;
BEGIN
Randomize;
Writeln ('Yo voy a pensar un número entre cero y cien.');
Writeln ('Debes adivinarlo en el menor número de intentos posible. Te
daré pistas.');
Azar := Random (200);
Repeat
Readln (Resp);
If (Resp<Azar) then
Writeln ('Um... no. Más alto.');
If (Resp>Azar) then
Writeln ('Demasiado.');
Intentos := Intentos + 1;
Until (Resp=Azar);
Writeln ('¡Lo lograste!');
Writeln ('Te costó ',Intentos,' intentos');
Writeln ('Presiona cualquier tecla');
Readkey;
END.
Un ejemplo de ciclo While sería simular un bucle for, como por ejemplo
mostrar la tabla del 4.
Program Whileofor;
Var
K : Byte;
BEGIN
K:=1; (* valor inicial *)
While (K<=10) do
Begin
Writeln ('4*',K,'=',4*K);
K:=K+1;
End;
END.
Misceláneos:
8. PROCEDIMIENTOS Y FUNCIONES
Program programa;
...
Procedure nombre(argumento1: tipo; argumento2: tipo...);
begin
(* instrucciones *)
end;
BEGIN
...
nombre(argumentos);
...
END.
Program factorial;
Label 1, 2;
Var N, i, f : LongInt;
resp : Byte;
BEGIN
1:
Write ('Ingrese el número deseado');
Readln(N);
f := 1;
For i := 1 to N do
f := f * i;
Writeln (N,'! = ',f);
2:
Write ('¿Desea ejecutar nuevamente el programa? [1=Sí/2=No]');
Readln (resp);
If (resp<1) or (resp>2) then
Goto 2;
If (resp=1) then
Goto 1;
END.
/n\ n!
| | = ----------
\k/ k!(n-k)!
Program CoeficienteBinomial;
Label 1, 2;
BEGIN
1:
Prompt('Ingrese N:', n);
Prompt('Ingrese K:', k);
n_sobre_k := Factorial(n) DIV (Factorial(k) * Factorial(n-k));
Writeln ('El valor de N sobre K es: ', n_sobre_k);
2:
Prompt('¿Desea ejecutar nuevamente el programa? [1=Sí/2=No]', resp);
If (resp<1) or (resp>2) then
Goto 2;
If (resp=1) then
Goto 1;
END.
Eso es todo.
FIN
Tema 0. Introducción.
Hay distintos lenguajes que nos permiten dar instrucciones a un ordenador. El
más directo es el propio del ordenador, llamado "lenguaje de máquina" o
"código máquina", formado por secuencias de ceros y unos.
Este lenguaje es muy poco intuitivo para nosotros, y difícil de usar. Por ello se
recurre a otros lenguajes más avanzados, más cercanos al propio lenguaje
humano (lenguajes de alto nivel), y es entonces el mismo ordenador el que se
encarga de convertirlo a algo que pueda manejar directamente.
Con poco más que lo visto hasta ahora ya se podría escribir un pequeño
programa que hiciera aparecer el mensaje "Hola" en la pantalla:
program Saludo;
begin
write('Hola');
end.
Las palabras "begin" y "end" marcan el principio y el final del programa, que
esta vez sólo se compone de una línea. Nótese que, como se dijo, el último
"end" debe terminar con un punto.
Cuando se trata de un texto que queremos que aparezca "tal cual", éste se
encierra entre comillas (una comilla simple para el principio y otra para el
final, como aparece en el ejemplo).
El punto y coma que sigue a la orden "write" no es necesario (va justo antes
de un "end"), pero tampoco es un error; así que podemos dejarlo, por si
después añadimos otra orden entre "write" y "end".
En el primer ejemplo que vimos, puede que no nos interese escribir siempre el
mensaje "Hola", sino uno más personalizado según quien ejecute el
programa. Podríamos preguntar su nombre al usuario, guardarlo en una
variable y después escribirlo a continuación de la palabra "Hola", con lo que
el programa quedaría
program Saludo2;
var
nombre: string;
begin
writeln('Introduce tu nombre, por favor');
readln(nombre);
write('Hola ',nombre);
end.
Los nombres de las variables siguen las reglas que ya habíamos mencionado
para los identificadores en general.
Integer. Es un número entero con signo, que puede valer desde -32768
hasta 32767. Ocupa 2 bytes de memoria.
program Ejemplo_de_registro;
var
dato: record
nombre: string[20];
edad: byte;
end;
begin
dato.nombre:='José Ignacio';
dato.edad:=23;
write('El nombre es ', dato.nombre );
write(' y la edad ', dato.edad, ' años.');
end.
var
dato: record
nombre: string[20];
edad: byte;
end;
begin
with dato do
begin
nombre:='José Ignacio';
edad:=23;
write('El nombre es ',nombre);
write(' y la edad ',edad,' años.');
end;
end.
Ejemplos.
var
numero: integer;
begin
numero := 25;
writeln('La variable vale ', numero);
numero := 50;
writeln('Ahora vale ', numero);
numero := numero + 10;
writeln('Y ahora ', numero);
writeln('Introduce ahora tú el valor');
readln( numero );
writeln('Finalmente, ahora vale ', numero);
end.
var
numero1, numero2, suma: integer;
begin
writeln('Introduce el primer número');
readln( numero1 );
writeln('Introduce el segundo número');
readln( numero2 );
suma := numero1 + numero2;
writeln('La suma de los dos números es: ', suma);
end.
Ejemplo 3: Media de los elementos de un vector.
var
vector: array [1..5] of real;
suma, media: real;
begin
writeln('Media de un vector con 5 elementos.');
writeln;
writeln('Introduce el primer elemento');
readln(vector[1]);
writeln('Introduce el segundo elemento');
readln(vector[2]);
writeln('Introduce el tercer elemento');
readln(vector[3]);
writeln('Introduce el cuarto elemento');
readln(vector[4]);
writeln('Introduce el quinto elemento');
readln(vector[5]);
suma := vector[1] + vector[2] + vector[3] + vector[4] + vector[5];
media := suma / 5;
writeln('La media de sus elementos es: ', media);
end.
Igual ocurre con los números: si es más grande que la anchura indicada, no se
"parte", sino que se escribe completo. Si es menor, se rellena con espacios por
la izquierda. Los decimales sí que se redondean al número de posiciones
indicado:
var num: real;
begin
num := 1234567.89;
writeln(num);
(* La línea anterior lo escribe con el formato por defecto:
exponencial *)
writeln(num:20:3); (* Con tres decimales *)
writeln(num:7:2); (* Con dos decimales *)
writeln(num:4:1); (* Con un decimal *)
writeln(num:3:0); (* Sin decimales *)
writeln(num:5); (* ¿Qué hará ahora? *)
end.
La salida por pantalla de este programa sería:
1.2345678900E+06
1234567.890
.ej1234567.89
.ej1234567.9
.ej1234568
1.2E+06
Más adelante, veremos que existen formas mucho más versátiles y cómodas
de leer datos a través del teclado, en el mismo tema en el que veamos cómo se
maneja la pantalla en modo texto desde Pascal...
var
e1, e2: integer; (* Números enteros *)
r1, r2, r3: real; (* Números reales *)
begin
e1:=17;
e2:=5;
r1:=1;
r2:=3.2;
writeln('Empezamos...');
r3:=r1+r2;
writeln('La suma de r1 y r2 es :', r3);
writeln(' o también ', r1+r2 :5:2); (* Indicando el formato
*)
writeln('El producto de r1 y r2 es :', r1 * r2);
writeln('El valor de r1 dividido entre r2 es :', r1 / r2);
writeln('La diferencia de e2 y e1 es : ', e2 - e1);
writeln('La división de e1 entre e2 : ', e1 / e2);
writeln(' Su división entera : ', e1 div e2);
writeln(' Y el resto de la división : ', e1 mod e2);
writeln('El opuesto de e2 es :', -e2);
end.
begin
texto1 := 'Hola ';
texto2 := '¿Cómo estás?';
texto3 := texto1 + texto2;
writeln(texto3); (* Escribirá "Hola ¿Cómo estás?" *)
end.
Operadores lógicos
Vimos de pasada que en el tema que había unos tipos de datos llamados
"boolean", y que podían valer TRUE (verdadero) o FALSE (falso). En la
próxima lección veremos cómo hacer comparaciones del estilo de "si A es
mayor que B y B es mayor que C", y empezaremos a utilizar variables de este
tipo, pero vamos a mencionar ya eso del "y".
Igual que antes, algunos de ellos (>=, <=, in) los utilizaremos también en los
conjuntos, más adelante.
Los operadores "and", "or" y "not", junto con otros, se pueden utilizar también
para operaciones entre bits de números enteros. Lo comento de pasada para
no liar a los que empiezan. De momento, ahí va resumido y sin más
comentarios. Quien quiera saber más, lo podrá ver en las ampliaciones al
curso básico.
Operador¦ Operación
---------+----------------------------
not ¦ Negación
and ¦ Producto lógico
or ¦ Suma lógica
xor ¦ Suma exclusiva
shl ¦ Desplazamiento hacia la izquierda
shr ¦ Desplazamiento a la derecha
Como última cosa en este tema, la precedencia de los operadores:
Operadores ¦Precedencia ¦ Categoría
-------------+------------+---------------------
@ not ¦ Mayor (1ª) ¦ Operadores unarios
* / div mod ¦ 2ª ¦ Operadores de multiplicación
and shl shr ¦ ¦
+ - or xor ¦ 3ª ¦ Operadores de suma
= <> < > ¦ Menor (4ª) ¦ Operadores relacionales
<= >= in ¦ ¦
Tema 5: Condiciones.
Vamos a ver cómo podemos evaluar condiciones desde Pascal. La primera
construcción que trataremos es if ... then. En español sería "si ... entonces",
que expresa bastante bien lo que podemos hacer con ella. El formato es "if
condición then sentencia". Veamos un ejemplo breve antes de seguir:
program if1;
begin
writeln('Escriba un número');
readln(numero);
if numero>0 then writeln('El número es positivo');
end.
La "condición" debe ser una expresión que devuelva un valor del tipo
"boolean" (verdadero/falso). La sentencia se ejecutará si ese valor es "cierto"
(TRUE). Este valor puede ser tanto el resultado de una comparación como la
anterior, como una propia variable booleana. Así, una forma más "rebuscada"
(pero que a veces resultará más cómoda y más legible) de hacer lo anterior
sería:
program if2;
var
numero: integer;
esPositivo: boolean;
begin
writeln('Escriba un número');
readln(numero);
esPositivo := (numero>0);
if esPositivo then writeln('El número es positivo');
end.
Cuando veamos en el próximo tema las órdenes para controlar el flujo del
programa, seguiremos descubriendo aplicaciones de las variables booleanas,
que muchas veces uno considera "poco útiles" cuando está aprendiendo.
var
numero: integer;
begin
writeln('Escriba un número');
readln(numero);
if numero<0 then
begin
writeln('El número es negativo. Pulse INTRO para seguir.');
readln
end;
end.
var
numero: integer;
begin
writeln('Escriba un número');
readln(numero);
if numero<0 then
writeln('El número es negativo.')
else
writeln('El número es positivo o cero.')
end.
Un detalle importante que conviene tener en cuenta es que antes del "else" no
debe haber un punto y coma, porque eso indicaría el final de la sentencia
"if...", y el compilador nos avisaría con un error.
var
numero: integer;
begin
writeln('Escriba un número');
readln(numero);
if numero<0 then
writeln('El número es negativo.')
else if numero>0 then
writeln('El número es positivo.')
else
writeln('El número es cero.')
end.
Si se deben cumplir varias condiciones a la vez, podemos enlazarlas con
"and" (y). Si se pueden cumplir varias, usaremos "or" (o). Para negar, "not"
(no):
if ( opcion = 1 ) and ( terminado = true ) then [...]
if ( opcion = 3 ) or ( teclaPulsada = true ) then [...]
if not ( preparado ) then [...]
if ( opcion = 2 ) and not ( nivelDeAcceso < 40 ) then [...]
begin
WriteLn('Escriba un letra');
ReadLn(letra);
case letra of
' ': WriteLn('Un espacio');
'A'..'Z', 'a'..'z': WriteLn('Una letra');
'0'..'9': WriteLn('Un dígito');
'+', '-', '*', '/': WriteLn('Un operador');
else
WriteLn('No es espacio, ni letra, ni dígito, ni operador');
end;
end.
Tema 6: Bucles.
Vamos a ver cómo podemos crear bucles, es decir, partes del programa que se
repitan un cierto número de veces.
Según cómo queramos que se controle ese bucle, tenemos tres posibilidades,
que vamos a empezar a ver ya por encima:
for..to: La orden se repite desde que una variable tiene un valor inicial
hasta que alcanza otro valor final (un cierto NUMERO de veces).
while..do: Repite una sentencia MIENTRAS que sea cierta la
condición que indicamos.
repeat..until: Repite un grupo de sentencias HASTA que se dé una
condición.
For.
El formato de "for" es
for variable := ValorInicial to ValorFinal do
Sentencia;
begin
for contador := 1 to 10 do
writeln( contador );
end.
begin
for tabla := 1 to 5 do
for numero := 1 to 10 do
writeln( tabla, 'por ', numero ,'es', tabla * numero );
end.
Hasta ahora hemos visto sólo casos en los que después de "for" había un única
sentencia. ¿Qué ocurre si queremos repetir más de una orden? Basta
encerrarlas entre "begin" y "end" para convertirlas en una sentencia
compuesta.
Así, vamos a mejorar el ejemplo anterior haciendo que deje una línea en
blanco entre tabla y tabla:
var
tabla, numero: integer;
begin
for tabla := 1 to 5 do
begin
for numero := 1 to 10 do
writeln( tabla, 'por ', numero ,'es', tabla * numero );
writeln; (* Línea en blanco *)
end;
end.
begin
for letra := 'a' to 'z' do
write( letra );
end.
Como último comentario: con el bucle "for", tal y como lo hemos visto, sólo
se puede contar en forma creciente y de uno en uno. Para contar de forma
decreciente, se usa "downto" en vez de "to".
Para contar de dos en dos (por ejemplo), hay usar "trucos": multiplicar por dos
o sumar uno dentro del cuerpo del bucle, etc... Eso sí, sin modificar la variable
que controla el bucle (usar cosas como "write(x*2)" en vez de "x := x*2", que
pueden dar problemas en algunos compiladores).
Como ejercicios propuestos:
While.
Un ejemplo que nos diga la longitud de todas las frases que queramos es:
var
frase: string;
begin
writeln('Escribe frases, y deja una línea en blanco para
salir');
write( '¿Primera frase?' );
readln( frase );
while frase <> '' do
begin
writeln( 'Su longitud es ', length(frase) );
write( '¿Siguiente frase?' );
readln( frase )
end
end.
Repeat .. Until.
Como último detalle, de menor importancia, no hace falta terminar con punto
y coma la sentencia que va justo antes de "until", al igual que ocurre con
"end".
var
ClaveCorrecta, Intento: String;
begin
ClaveCorrecta := 'PascalForever';
repeat
WriteLn( 'Introduce la clave de acceso...' );
ReadLn( Intento )
until Intento = ClaveCorrecta
(* Aquí iría el resto del programa *)
end.
Antes que nada, hay que puntualizar una cosa: el que así se resuelva de una
forma no quiere decir que no se pueda hacer de otras. Ni siquiera que la mía
sea la mejor, porque trataré de adaptarlos al nivel que se supone que tenemos.
var i: integer;
begin
for i := 1 to 8 do
writeln( i*2 );
end.
var i: integer;
begin
for i := 6 downto 1 do
writeln( i );
end.
var i: integer;
begin
for i := 1 to 10 do
writeln( i*2 +1 );
end.
var i: integer;
begin
for i := 6 downto 0 do
writeln( i*2 );
end.
Saltamos la parte que declara las variables y que pide los datos. La parte de la
multiplicación sería, para matrices cuadradas de 10 por 10, por ejemplo:
for i := 1 to 10 do
for j := 1 to 10 do
c[i,j] := 0; (* limpia la matriz destino *)
for i :=1 to 10 do
for j := 1 to 10 do
for k := 1 to 10 do
c[i,j] := c[i,j] + a[k,j] * b[i,k];
var
ClaveCorrecta, Intento: String;
begin
ClaveCorrecta := 'PascalForever';
repeat
WriteLn( 'Introduce la clave de acceso...' );
ReadLn( Intento )
if Intento <> ClaveCorrecta then
writeln( ' Esa no es la clave correcta! ');
until Intento = ClaveCorrecta
(* Aquí iría el resto del programa *)
end.
2.- Mejorar más todavía para que sólo haya tres intentos.
program ClaveDeAcceso3;
var
ClaveCorrecta, Intento: String;
NumIntento: integer; (* número de intento *)
begin
ClaveCorrecta := 'PascalForever';
NumIntento := 0; (* aún no hemos probado *)
repeat
NumIntento := NumIntento + 1; (* siguiente intento *)
WriteLn( 'Introduce la clave de acceso...' );
ReadLn( Intento )
if Intento <> ClaveCorrecta then
begin
writeln( ' Esa no es la clave correcta! ');
if NumIntentos = 3 then exit (* sale si es el 3º *)
end
until Intento = ClaveCorrecta
(* Aquí iría el resto del programa *)
end.
Sólo ponemos una de ellas, porque las demás son muy similares.
program ClaveDeAcceso4; (* equivale a
ClaveDeAcceso2*)
var
ClaveCorrecta, Intento: String;
begin
ClaveCorrecta := 'PascalForever';
Intento := ''; (* cadena vacía *)
while Intento <> ClaveCorrecta do (* mientras no acertemos *)
begin
WriteLn( 'Introduce la clave de acceso...' );
ReadLn( Intento )
if Intento <> ClaveCorrecta then
writeln( ' Esa no es la clave correcta! ');
end; (* fin del "while" *)
(* Aquí iría el resto del programa *)
end.
Estas constantes se manejan igual que variables como las que habíamos visto
hasta hora, sólo que no se puede cambiar su valor. Así, es valido hacer
Writeln(MiNombre);
if Longitud > LongitudMaxima then ...
OtraVariable := MiNombre;
LongCircunf := 2 * PI * r;
Supongamos que estamos haciendo nuestra agenda en Pascal (ya falta menos
para que sea verdad), y estamos tan orgullosos de ella que queremos que en
cada pantalla de cada parte del programa aparezca nuestro nombre, el del
programa y la versión actual. Si lo escribimos de nuevas cada vez, además de
perder tiempo tecleando más, corremos el riesgo de que un día queramos
cambiar el nombre (ya no se llamará "Agenda" sino "SuperAgenda") pero lo
hagamos en unas partes sí y en otras no, etc., y el resultado tan maravilloso
quede estropeado por esos "detalles".
La solución será definir todo ese tipo de datos como constantes al principio
del programa, de modo que con un vistazo a esta zona podemos hacer cambios
globales:
const
Nombre = 'Nacho';
Prog = 'SuperAgenda en Pascal';
Versión = 1.95;
LongNombre = 40;
LongTelef = 9;
LongDirec = 60;
...
Las declaraciones de las constantes se hacen antes del cuerpo del programa
principal, y generalmente antes de las declaraciones de variables:
program MiniAgenda;
const
NumFichas = 50;
var
Datos: array[ 1..NumFichas ] of string;
begin
...
begin
intentos := 3;
...
begin
...
Definición de tipos.
indica que vamos a usar una variable que se va a llamar PrimerNumero y que
almacenará valores de tipo entero. Si queremos definir una de las fichas de lo
que será nuestra agenda, también haríamos:
var ficha: record
nombre: string;
direccion: string;
edad: integer;
observaciones: string
end;
var
ficha1: record
nombre: string;
direccion: string;
edad: integer;
observaciones: string
end;
ficha2: record
nombre: string;
direccion: string;
edad: integer;
observaciones: string
end;
begin
ficha1.nombre := 'Pepe';
ficha1.direccion := 'Su casa';
ficha1.edad := 65;
ficha1.observaciones := 'El mayor de mis amigos...';
ficha2 := ficha1;
writeln( ficha2.nombre);
end.
Veamos qué haría este programa: define dos variables que van a guardar la
misma clase de datos. Da valores a cada uno de los datos que almacenará una
de ellas. Después hacemos que la segunda valga lo mismo que la primera, e
imprimimos el nombre de la segunda. Aparecerá escrito "Pepe" en la
pantalla...
begin
...
Si las definimos a la vez, SI QUE SON DEL MISMO TIPO. Pero surge un
problema que se entenderá mejor más adelante, cuando empecemos a crear
funciones y procedimientos. ¿Qué ocurre si queremos usar en alguna parte del
programa otras variables que también sean de ese tipo? ¿Las definimos
también a la vez? En muchas ocasiones no será posible.
Así que tiene que haber una forma de indicar que todo eso que sigue a la
palabra "record" es un tipo al que nosotros queremos acceder con la misma
comodidad que si fuese "integer" o "boolean", queremos definir un tipo, no
simplemente declararlo, como estábamos haciendo.
Pues es sencillo:
type NombreDeTipo = DeclaracionDeTipo;
o en nuestro caso
type TipoFicha = record
nombre: string;
direccion: string;
edad: integer;
observaciones: string
end;
Ahora sí que podremos asignar valores entre variables que hayamos definido
en distintas partes del programa, podremos usar esos tipos para crear ficheros
(que también veremos más adelante), etc.
Pero vamos a ver un programa que use esta función, para que quede un poco
más claro:
program PruebaDePotencia;
var
numero1, numero2: integer; (* Variable globales
*)
function potencia(a,b: integer): integer; (* Definimos la función
*)
var
i: integer; (* Locales: para bucles *)
temporal: integer; (* y para el valor temporal *)
begin
temporal := 1; (* incialización *)
for i := 1 to b do
temporal := temporal * a; (* hacemos "b" veces "a*a" *)
potencia := temporal; (* y finalmente damos el valor
*)
end;
Adaptar la función "potencia" que hemos visto para que trabaje con
números reales, y permita cosas como 3.2 ^ 1.7
Hacer una función que halle la raíz cúbica del número que se le
indique.
Definir las funciones suma y producto de tres números y hacer un
programa que haga una operación u otra según le indiquemos (con
"case", etc).
Un programa que halle la letra (NIF) que corresponde a un cierto DNI.
Parámetros.
Ya habíamos visto, sin entrar en detalles, qué es eso de los parámetros: una
serie de datos extra que indicábamos entre paréntesis en la cabecera de un
procedimiento o función.
Es algo que estamos usando, sin saberlo, desde el primer tema, cuando
empezamos a usar "WriteLn":
writeln( 'Hola' );
begin
dato := 2;
writeln( dato );
modifica( dato );
writeln( dato );
end.
Vamos a ir siguiendo cada instrucción:
begin
dato := 2;
writeln( dato );
modifica( dato );
writeln( dato );
end.
Esta vez la última línea del programa sí que escribe un 3 y no un 2, porque
hemos permitido que los cambio hechos a la variable salgan del
procedimiento. Esto espasar un parámetro por referencia.
begin
dato := 2;
writeln( dato );
modifica( dato );
writeln( dato );
end.
Recursividad.
Luego podemos escribir cada factorial en función del factorial del siguiente
número:
n! = n · (n-1)!
Acabamos de dar la definición recursiva del factorial. Ahora sólo queda ver
cómo se haría eso programando:
program PruebaDeFactorial;
begin
writeln( 'Introduce un número entero (no muy grande) ' );
readln(numero);
writeln( 'Su factorial es ', factorial(numero) );
end.
Soluciones.
Pues aquí van unas soluciones (insisto en que no tienen por qué ser las únicas
ni las mejores) a los ejercicios propuestos en el tema 8:
1.- Adaptar la función "potencia" que hemos visto para que trabaje con
números reales, y permita cosas como 3.2 ^ 1.7
No basta con cambiar los tipos de las variables, poniendo "real" en vez de
"integer". Si hacemos esto, ni siquiera podremos compilar el programa,
porque una variable de tipo "real" no se puede usar para controlar un bucle.
var
num1, num2: real; (* Definición de variables *)
opcion: char;
begin
write( 'Introduzca el primer número: ');
readln( num1 );
write( 'Introduzca el segundo número: ');
readln( num2 );
writeln( '¿Qué operación desea realizar?' );
writeln( 's = Suma p = Producto' );
readln( opcion );
case opcion of
's': writeln( 'Su suma es ', suma(num1,num2) );
'p': writeln( 'Su producto es ', producto(num1,num2) );
else
writeln( 'Operación desconocida!' );
end; (* Fin del case *)
end. (* y del programa *)
4.- Un programa que halle la letra que corresponde a un cierto DNI.
0=T 1=R 2=W 3=A 4=G 5=M 6=Y 7=F 8=P 9=D 10=X 11=B 12=N 13=J
Así, un programa que halle la letra (implementando esta parte como función,
para que quede "más completo") puede ser simplemente:
program Nif; (* Letra del NIF. Nacho Cabanes, Jun. 92
*)
var numero:longint;
begin
writeln('¿Cual es el DNI cuyo NIF quiere hallar?');
readln(numero);
writeln('La letra es ', LetraNif( Numero ) ,'.');
end.
begin
dato := 2;
writeln( dato );
modifica( dato );
writeln( dato );
end.
Sólo ha cambiado el nombre de la variable global, que ahora se llama "dato",
igual que la local. Pero de cualquier modo, ambas variables SON
DISTINTAS, de modo que el programa sigue funcionando igual que antes,
cuando los nombres de las variables eran distintos.
Este es un caso con poco sentido en la práctica, pero que se puede resolver
simplemente recordando que un producto no son más que varias sumas:
function producto( a,b : integer) : integer;
var temporal: integer;
begin
if b = 1 then
producto := a
else
producto := a + producto (a, b-1 );
end;
begin
write( 'Introduzca el primer número: ');
readln( num1 );
write( 'Introduzca el segundo número: ');
readln( num2 );
writeln( 'Su producto es ', producto(num1,num2) );
end.
Esta vez vamos a ampliar con otros tipos de datos que podemos encontrar en
Turbo Pascal (aunque puede que no en otras versiones del lenguaje Pascal).
Enteros.
Correspondencia byte-char.
Reales del 8087.
Tipos enumerados.
Más detalles sobre Strings.
Registros variantes.
Conjuntos.
Vamos allá:
begin
for bucle := 32 to 255 do
write( chr(bucle) );
end.
Declaramos las variables igual que hacíamos con cualquier otro tipo:
var dia: DiasSemana
Los tipos enumerados también son tipos ordinales, por lo que podemos usar
pred, succ y ord con ellos. Así, el en ejemplo anterior
pred(Martes) = Lunes, succ(Martes) = Miercoles, ord(Martes) = 1
begin
writeln( 'Introduce una línea de texto...' );
readln( linea );
for pos := 1 to ord(linea[0]) do
writeln(' La letra número ', pos,' es una ', linea[pos]);
end.
Comentarios:
También habíamos visto ya los registros (records), pero con unos campos
fijos. No tiene por qué ser necesariamente así. Tenemos a nuestra disposición
losregistros variantes, en los que con un "case" podemos elegir unos campos
u otros. La mejor forma de entenderlos es con un ejemplo.
program RegistrosVariantes;
type
TipoDato = (Num, Fech, Str);
Fecha = record
D, M, A: Byte;
end;
Ficha = record
Nombre: string[20]; (* Campo fijo *)
case Tipo: TipoDato of (* Campos variantes *)
Num: (N: real); (* Si es un número: campo N
*)
Fech: (F: Fecha); (* Si es fecha: campo F *)
Str: (S: string); (* Si es string: campo S *)
end;
var
UnDato: Ficha;
begin
UnDato.Nombre := 'Nacho'; (* Campo normal de un record *)
UnDato.Tipo := Num; (* Vamos a almacenar un número *)
UnDato.N := 3.5; (* que vale 3.5 *)
begin
LetrasValidas = ['a'..'z', 'A'..'z', '0'..'9', 'ñ', 'Ñ']
Fiesta = [ Sabado, Domingo ]
end.
uses crt;
var
[...]
Comentarios generales:
X es la columna, de 1 a 80.
Y es la fila, de 1 a 25.
El cursor es el cuadrado o raya parpadeante que nos indica donde
seguiríamos escribiendo.
Los colores están definidos como constantes con el nombre en inglés.
Así Black = 0, de modo que TextColor ( Black ) es lo mismo
que TextColor(0).
La pantalla se puede manejar también accediendo directamente a la
memoria de vídeo, pero eso es bastante más complicado.
uses crt;
var
bucle : byte;
tecla : char;
begin
ClrScr; { Borra la pantalla }
TextColor( Yellow ); { Color amarillo }
TextBackground( Red ); { Fondo rojo }
GotoXY( 40, 13 ); { Vamos al centro de la
pantalla }
Write(' Hola '); { Saludamos }
Delay( 1000 ); { Esperamos un segundo }
Window ( 1, 15, 80, 23 ); { Ventana entre las filas 15 y 23
}
TextBackground ( Blue ); { Con fondo azul }
ClrScr; { La borramos para que se vea }
for bucle := 1 to 100
do WriteLn( bucle ); { Escribimos del 1 al 100 }
WriteLn( 'Pulse una tecla..');
tecla := ReadKey; { Esperamos que se pulse una
tecla }
Window( 1, 1, 80, 25 ); { Restauramos ventana original }
GotoXY( 1, 24 ); { Vamos a la penúltima línea }
Write( 'Ha pulsado ', tecla ); { Pos eso }
Sound( 220 ); { Sonido de frecuencia 220 Hz }
Delay( 500 ); { Durante medio segundo }
NoSound; { Se acabó el sonido }
Delay( 2000 ); { Pausa antes de acabar }
TextColor( LightGray ); { Colores por defecto del DOS }
TextBackground( Black ); { Y borramos la pantalla }
ClrScr;
end.
const
Black = 0;
Blue = 1;
Green = 2;
Cyan = 3;
Red = 4;
Magenta = 5;
Brown = 6;
LightGray = 7;
DarkGray = 8;
LightBlue = 9;
LightGreen = 10;
LightCyan = 11;
LightRed = 12;
LightMagenta= 13;
Yellow = 14;
White = 15;
procedure ClrScr;
begin
gotoxy(0,0);
write( CLREOS );
end;
procedure TextColor(n: byte);
begin
write( chr(27), 'b', chr(n) );
end;
var
bucle : byte;
tecla : char;
begin
ClrScr; { Borra la pantalla }
TextColor( Yellow ); { Color amarillo }
TextBackground( Red ); { Fondo rojo }
GotoXY( 30, 13 ); { Vamos al centro de la pantalla }
Write(' Hola. Pulse una tecla... '); { Saludamos }
read(kbd,tecla); { Esperamos que se pulse una
tecla }
Las diferencias entre las dos últimas clases de fichero pueden parecer poco
claras ahora e incluso en el próximo apartado, pero en cuanto hayamos visto
todos los tipos de ficheros se comprenderá bien cuando usar unos y otros.
Ficheros de texto.
Casi salta a la vista que los ficheros del primer tipo, los de texto, van a ser más
fáciles de tratar que los "ficheros en general". Hasta cierto punto es así, y por
eso es por lo que vamos a empezar por ellos.
var
fichero: text; (* Fichero de texto *)
linea: string; (* Línea que leemos *)
begin
assign( fichero, 'C:\AUTOEXEC.BAT' ); (* Le asignamos el nombre
*)
reset( fichero ); (* Lo abrimos para
lectura *)
while not eof( fichero ) do (* Mientras que no se
acabe *)
begin
readln( fichero, linea ); (* Leemos una línea *)
writeln( linea ); (* y la mostramos *)
end;
close( fichero ); (* Se acabó: lo cerramos
*)
end.
Eso es todo. Debería ser bastante autoexplicativo, pero aun así vamos a
comentar algunas cosas:
Para abrir el fichero para añadir texto al final, usaríamos "append" en vez de
"reset". En ambos casos, los datos se escribirían con
writeln( fichero, linea );
var
fichero: text; (* Fichero de texto *)
linea: string; (* Línea que leemos *)
begin
assign( fichero, 'C:\AUTOEXEC.BAT' ); (* Le asignamos el nombre
*)
{$I-} (* Deshabilita comprobación
de entrada/salida *)
reset( fichero ); (* Lo intentamos abrir *)
{$I+} (* La habilitamos otra vez
*)
if ioResult = 0 then (* Si todo ha ido bien *)
begin
while not eof( fichero ) do (* Mientras que no se acabe
*)
begin
readln( fichero, linea ); (* Leemos una línea *)
writeln( linea ); (* y la mostramos *)
end;
close( fichero ); (* Se acabó: lo cerramos *)
end; (* Final del "if" *)
end.
var
fichero1, fichero2: text; (* Ficheros de texto *)
linea: string; (* Línea actual *)
begin
assign( fichero1, 'C:\AUTOEXEC.BAT' ); (* Le asignamos nombre
*)
assign( fichero2, 'AUTOEXEC.BAN' ); (* y al otro *)
{$I-} (* Sin comprobación E/S *)
reset( fichero1 ); (* Intentamos abrir uno *)
{$I+} (* La habilitamos otra vez
*)
if ioResult = 0 then (* Si todo ha ido bien *)
begin
rewrite( fichero2 ); (* Abrimos el otro *)
while not eof( fichero1 ) do (* Mientras que no acabe 1
*)
begin
readln( fichero1, linea ); (* Leemos una línea *)
writeln( fichero2, linea ); (* y la escribimos *)
end;
writeln( 'Ya está '); (* Se acabó: avisamos,
*)
close( fichero1 ); (* cerramos uno *)
close( fichero2 ); (* y el otro *)
end (* Final del "if" *)
else
writeln(' No he encontrado el fichero! '); (* Si no existe
*)
end.
Como ejercicios propuestos:
Ya hemos visto cómo acceder a los ficheros de texto, tanto para leerlos como
para escribir en ellos. Hoy nos centraremos en lo que vamos a llamar
"ficheros con tipo".
Estos son ficheros en los que cada uno de los elementos que lo integran es del
mismo tipo (como vimos que ocurre en un array).
Pues una vez que se conocen los ficheros de texto, no hay muchas diferencias
a la hora de un primer manejo: debemos declarar un fichero, asignarlo, abrirlo,
trabajar con él y cerrarlo.
Pero ahora podemos hacer más cosas también. Con los de texto, el uso
habitual era leer línea por línea, no carácter por carácter. Como las líneas
pueden tener cualquier longitud, no podíamos empezar por leer la línea 4, por
ejemplo, sin haber leído antes las tres anteriores. Esto es lo que se
llama ACCESO SECUENCIAL.
Ahora sí que sabemos lo que va a ocupar cada dato, ya que todos son del
mismo tipo, y podremos aprovecharlo para acceder a una determinada
posición del fichero cuando nos interese, sin necesidad de pasar por todas las
posiciones anteriores. Esto es el ACCESO ALEATORIO (o directo).
Pero Turbo Pascal nos lo facilita más aún, con una orden, seek, que permite
saltar a una determinada posición de un fichero sin tener que calcular nada
nosotros mismos. Veamos un par de ejemplos...
type
ficha = record (* Nuestras fichas *)
nombre: string [80];
edad: byte
end;
var
fichero: file of ficha; (* Nuestro fichero *)
bucle: byte; (* Para bucles, claro
*)
datoActual: ficha; (* La ficha actual *)
begin
assign( fichero, 'basura.dat' ); (* Asignamos *)
rewrite( fichero ); (* Abrimos (escritura)
*)
writeln(' Te iré pidiendo los datos de cuatro personas...' );
for bucle := 1 to 4 do (* Repetimos 4 veces *)
begin
writeln(' Introduce el nombre de la persona número ', bucle);
readln( datoActual.nombre );
writeln(' Introduce la edad de la persona número ', bucle);
readln( datoActual.edad );
write( fichero, datoActual ); (* Guardamos el dato *)
end;
close( fichero ); (* Cerramos el fichero
*)
end. (* Y se acabó *)
La única diferencia con lo que ya habíamos visto es que los datos son de tipo
"record" y que el fichero se declara de forma distinta, con "file of TipoBase".
Entonces ahora vamos a ver cómo leeríamos sólo la tercera ficha de este
fichero de datos que acabamos de crear:
program LeeUnDato;
type
ficha = record
nombre: string [80];
edad: byte
end;
var
fichero: file of ficha;
bucle: byte;
datoActual: ficha;
begin
assign( fichero, 'basura.dat' );
reset( fichero ); (* Abrimos (lectura) *)
seek( fichero, 2 ); (* <== Vamos a la ficha 3
*)
read( fichero, datoActual ); (* Leemos *)
writeln(' El nombre es: ', datoActual.nombre );
writeln(' La edad es: ',datoActual.edad );
close( fichero ); (* Y cerramos el fichero
*)
end.
En primer lugar, va a ser una agenda que guarde una sola ficha en memoria y
que vaya leyendo cada ficha que nos interese desde el disco, o escribiendo en
él los nuevos datos (todo ello de forma "automática", sin que quien maneje la
agenda se de cuenta). Esto hace que sea más lenta, pero no tiene más
limitación de tamaño que el espacio libre en nuestro disco duro. Las
posibilidades que debe tener serán:
Nombre: 20 letras.
Dirección: 30 letras.
Ciudad: 15 letras.
Código Postal: 5 letras.
Teléfono: 12 letras.
Observaciones: 40 letras.
Ficheros generales.
Hemos visto cómo acceder a los ficheros de texto y a los fichero "con tipo".
Pero en la práctica nos encontramos con muchos ficheros que no son de texto
y que tampoco tienen un tipo de datos claro.
Muchos formatos estándar como PCX, DBF o GIF están formados por una
cabecera en la que se dan detalles sobre el formato de los datos, y a
continuación ya se detallan los datos en sí.
Hay otra diferencia con los ficheros que hemos visto hasta ahora, y es que
cuando abrimos un fichero sin tipo con "reset", debemos indicar el tamaño de
cada dato (normalmente diremos que 1, y así podemos leer variables más o
menos grandes indicándolo con el "cuantos" que aparece en BlockRead).
Los bloques que leemos con "BlockRead" deben tener un tamaño menor de
64K (el resultado de multiplicar "cuantos" por el tamaño de cada dato).
Una mejora: es habitual usar "SizeOf" para calcular el tamaño de una variable,
en vez de calcularlo a mano y escribir, por ejemplo, 2048. Es más fiable y
permite modificar el tipo o el tamaño de la variable en la que almacenamos los
datos leídos sin que eso repercuta en el resto del programa.
Type
Gif_Header = Record { Primeros 13 Bytes de un
Gif }
Firma, NumVer : Array[1..3] of Char;
Tam_X,
Tam_Y : Word;
_Packed,
Fondo,
Aspecto : Byte;
end;
Var
Fich : File;
Cabecera : GIF_Header;
Nombre: String;
begin
Write( '¿Nombre del fichero GIF (con extensión)? ');
ReadLn( Nombre );
Assign( Fich, Nombre );
Reset( Fich, 1 ); { Tamaño base: 1
byte }
Blockread( Fich, Cabecera, SizeOf(Cabecera) );
Close( Fich );
With Cabecera DO
begin
Writeln('Versión: ', Firma, NumVer);
Writeln('Resolución: ', Tam_X, 'x',
Tam_Y, 'x', 2 SHL (_Packed and 7));
end;
end.
Ejemplo: agenda.
{ ========================================================
Ejemplo de Agenda, con lo que hemos visto hasta ahora.
Características:
- Número de fichas ilimitado, pero mayor lentitud, porque
los datos están permanentemente en disco, y en memoria se
almacena sólo la ficha actual.
- Muestra un ficha.
- Se puede ver la siguiente, la anterior o la número "x"
- Se pueden añadir fichas nuevas.
Posibles mejoras:
- Muuuuuchas. Por ejemplo...
- Mejorar la presentación.
- Mejorar la entrada de datos y su modificación.
- Buscar un determinado texto en las fichas.
- Imprimir una o varias fichas.
- Ordenar el fichero alfabéticamente.
- Mantener todas o parte de las fichas en memoria para mayor
velocidad.
- Borrar fichas.
- Clave de acceso.
- Datos encriptados.
- Etc, etc, etc...
program MiniAg;
const
nombref: string[12]='agenda.dat'; { Nombre del
fichero }
var
FichAgenda: file of tipoagenda;
{ Fichero }
ficha: TipoAgenda; { Guarda la ficha
actual }
NumFicha: word; { El número de ficha
actual }
Ultima: word; { Número de la última
ficha }
opcion: char; { La opción del menú que se
elige }
¿Para qué? Nos podría bastar con teclear en un programa todas las funciones
que nos interesen. Si creamos otro programa que las necesite, pues las
copiamos también en ese y ya está...
Una "unit" tiene dos partes: una pública, que es aquella a la que podremos
acceder, y una privada, que es el desarrollo detallado de esa parte pública, y a
esta parte no se puede acceder desde otros programas.
uses miCrt1;
begin
AtXY( 7, 5, 'Texto en la posición 7,5.' );
end.
Este programa no necesita llamar a la unidad CRT original, sino que nuestra
unidad ya lo hace por él. Vamos a mejorar ligeramente nuestra unidad,
añadiéndole un procedimiento "pausa":
unit miCrt2; { Unidad que "mejora más" la CRT }
{-------------------}
interface { Parte "pública", que se exporta }
{-------------------}
implementation { Parte "privada", detallada }
{-------------------}
end. { Final de la unidad }
y un programa que usase esta unidad, junto con la CRT original podría ser:
program PruebaDeMiCrt2;
begin
ClrScr; { De Crt }
atXY( 7, 5, 'Texto en la posición 7,5.' ); { de miCrt2 }
pausa; { de miCrt2 }
end.
{-------------------}
interface { Parte "pública", que se exporta }
{-------------------}
implementation { Parte "privada", detallada }
begin
ClrScr; { De Crt }
atXY( 7, 5, 'Texto en la posición 7,5.' ); { de miCrt3 }
if not EraMono then
atXY ( 10, 10, 'Modo de color ' );
pausa; { de miCrt3 }
end.
Así que se acabó la limitación de 64K. Ahora podremos tener, por ejemplo, 30
Mb de datos en nuestro programa y con un acceso muchísimo más rápido que
si teníamos las fichas en disco, como hicimos antes.
Ahora "sólo" queda ver cómo utilizar estas variables dinámicas. Esto lo vamos
a ver en 3 apartados. El primero (éste) será la introducción y veremos cómo
utilizar arrays con elementos que ocupen más de 64K. El segundo,
manejaremos las "listas enlazadas". El tercero nos centraremos en los "árboles
binarios" y comentaremos cosas sobre otras estructuras.
Introducción.
type
pFicha = ^Ficha; (* Puntero a la ficha *)
var
fichero: file of ficha; (* El fichero, claro *)
datoLeido: ficha; (* Una ficha que se lee *)
indice: array [1..1000] of pFicha; (* Punteros a 1000 fichas
*)
contador: integer; (* Nº de fichas que se lee
*)
begin
assign( fichero, 'Datos.Dat' ); (* Asigna el fichero *)
reset( fichero ); (* Lo abre *)
for contador := 1 to 1000 do (* Va a leer 1000 fichas *)
begin
read( fichero, datoleido ); (* Lee cada una de ellas *)
new( indice[contador] ); (* Le reserva espacio *)
indice[contador]^ := datoLeido; (* Y lo guarda en memoria
*)
end;
close( fichero ); (* Cierra el fichero *)
writeln('El nombre de la ficha 500 es: ');
writeln(indice[500]^.nombre);
for contador := 1 to 1000 do (* Liberamos memoria usada
*)
dispose( indice[contador] );
end.
será el campo nombre del dato al que apunta la dirección 500 del índice. El
manejo es muy parecido al de un array que contenga records, como ya
habíamos visto, con la diferencia de el carácter ^, que indica que se trata de
punteros.
Ya hemos visto una forma de tener arrays de más de 64K de tamaño, pero
seguimos con la limitación en el número de fichas. En el próximo apartado
veremos cómo evitar también esto.
Arrays de punteros.
Vimos una introducción a los punteros y comentamos cómo se manejarían
combinados con arrays. Antes de pasar a estructuras más complejas, vamos a
hacer un ejemplo práctico (que realmente funcione).
Hay cosas que se podrían hacer mejor, pero me he centrado en procurar que
sea lo más legible posible...
program Lector; { Lee ficheros de texto }
const
MaxLineas = 2000; { Para modificarlo fácilmente }
type
LineaTxt = string [80]; { Una línea de texto }
PLineaTxt = ^LineaTxt; { Puntero a lína de texto }
lineas = array[1..maxLineas] { Nuestro array de líneas }
of PLineaTxt;
var
nomFich: string; { El nombre del fichero }
fichero: text; { El fichero en sí }
datos: lineas; { Los datos, claro }
lineaActual: string; { Cada línea que lee del
fichero }
TotLineas: word; { El número total de líneas }
Primera: word; { La primera línea en
pantalla }
Procedure Inicio; { Abre el fichero }
begin
textbackground(black); { Colores de comienzo: fondo
negro }
textcolor(lightgray); { y texto gris }
clrscr; { Borramos la pantalla }
writeln('Lector de ficheros de texto.');
writeln;
write('Introduzca el nombre del fichero: ');
readln(nomFich);
end;
procedure Lee;
begin;
clrscr;
TotLineas := 0; { Inicializa variables }
Primera := 0;
while (not eof(fichero)) { Mientras quede fichero }
and (TotLineas < MaxLineas) do { y espacio en el array }
begin
readln( fichero, LineaActual ); { Lee una línea }
TotLineas := TotLineas + 1 ; { Aumenta el contador }
new(datos[TotLineas]); { Reserva memoria }
datos[TotLineas]^ := LineaActual; { y guarda la línea }
end;
if TotLineas > 0 { Si realmente se han leído
líneas }
then Primera := 1; { empezaremos en la primera }
close(fichero); { Al final, cierra el fichero }
end;
begin
Inicio; { Pantalla inicial }
assign(fichero, nomFich); { Asigna el fichero }
{$I-} { desactiva errores de E/S }
reset(fichero); { e intenta abrirlo }
{$I+} { Vuelve a activar errores }
if IOresult = 0 then { Si no ha habido error }
begin
Pantalla; { Dibuja la pantalla }
Lee; { Lee el fichero }
Muestra; { Y lo muestra }
end
else { Si hubo error }
begin
writeln(' ¡ No se ha podido abrir el fichero ! '); { Avisa }
pausa;
end;
salir { En cualq. caso, sale al final
}
end.
Listas enlazadas.
Después vimos con más detalle como podíamos hacer arrays de más de 64K.
Aprovechábamos mejor la memoria y a la vez seguíamos teniendo acceso
directo a cada dato. Como inconveniente: no podíamos añadir más datos que
los que hubiéramos previsto al principio (2000 líneas en el caso del lector de
ficheros que vimos como ejemplo).
Pues ahora vamos a ver dos tipos de estructuras totalmente dinámicas (frente a
los arrays, que eran estáticos). En esta lección serán las listas, y en la próxima
trataremos los árboles binarios. Hay otras muchas estructuras, pero no son
difíciles de desarrollar si se entienden bien estas dos.
Ahora "el truco" consistirá en que dentro de cada dato almacenaremos todo lo
que nos interesa, pero también una referencia que nos dirá dónde tenemos que
ir a buscar el siguiente.
Para añadir un ficha, no tendríamos más que reservar la memoria para ella, y
el Turbo Pascal nos diría "le he encontrado sitio en la posición 4079". Así que
nosotros iríamos a la última ficha y le diríamos "tu siguiente dato va a estar en
la posición 4079".
Un puntero que "no apunta a ningún sitio" tiene el valor NIL, que nos servirá
después para comprobar si se trata del final de la lista: todas las fichas
"apuntarán" a la siguiente, menos la última, que "no tiene siguiente".
y la crearíamos con
new (dato1); { Reservamos memoria }
dato1^.nombre := 'Pepe'; { Guardamos el nombre, }
dato1^.direccion := 'Su casa'; { la dirección }
dato1^.edad := 45; { la edad }
dato1^.siguiente := nil; { y no hay ninguna más }
new (dato3);
dato3^.nombre := 'Carlos';
dato3^.direccion := 'Por ahí';
dato3^.edad := 14;
dato3^.siguiente := dato2; { enlazamos con la siguiente }
o gráficamente:
+------+ +------+ +------+
¦Dato1 ¦ +->-¦Dato3 ¦ +->--¦Dato2 ¦
+------¦ ¦ +------¦ ¦ +------¦
¦ ¦ ¦ ¦ ¦
+---------+ +--------+ +-----------+
---------
--- nil
Es decir: cada ficha está enlazada con la siguiente, salvo la última, que no está
enlazada con ninguna (apunta a NIL).
Hemos empleado tres variables para guardar tres datos. Si tenemos 20 datos,
¿necesitaremos 20 variables? ¿Y 3000 variables para 3000 datos?
type
puntero = ^TipoDatos;
TipoDatos = record
numero: integer;
sig: puntero
end;
var
l: puntero; { Variables globales: la lista }
begin
l := CrearLista(5); { Crea una lista e introduce un 5 }
InsertaLista(l, 3); { Inserta un 3 }
InsertaLista(l, 2); { Inserta un 2 }
InsertaLista(l, 6); { Inserta un 6 }
MuestraLista(l) { Muestra la lista resultante }
end.
Ejercicios propuestos:
Árboles binarios.
Estas dos son estructuras más sencillas de programar de lo que sería una lista
en su caso general, pero que son también útiles en muchos casos.
Finalmente, antes de pasar con los "árboles", comentaré una mejora a estas
listas enlazadas que hemos visto. Tal y como las hemos tratado, tienen la
ventaja de que no hay limitaciones tan rígidas en cuanto a tamaño como en las
variables estáticas, ni hay por qué saber el número de elementos desde el
principio. Pero siempre hay que recorrerlas desde DELANTE hacia ATRÁS,
lo que puede resultar lento. Una mejora relativamente evidente es lo que se
llama una lista doble o lista doblemente enlazada: si guardamos punteros al
dato anterior y al siguiente, en vez de sólo al siguiente, podremos avanzar y
retroceder con comodidad. Pero tampoco profundizaremos más en ellas.
ARBOLES.
Pues eso será un árbol: una estructura dinámica en la que cada nodo
(elemento) puede tener más de un "siguiente". Nos centraremos en los
árboles binarios, en los que cada nodo puede tener un hijo izquierdo, un hijo
derecho, ambos o ninguno (dos hijos como máximo).
No vamos a ver cómo se hace eso de los "equilibrados", que sería propio de
un curso de programación más avanzado, o incluso de uno de "Tipos
Abstractos de Datos" o de "Algorítmica", y vamos a empezar a ver rutinas
para manejar estos árboles binarios de búsqueda.
Si alguien no se cree que funciona, que coja lápiz y papel y lo compruebe con
el árbol que hemos puesto antes como ejemplo. Es muy importante que este
procedimiento quede claro antes de seguir leyendo, porque los demás serán
muy parecidos.
La rutina de inserción sería:
procedure Insertar(var punt: puntero; valor: TipoDato);
begin
if punt = nil then { Si hemos llegado a una hoja }
begin
new(punt); { Reservamos memoria }
punt^.dato := valor; { Guardamos el dato }
punt^.hijoIzq := nil; { No tiene hijo izquierdo }
punt^.hijoDer := nil; { Ni derecho }
end
else { Si no es hoja }
if punt^.dato > valor { Y encuentra un dato mayor
}
Insertar(punt^.hijoIzq, valor) { Mira por la izquierda }
else { En caso contrario (menor)
}
Insertar(punt^.hijoDer, valor) { Mira por la derecha }
end;
O bien, simplemente, se pueden borrar recursivamente los dos hijos antes que
el padre (ahora ya no hace falta ir "en orden", porque no estamos leyendo,
sino borrando todo):
procedure BorrarArbol(punt: puntero);
begin
if punt <> nil then { Si queda algo que borrar }
begin
BorrarArbol(punt^.hijoIzq); { Borra la izqda
recursivamente }
BorrarArbol(punt^.hijoDer); { Y luego va hacia la derecha }
dispose(punt); { Libera lo que ocupaba el
nodo }
end;
end;
Ejercicios propuestos:
Implementar una pila de strings[20].
Implementar una cola de enteros.
Implementar una lista doblemente enlazada que almacene los datos
leídos de un fichero de texto (mejorando el lector de ficheros que
vimos).
Hacer lo mismo con una lista simple, pero cuyos elementos sean otras
listas de caracteres, en vez de strings de tamaño fijo.
Añadir la función "buscar" a nuestro árbol binario, que diga si un dato
que nos interesa pertenece o no al árbol (TRUE cuando sí pertenece;
FALSE cuando no).
¿Cómo se borraría un único elemento del árbol?
En primer lugar vamos a ir viendo las que podían haber formado parte de
temas anteriores, y después las que faltan. Tampoco pretendo que esto sea una
recopilación exhaustiva, sino simplemente mencionar algunas órdenes
interesantes que parecían haberse quedado en el tintero.
En Turbo Pascal 7.0, tenemos dos órdenes extra para el control de bucles,
tanto si se trata de "for", como de "repeat" o "until". Estas órdenes son:
Un ejemplo "poco útil" que use ambas podría ser escribir los números pares
hasta el 10 con un for:
for i := 1 to 1000 do { Nos podemos pasar }
begin
if i>10 then break; { Si es así, sale }
if i mod 2 = 1 then continue; { Si es impar, no lo escribe }
writeln( i ); { Si no, lo escribe }
end;
Goto.
El formato es
goto etiqueta
y las etiquetas se deben declarar al principio del programa, igual que las
variables. Para ello se usa la palabra "label". Vamos a verlo directamente con
un ejemplo:
label uno, dos;
var
donde: byte;
begin
writeln('¿Quiere saltar a la opción 1 o a la 2?');
readln (donde);
case donde of
1: goto uno;
2: goto dos;
else writeln('Número incorrecto');
end;
exit;
uno:
writeln('Esta es la opción 1. Vamos a seguir...');
dos:
writeln('Esta es la opción 2.');
end.
Para esto usamos "getmem" y "freemem", en los que debemos indicar cuánta
memoria queremos reservar para el dato en cuestión. Si queremos utilizarlos
como new y dispose, reservando toda la memoria que ocupa el dato (será lo
habitual), podemos usar "sizeof" para que el compilador lo calcule por
nosotros:
type
TipoDato = record
Nombre: string[40];
Edad: Byte;
end;
var
p: pointer;
begin
if MaxAvail < SizeOf(TipoDato) then
Writeln('No hay memoria suficiente')
else
begin
GetMem(p, SizeOf(TipoDato));
{ Trabajaríamos con el dato, y después... }
FreeMem(p, SizeOf(TipoDato));
end;
end.
Por cierto, "pointer" es un puntero genérico, que no está asociado a ningún
tipo concreto de dato. No lo vimos en la lección sobre punteros, porque para
nosotros lo habitual es trabajar con punteros que están relacionados con un
cierto tipo de datos.
Exit, halt.
En el tema 8 (y en los ejemplos del tema 6) vimos que podíamos usar exit para
salir de un programa.
Por ejemplo: "halt" o "halt(0)" indicaría una salida normal del programa (sin
errores), "halt(1)" podría indicar que no se han encontrado los datos
necesarios, etc.
Números aleatorios.
begin
Randomize;
for i := 1 to 50 do
Write (Random(1000), ' ');
end.
Funciones.
La mayoría de las que vamos a ver son funciones matemáticas que están ya
predefinidas en Pascal. Muchas de ellas son muy evidentes, pero precisamente
por eso no podíamos dejarlas sin mencionar al menos:
begin
writeln(elevado(2,3));
end.
repeat: Repite una serie de órdenes hasta ("until") que se cumple una
condición.
with: Para acceder a los campos de un "record" sin tener que nombrarlo
siempre.