Monografias.com > Sin categoría
Descargar Imprimir Comentar Ver trabajos relacionados

Manual de FreePascal (Parte 1) (página 3)




Enviado por rafaelfreites



Partes: 1, 2, 3, 4, 5, 6

begin

for contador := 9 to 0 do

begin

Writeln('Hola'); // Esto no se ejecutará nunca

end;

end.

Como ejemplo final vamos a codificar un programa que
calcule la suma de potencias de forma manual y que
compruebe que el valor coincide
con el de la fórmula antes mostrada, r^0 +r^1 +r^2 …+
r^n-1 = (r^n -1)/ (r-1)

program Calcular_n_potencias;

var

r, n, i, suma : Integer;

begin

Writeln('R :');

Readln(r);

if r > 1 then

begin

Writeln('N :');

Readln(n);

Writeln('Resultado :');

Writeln((r**n-1) div (r-1));

// Método
mecánico

suma := 0;

for i := 0 to n-1 do

begin

suma := suma + r**i;

end;

Writeln('Resultado Mecánico :');

Writeln(suma);

end

else

begin

Writeln('R tiene que ser un valor mayor que 1');

end;

end.

Estructuras iterativas
condicionales

Las estructuras
iterativas condicionales basan su potencial en que el bucle
finaliza en función
del valor que toma una expresión booleana.

El bucle while..do

Este bucle no finaliza hasta que la condición que
ha permitido iniciarlo se vuelve falsa. La estructura de
while..do es la siguiente.

while expresionBooleana do Sentencia;

Veamos de ejemplo, el programa siguiente. Este programa
no finaliza hasta que el usuario no ha introducido un punto como
cadena de entrada.

var

cadena : string;

begin

cadena := ; // Una cadena
vacía

while cadena <> '.' do

begin

Write('Escriba algo (Un punto para terminar) :
');

Readln(cadena);

end;

Writeln('Fin del programa');

end.

El bucle while ejecuta la sentencia o sentencias hasta
que la condición del while se vuelve falsa. Hasta que no
se ha terminado un ciclo no se evalúa de nuevo la
condición, por tanto, aunque en medio del código
la condición ya sea falsa se seguirán ejecutando
sentencias hasta que al finalizar el bloque se tenga que repetir
el bucle. Entonces la evaluación
daría falso y el código seguiría al punto
siguiente de después de la sentencias.

En el ejemplo anterior, es una expresión booleana
que se vuelve falsa cuando cadena es igual a un punto. Cuando el
usuario introduce un punto, el bucle ya no se repite y el usuario
ve impreso en la pantalla "Fin del programa".

Si la condición del bucle ya fuera falsa al
iniciarse por primera vez la sentencia while, el bucle ya no
empezaría. El código siguiente, por ejemplo, no se
ejecutaría nunca :

while false do

begin

Writeln('Esto no se ejecutará nunca');

end;

El literal false denota una expresión booleana
falsa, por lo que el bucle no empezará nunca. Vemos el
caso siguiente :

while true do

begin

Writeln('Esto no terminará nunca…');

end;

En este caso la expresión es siempre cierta por
tanto el bucle sería infinito. De momento no sabemos como
salir de un bucle infinito. Es importante asegurase de que la
condición de salida se cumplirá alguna vez cuando
diseñemos el algoritmo.

El bucle repeat..until

El bucle repeat..until es más o menos el
contrario del bucle while..do. El bucle repeat..until, va
ejecutando sentencias hasta que la condición del bucle es
cierta. Otra diferencia se encuentra en e l hecho de que la
evaluación de la condición del bucle se hace al
final del bucle y no al principio de éste. Esto provoca
que el código se ejecute al menos una vez antes de salir
del bucle. Cuando la condición del bucle se cumple
entonces éste termina.

Algunos autores consideran el uso de repeat..until
inapropiado para una buena programación. En cualquier caso, lo cierto,
es que toda estructura de tipo repeat..until es convertible en
una estructura de tipo while..do. Veamos un ejemplo de
repeat..until en el siguiente programa :

var

nombre : string;

begin

repeat

Write('Escriba su nombre : ');

Readln(nombre);

until nombre<>;

end.

En este caso podemos observar que si el usuario pulsa Intro sin
haber introducido nada la variable nombre no es diferente de la
cadena vacía () y por tanto el bucle vuelve a
empezar.

Tal como hemos comentado anteriormente todos los
repeat..until son transformables a while..do. La versión
con while..do del programa anterior sería la
siguiente :

var

nombre : string;

begin

nombre := ;

while nombre = do

begin

Write('Escriba su nombre : ');

Readln(nombre);

end;

end.

En este caso hay que asegurarse de que el código
del bucle se ejecutará al menos una vez. Para esto,
realizamos la asignación nombre := para que la
condición del bucle while se cumpla al menos la primera
vez.

Las funciones break y
continue.

Algunas veces, necesitaremos alterar el flujo de
ejecución de bucles for, while o repeat. Para este
cometido disponemos de las funciones break y continue.

La función break hace que el programa salga del
bucle y la función continue hace que el bucle procese la
siguiente iteración sin terminar la iteración
actual. Estas funciones sólo se pueden emplea r dentro de
bucles for, while y repeat y no se pueden emplear fuera de estos
ámbitos.

for numero := 1 to 10 do

begin

Writeln('Hola #', numero);

Continue;

Writeln('Adiós');

end;

while true do

begin

if Salir then break;

end;

En el primer caso 'Adiós' no se verá
impreso nunca en la pantalla pues el bucle continúa con la
siguiente iteración aunque el bloque de sentencias no haya
terminado.

En el segundo ejemplo podemos salir del bucle infinito
mediante una condición de salida Salir, que es booleana, y
la función break.

Hay que tener en cuenta de que aunque es posible poner
sentencias después de un break o continue, si estos forman
parte de un mismo bloque de sentencias éstas ya no se
ejecutarán, tal como hemos visto en el ejemplo
anterior.

El selector case

Muchas veces necesitaremos modificar el comportamiento
de un programa en función a los valores
que toma una variable. Con lo que conocemos de Pascal, la
única forma de hacerlo sería mediante if. Pero si
la variable en cuestión puede tomar varios valores y
necesitamos diferentes comportamientos entonces la sentencia if
es algo limitada. Siempre podríamos imaginar el caso
siguiente :

var

diasemana : Integer;

begin

Write('Introduzca el día de la semana :
');

Readln(diasemana);

if diasemana > 7 then begin Writeln('Número no
válido') else

if diasemana = 1 then begin Writeln('Lunes')
else

if diasemana = 2 then begin Writeln('Martes')

if diasemana = 7 then begin

Writeln('Domingo');

end.

Como se adivinará, esta no es la mejor forma de
resolver este problema y termina por confundir al programador.
Para este tipo de casos Pascal posee la estructura selectora
case. Case sólo puede trabajar con ordinales (booleanos,
caracteres y enteros) pero es una forma muy elegante de
implementar lo que queremos. La estructura de case es algo
más enrevesada que las anteriores pero muy sencilla a la
larga :

Case expresionOrdinal of

Constante1 : Sentencia1;

Constante2 : Sentencia2;

ConstanteN : SentenciaN;

else SentenciaElse;

end;

Pasemos a ver un ejemplo de como se implementaría
el problema de los días de la semana con un
case :

var

diasemana : Integer;

begin

Write('Introduzca el día de la semana :
');

Readln(diasemana);

case diasemana of

1 : begin

Writeln('Lunes');

end;

2 : begin

Writeln('Martes');

end;

3 : begin

Writeln('Miércoles');

end;

4 : begin

Writeln('Jueves');

end;

5 : begin

Writeln('Viernes');

end;

6 : begin

Writeln('Sábado');

end;

7 : begin

Writeln('Domingo');

end;

else

begin

Writeln('Número no válido');

end;

end;

end.

La estructura introducida por else se ejecutará
si el valor de diasemana no tiene un selector, por ejemplo si
introducimos un 0 o un 8.

Hay que tener en cuenta varias cuestiones a la hora de
escribir un case. Los valores ordinales de cada selector tienen
que ser valores literales o valores constantes o una
expresión evaluable en tiempo de
compilación. Así, no se permiten las variables. Por
ejemplo :

var

Numero1, Numero2 : Integer;

const Numero4 = 4;

begin

Numero1 := 3;

case Numero2 of

Numero1 : begin

… // Esta construcción no es valida

end;

Numero4 : begin

… // Esta sí que lo es

end;

Numero4+1 : begin

… // Esta también lo es

end;

end;

end.

Numero4+1 es válido pues es una expresión
evaluable en tiempo de compilación, en nuestro caso tiene
el valor 5. Como se puede ver, la estructura else se puede omitir
sin ningún problema. En este caso si Numero2 tiene un
valor sin selector no se hace nada. En una estructura case es
posible especificar más de un tipo ordinal para cada
selector, separándolos entre comas, o bien especificando
un rango ordinal para el selector, separados con dos puntos
seguidos (..).

Case Numero of

0..10 : begin

// Esto se ejecutaría con numero del 0 al 10
incluidos

end;

11, 13 : begin

// Sólo los valores 11 y 13

end;

12 : begin // Sólo el valor 12

end;

14..80, 82..90 : begin

// Los valores de 14 a 80 y de 82 a 90

end;

81, 91..100 : begin

// El valor 81 y del 91 al 100

end;

end;

No importa el orden en el que escribamos los selectores.
Lo importante es que no se repitan, si fuera el caso el
compilador nos advertiría del fallo. Este es uno de los
motivos por los cuales los valores de los selectores tienen que
ser evaluables en tiempo de compilación y no de
ejecución.

En todos los ejemplos anteriores hemos empleado enteros
ya que son ordinales por excelencia pero también podemos
emplear booleanos y caracteres, que como sabemos también
son ordinales.

uses Crt;

var

caracter : Char;

begin

Write('Introduzca un carácter…');

caracter := Readkey;

case caracter of

'A'..'Z', 'a'..'z' : begin Writeln('Es una
letra');

end;

'0'..'9' : begin

Writeln('Es un número');

end;

else begin

Writeln('Otro tipo de carácter');

end;

end.

La función ReadKey es una función de la
unit Crt que nos devuelve el carácter de la tecla que el
usuario ha pulsado.

Tipos de datos
estructurados

Los datos estructurados

Hasta el momento hemos trabajado todo el rato con
tipos de datos
básicos de Pascal : booleanos, caracteres, string,
enteros y reales. La mayoría de aplicaciones de la vida
real no emplean datos de uno en uno sino que emplean grupos de datos.
Para resolver este problema Pascal incorpora un conjunto de datos
estructurados : array (vectores o
tablas), registros y
conjuntos.
También son tipos de datos estructurados las clases y los
objetos pero que debido a su complejidad trataremos en otros
capítulos. Finalmente, los archivos
también son un tipo especial de datos estructurados que
trataremos aparte.

Declaraciones de tipo

Antes hemos hablado de tipos fundamentales, pero
también es posible definir nuevos tipos de datos que
podremos utilizar en nuestras variables. Con la palabra reservada
type podemos declarar nuevos tipos que podremos emplear cuando
declaremos variables, de forma que estas variables sean del tipo
definido. Veamos un ejemplo asaz inútil pero que
servirá para introducirse a los tipos de datos. Los tipos
se tendrían que declarar antes de las variables pero
dentro de la declaración de tipo no es necesario seguir un
orden especial.

type

Numero = Integer;

NumeroNoIdentico = type Integer;

var

N1 : Numero; // Esto es lo mismo que un
Integer

N2 : NumeroNoIdentico; // Esto no es exactamente un
Integer aunque funciona igual

begin

end.

Como se puede ver, la declaración de un tipo es
semejante a la declaración de una constante. En el primer
caso Numero no es más que otro nombre para Integer
mientras que en el segundo caso NumeroNoIdentico es un
símbolo nuevo para el compilador. Este último caso
sirve para forzar al compilador que sea capaz de distinguir entre
un Integer y NumeroNoIdentico mientras que Numero es imposible de
distinguir de Integer. Es un caso algo especial y se emplea pocas
veces. Sólo veremos este ejemplo.

Enumeraciones

El tipo enumerado permite construir datos con
propiedades ordinales. La característica del tipo
enumerado es que sólo puede tomar los valores de su
enumeración.

type

TMedioTransporte : (Pie, Bicicleta, Moto, Coche,
Camion, Tren, Barco, Avion);

var

MedioTransporte : TMedioTransporte;

begin

MedioTransporte := Camion;

{ Sólo es posible asignar a identificadores de la
enumeración }

case MedioTransporte of // Es posible ya que un
enumerado es un tipo ordinal

Pie : begin

end;

Bicicleta : begin

end;

end;

end.

Por convención, los tipos de datos se suelen
declarar con una T seguido del nombre. De esta forma, los
identificadores que empiezan por T se entiende que son tipos y no
variables.

Las enumeraciones se declaran con paréntesis y un
seguido de identificadores separados por comas la utilidad
más importante de las enumeraciones se encuentra
especialmente en los conjuntos.

Conjuntos

Un conjunto es una colección de elementos de un
mismo tipo ordinal. De esta forma podemos añadir y
suprimir elementos de un conjunto, comprobar si un elemento
determinado pertenece al conjunto, etc.

Los conjuntos, además, permiten realizar operaciones
propias de conjuntos como la unión, con el operador suma,
o la intersección con la operación producto.

Los literales de conjuntos se expresan con
claudátores y los identificadores del conjunto. Si
deseamos más de uno hay que separarlos entre comas. El
literal de conjunto vacío es [].

Dado el conjunto siguiente de comida rápida y dos
variables de tipo de este conjunto definimos las operaciones
siguientes :

type

TJunkFood = (Pizza, Donut, Frankfurt, Burguer); //
Enumeración

TSetJunkFood = set of TJunkFood; // Conjunto de la
enumeración anterior

var

JunkFood, JunkFood2 : TSetJunkFood;

Operación

Sintaxis

Qué hace?

Asignación directa

JunkFood := [Pizza, Donut]; Si queremos el
conjunto vacío : JunkFood :=
[];

Asigna un conjunto de valores al conjunto de forma
que estos pertenezcan a él.

Unión de conjuntos o elementos.

Si añadimos elementos: JunkFood :=
JunkFood + [Burguer]; Si unimos conjuntos del mismo
tipo : JunkFood := JunkFood +
JunkFood2;

Une un conjunto con otro de forma que el resultado
es el conjunto unión.

Diferencia

Si suprimimos elementos : JunkFood :=
JunkFood – [Burguer]; Suprimimos los elementos de JunkFood2
que hay en JunkFood : JunkFood := JunkFood –
JunkFood2;

Suprime los elementos del segundo conjunto que se
encuentran en el primero y devuelve el conjunto
resultado.

Intersección

Intersección de un conjunto y un
literal : JunkFood := JunkFood * [Burguer]; (Si
en JunkFood no hubiera Burguer obtendríamos el
conjunto vacío). Intersección de dos
conjuntos. JunkFood := JunkFood *
JunkFood2;

Devuelve un conjunto que contiene los elementos
que pertenecen a ambos

conjuntos intersecados.

Para comprobar que un elemento pertenece
a un determinado conjunto hay que emplear el operador
in :

if Burguer in JunkFood then { este operador devuelve
cierto si en JunkFood hay el elemento Burguer }

begin

end;

Arrays o vectores

Llamamos array, vector o tabla a una estructura de
datos formada por un conjunto de variables del mismo tipo a
los cuales podemos acceder mediante uno o más
índices.

Se declaran de la forma siguiente :

array [minElemento..maxElemento] of tipoDato;

Por ejemplo, en el caso siguiente :

var

MiArray : array [1..9] of Integer;

Hemos declarado un array de 9 elementos, del 1 al 9, al
cual podemos acceder en un momento determinado del programa de la
forma siguiente :

begin

MiArray[2] := 100;

MiArray[3] := 23;

MiArray[4] := MiArray[2] + MiArray[3];

end;

Hay que ir con cuidado a la hora de leer un elemento del
array porque puede ser que nos estemos leyendo un punto que no
está en el array :

begin

MiArray[10] := 4;// Error !!!

end.

Para saber el elemento máximo y mínimo de
un array podemos emplear las funciones High y Low sobre el
array :

begin

Writeln(Low(MiArray)); // Devuelve 1

Writeln(High(MiArray)); // Devuelve 9

end.

Podemos definir arrays multidimensionales concatenando
dos rangos, por ejemplo, definiremos dos arrays de Integer. El
primero será bidimensional de 3×2 y el segundo
tridimensional de 4x3x2.

var

Bidimensional : array [1..3, 1..2] of
Integer;

Tridimensional : array [1..4, 1..3, 1..2] of
Integer;

Llegados aquí puede ser algo complicado entender
como es un array bidimensional o un array multidimensional. Es
fácil de interpretar. Cada elemento del array indicado por
el primer índice es otro array el cual podemos acceder a
sus elementos mediante la combinación del primer
índice y el segundo. Por ejemplo si hacemos
Bidimensional[2] estamos accediendo al segundo array de 2
elementos. Podemos acceder al primer elemento de este array de 2
elementos mediante la construcción
siguiente :

Bidimensional[2, 1]

Bidimensional[1]

Bidimensional[1,1]

Bidimensional[1,2]

Bidimensional[2]

Bidimensional[2,1]

Bidimensional[2,2]

Bidimensional[3]

Bidimensional[3,1]

Bidimensional[3,2]

 

En este caso, el segundo índice sólo es
valido para los valores 1 y 2. Obsérvese que si hace Low y
High directamente a Bidimensional obtendrá 1 y 3,
respectivamente, mientras que si los aplica a Bidimensional[1]
obtendrá 1 y 2.

En el caso de que necesitemos asignar el contenido de un
array a otro sólo es posible cuando tengan el mismo tipo
estrictamente, hay que tener en cuenta que para el compilador
igual declaración no implica el mismo tipo. Véase
el programa siguiente :

program CopiarArray;

type

TMiArray : array [0..3] of Integer;

var

Array1 : TMiArray;

Array2 : TMiArray;

Array3 : array [0..3] of Integer;

Array4 : array [0..3] of Integer;

Array5, Array6 : array [0..3] of
Integer;

begin

Array1 := Array2; // Compatible ya que el tipo es
el mismo

Array3 := Array4; { Incompatible, tiene la misma
declaración pero diferente tipo. Esto da un error de
compilación }

Array5 := Array6; { Compatible, la
declaración obliga al compilador a entender que las dos
variables son idénticas }

end.

En los ejemplos hemos empleado rangos numéricos
con enteros positivos pero también es posible definir el
rango del array con números negativos como por ejemplo
[-2..2] o bien [-5..-3]. Es importante recordar que el rango
inicial no puede ser nunca superior al final. Por tanto un array
con rango [5..3] es incorrecto.

Los string y los arrays*

Es posible acceder a los caracteres de un string (tanto
si es ShortString, AnsiString o String[n]) mediante una sintaxis
de array. Los string están indexados en cero pero el
primer carácter se encuentra en la posición
1.

Cadena := 'Hola buenos días';

Writeln(Cadena[2]); {o}

Writeln(Cadena[14]); {í}

El nombre de caracteres de una cadena se puede obtener
con la función Length. El valor que devuelve es el nombre
de caracteres dinámicos, o sea el nombre de caracteres que
tiene la cadena. En cambio no
devuelve el máximo de caracteres que puede obtener la
cadena :

var

Cadena : string[40];

begin

Cadena := 'Hola buenos días';

Writeln(Length(Cadena)); // Devuelve 16

end.

Para saber la longitud máxima de la cadena hay
que emplear la función High. La función Low siempre
devuelve cero con una cadena. Obsérvese que los elementos
de una cadena son caracteres y por tanto podemos asignar a un
Char el elemento de una cadena.

Registros o records

Un registro es un
tipo de dato que contiene un conjunto de campos que no son
más que un conjunto de identificadores reunidos bajo un
mismo nombre.

La declaración de un registro es de la forma
siguiente :

record

Variable1 : Tipo1;.

VariableN : TipoN;

end;

Por ejemplo si queremos almacenar datos referentes a un
cliente podemos
emplear el registro siguiente :

type

TDatosCliente = record

Nombre : string;

Apellidos : string;

NumTelefono : Integer;

end;

Ahora sólo hay que declarar una variable de este
tipo y emplear sus campos :

var

DatosCliente : TDatosCliente;

begin

Writeln('Introduzca los datos del cliente');

Write('Nombre : ');
Readln(DatosCliente.Nombre);

Write('Apellidos : ');
Readln(DatosCliente.Apellidos);

Write('Nm. Teléfono : ');
Readln(DatosCliente.NumTelefono);

end.

Como se puede ver, empleamos el punto para distinguir
con qué variable queremos trabajar. Así
DatosCliente.Nombre representa el campo Nombre que como hemos
visto en la declaración es de tipo String. Téngase
en cuenta que DatosCliente es de tipo TDatosCliente pero que los
campos de éste tienen su propio tipo, tal como se ha
declarado en el registro.

Podemos indicar al compilador que los identificadores
que no conozca los busque en el record mediante la palabra
reservada with. Podíamos haber escrito el código
así :

var

DatosCliente : TDatosCliente;

begin

Writeln('Introduzca los datos del cliente');

with DatosCliente do

begin

Write('Nombre : '); Readln(Nombre); // Esto
sólo puede ser

DatosCliente.Nombre

Write('Apellidos : '); Readln(Apellidos); //
DatosCliente.Apellidos

Write('Nm. Teléfono : ');
Readln(NumTelefono); // DatosCliente.NumTelefono

end;

end.

A menos que sea necesario, no se recomienda el uso de
with ya que confunde al programador. La utilización del
punto permite cualificar el identificador y evita confusiones.
También podemos cualificar el identificador en
función de su unit. Por ejemplo la función ReadKey
de la unit Crt se puede indicar como Crt.ReadKey. En este caso,
el punto es útil para determinar exactamente qué
variable o qué función estamos llamando,
especialmente cuando haya más de una en diferentes
units.

[]

Registros variantes

Pascal permite definir lo que se llaman registros con
partes variantes. Un registro con una parte variante consta de un
campo que llamaremos selector. El valor de este selector es
útil para el programador pues le permite saber cual de los
campos posibles se están empleando. El compilador no tiene
en cuenta el valor de este campo selector ya que permite el
acceso a todos los campos declarados en la parte
variante.

type

TConjuntoDatos = ( TCaracter, TCadena, TNumero,
TCadenayCaracter );

TCombinado = record

case ConjuntoDatos : TConjuntoDatos of

TCaracter : ( Caracter : Char);

TCadena : ( Cadena : string );

TNumero : ( Numero : integer);

end;

var

Combinado : TCombinado;

Por ejemplo podemos realizar lo siguiente
Combinado.Caracter := 'z'; y después
Combinado.ConjuntoDatos := TCaracter; para indicar
qué campo hay que leer. Por ejemplo, si hacemos la
asignación Combinado.Numero := 65; y después
hacemos Writeln(Combinado.Caracter); se escribirá en la
pantalla la letra A.

Este comportamiento, en apariencia tan raro, es debido a
que un registro con la parte variante sita los distintos campos
en una misma región de memoria de forma
que podamos trabajar con un Byte y un Boolean o trabajar con un
Cardinal y leer sus dos Word, el
superior y el inferior.

El uso de registros variantes está restringido a
usos muy concretos. Esta es la forma Pascal de implementar un
unión de C/C++.

Archivos

[editar]

La necesidad de los archivos

Los programas que
hemos desarrollado hasta ahora se caracterizan por poseer una
memoria volátil que se concretaba con las variables. Al
finalizar el programa, el valor de las variables, sea útil
o no, se pierde sin que haya forma de recuperarlo en una
posterior ejecución del mismo programa. Los archivos
permiten resolver este problema ya que permiten el almacenaje de
datos en soportes no volátiles, como son los disquetes,
los discos duros y
su posterior acceso y modificación.

Cómo trabajar con archivos en
Pascal

Todos los sistemas de
archivos permiten asignar un nombre a los archivos de forma que
lo podamos identificar de forma única dentro de un
directorio, por ejemplo.

En Pascal, en vez de operar directamente con los nombres
de archivos trabajaremos con un alias que no es nada más
que una variable que ha sido asignada a un nombre de archivo. Las
posteriores operaciones que queramos llevar a cabo sobre este
archivo tomarán como parámetro este alias y todas
las operaciones se realizarán al archivo al cual se
refiere este alias.

Tipos de archivos en Pascal

Los tres tipos de archivos con los cuales podemos
trabajar en Pascal son : archivos con tipo, archivo sin tipo
y archivos de texto.

Los archivos con tipo almacenan un único tipo de
dato y permiten los que se llama acceso aleatorio, en
contraposición con el acceso secuencial que obliga a leer
(o al menos pasar) por todos los datos anteriores a uno concreto. De
esta forma podemos situarnos en cualquier parte del archivo de
forma rápida y leer o escribir datos.

Los archivos sin tipo no almacenan, a la contra,
ningún tipo en concreto. El programador es quien decide
qué datos se leen y en qué orden se leen. El acceso
a datos de este tipo de archivos es totalmente secuencial aunque
es más versátil pues se permiten almacenar datos de
casi cualquier tipo.

Los archivos de texto permiten la lectura y
escritura de
archivos ASCII y
la conversión automática a cadenas String[255], o
ShortString, que en realidad no son cadenas
ASCII. El acceso a
estos archivos también es secuencial.

[editar]

Trabajar con archivos de
texto

Empezaremos con archivos de texto pues son más
simples que otros tipos de archivo.

El tipo de archivo de texto se define con el tipo Text.
Por tanto declarar un alias de archivo de texto es tan simple
como la declaración siguiente :

var

ArchivoTexto : Text;

Para asignar a un alias un nombre de archivo emplearemos
la función Assign^2 <#sdfootnote2sym>

Assign(ArchivoTexto, 'PRUEBA.TXT');

En este ejemplo hemos asignado la variable ArchivoTexto
al archivo PRUEBA.TXT. Hay que tener en cuenta de que en Windows los
archivos no son sensibles a las mayúsculas,
podíamos haber puesto prueba.txt y sería el mismo
archivo. Téngase en cuenta que en algunos sistemas
operativos (como Linux) esto puede
no ser cierto.

Ahora habrá que abrir el archivo para realizar
alguna operación. Básicamente distinguimos entre
dos tipos de operaciones en un archivo : operaciones de
lectura,
leemos el contenido, y operaciones de escritura, donde escribimos
el contenido.

Pascal, además permite abrir el archivos con
diferentes finalidades y con diferentes funciones. Si lo queremos
abrir de sólo lectura, sin poder realizar
escritura, emplearemos la función Reset. Si lo que
queremos es reescribir de nuevo el archivo, sin posibilidad de
leer, emplearemos Rewrite. Además, los archivos de texto
permiten añadir datos al final del archivo si este se abre
con la orden Append, la cual es exclusiva de los archivos de
texto. Las tres funciones toman como único
parámetro el alias del archivo.

Reset(ArchivoTexto); // Sólo podemos
leer

// o bien

Rewrite(ArchivoTexto); // Borramos el archivo y
sólo podemos escribir

// o bien

Append(ArchivoTexto); // Sólo podemos escribir al
final del archivo

La diferencia fundamental entre Rewrite y Append se
encuentra en que Rewrite borra todos los datos del archivo si ya
existiese, de lo contrario lo crea, mientras que Append crea el
archivo si no lo existe, de lo contrario se sita al final del
archivo. Reset sólo funciona con archivos que ya existen,
en caso contrario genera un error.

El llamado cursor de lectura/escritura se refiere al
punto desde el cual empieza nuestra lectura o escritura. Al abrir
con Reset o Rewrite el cursor siempre se encuentra al principio
del archivo. En el caso de Append el cursor se encuentra en el
final del archivo de texto de forma que la única cosa que
podemos hacer es escribir del final del fichero en adelante, o
sea alargándolo. Al leer o escribir en u n archivo el
cursor de lectura/escritura adelanta hacia el fin del fichero
tantas posiciones como el tamaño del dato escrito o
leído. En caso de lectura, leer más allá del
fin del archivo es incorrecto mientras que escribir a partir del
fin del fichero hace que éste crezca.

Los únicos datos que podemos escribir en un
archivo de texto son efectivamente cadenas de texto. La
estructura de los archivos de texto ASCII es bien simple. Constan de
líneas formadas por una tira de caracteres que terminan
con los caracteres ASCII número 13 y 10 (fin de
línea y retorno de carro).

Para leer o escribir una línea desde un archivo
de texto emplearemos las funciones Readln o Writeln
respectivamente, pero teniendo en cuenta de que la primera
variable especificada como parámetro sea el alias del
archivo y el segundo parámetro sea un String. Sólo
en el caso de Writeln, el segundo parámetro puede ser un
literal de String mientras que Readln exige una variable String
como segundo parámetro.

Una vez hemos finalizado el trabajo con
el archivo conviene cerrarlo para asegurarse de que los datos
escritos se escriben correctamente y para indicar que ya no lo
vamos a emplear más. La función Close^3
<#sdfootnote3sym>, que toma como parámetro el alias
del archivo, se encarga de esta tarea.

Por ejemplo este programa escribe la fecha actual a un
archivo de texto :

program EscribirFecha;

uses Dos, SysUtils;

{ La unit DOS se necesita para GetDate la unit SysUtils
es necesaria para IntToStr }

const

DiasSemana : array[0..6] of string = ('Domingo',
'Lunes', 'Martes','Miércoles', 'Jueves', 'Viernes',
'Sábado');

var

ArchivoTexto : Text;

Anyo, Mes, Dia, DiaSemana : Integer;

begin

Assign(ArchivoTexto, 'FECHA.TXT');

Rewrite(ArchivoTexto); // Lo abrimos para escritura
borrándolo todo.

GetDate(Anyo, Mes, Dia, DiaSemana);

Writeln(ArchivoTexto, DiasSemana[DiaSemana]+'
'+IntToStr(Dia)+'/'+IntToStr(Mes)+'/'+IntToStr(Anyo));

Close(ArchivoTexto);

end.

Antes de seguir hay que hacer un comentario sobre el
array DiasSemana que hemos empleado. Es posible declarar
constantes con tipo de forma que se especifica de qué tipo
son y su valor inicial, ya que las constantes con tipos pueden
ser modificadas a lo largo del programa, aunque no suele ser muy
usual. En el caso de los arrays la sintaxis obliga a separar los
literales entre paréntesis. En caso de arrays
multidimensionales hay que emplear la misma sintaxis y separarla
entre comas :

const

ArrayBi : array [0..1, 0..1] of integer = ((-3, 4),
(2, 7));

El array que hemos declarado lo empleamos para saber el
nombre del día de la semana. Los valores del día
actual se obtienen gracias a la función GetDate a la cual
le pasaremos cuatro variables: día, mes año y una
para el día de la semana. Mediante el array DiasSemana
podemos obtener el nombre de los días de la semana ya que
GetDate devuelve un valor entre 0 y 6 dónde cero es
domingo y el seis es sábado. De esta forma el día
de la semana devuelto por GetDate coincide con los nombres del
array.

En el Writeln posterior hemos concatenado el día
de la semana, el día, el mes y el año, aunque por
ser Writeln podríamos haber separado estos elementos por
comas como si fueran más parámetros.

Vamos a leer el archivo que hemos creado con otro
programa que lea una línea del archivo y la imprima por la
pantalla.

program LeerFecha;

var

ArchivoTexto : Text;

CadenaFecha : string;

begin

Assign(ArchivoTexto, 'FECHA.TXT');

Reset(ArchivoTexto); // Lo abrimos para sólo
lectura

Readln(ArchivoTexto, CadenaFecha);

Writeln(CadenaFecha);

Close(CadenaFecha);

end.

Leemos una sola línea del archivo de texto y la
almacenamos en la variable CadenaFecha. Después la
mostramos en pantalla para comprobar que el dato leído es
el que realmente había en el archivo. Finalmente cerramos
el archivo.

El uso de Read en vez de Readln no es recomendable ya
que puede tener un comportamiento extraño. En cambio, si
lo que queremos es añadir una cadena sin saltar de
línea podemos emplear Write con la misma sintaxis
Writeln.

Hay otras funciones muy interesantes y útiles
para los archivos de texto, todas toman como parámetro el
alias del archivo de texto. Eof (End of file) devuelve true si el
cursor del archivo ha llegado al final de éste. Eoln (End
of line) devuelve true si el cursor se encuentra al final de una
línea. Esta última función se suele emplear
cuando se lee con Read carácter a carácter. La
función Eof permite parar la lectura del archivo ya que
leer más allá del archivo genera un error de
ejecución.

La función Flush descarga los buffers internos
del archivo de texto al disco de forma que el buffer, o memoria
intermedia, se vacía y se actualiza el archivo.

Las funciones SeekEof y SeekEoln son parecidas a las
respectivas Eof y Eoln pero sin tener en cuenta los caracteres en
blanco, de forma que si desde la posición del cursor del
archivo hasta el fina l del archivo o final de línea todo
son blancos SeekEof y SeekEoln, respectivamente, devuelven
true.

Truncate suprime todos los datos posteriores a la
posición actual del cursor del archivo.

Trabajar con archivos con
tipo

Para declarar que un archivo está formado por
datos de un sólo tipo hay que declararlo con las palabras
reservadas file of y el tipo del archivo :

type

TAgenda = record

Nombre, Apellidos : string;

NumTelefono : Integer;

end;

var

Agenda : file of TAgenda;

De esta forma podríamos implementar una agenda
muy rudimentaria con sólo tres campos (Nombre, Apellidos y
NumTelefono). Para crear, escribir y leer del archivo el
funcionamiento es idéntico a los archivos de texto con la
diferencia que hay que leer los datos en una variable del tipo
del archivo. También hay pequeñas diferencias
respecto a la apertura del archivo. Reset permite la lectura y la
escritura, en archivos de texto sólo permitía
lectura. Append no se puede emplear en archivos que no sean de
texto y Rewrite se comporta igual que con los archivos de
texto.

Vamos a ver un ejemplo de como podemos añadir
datos al archivo de agenda :

program EscribirArchivoTipo;

uses SysUtils; // Para la función
FileExists

const

NombreArchivo = 'AGENDA.DAT';

type

TAgenda = record

Nombre, Apellidos : string;

NumTelefono : Cardinal;

end;

var

Agenda : file of TAgenda;

DatoTemporal : TAgenda;

NumVeces, i : Integer;

begin

Assign(Agenda, NombreArchivo); // Asignamos el
archivo

if not FileExists(NombreArchivo) then

begin

Rewrite(Agenda); // Si no existe lo creamos

end

else

begin

Reset(Agenda); // Lo abrimos para lectura
escritura

end;

Write('Cuantas entradas desea añadir en la
agenda ? ');

Readln(NumVeces);

for i := 1 to NumVeces do

begin

Write('Introduzca el nombre : ');
Readln(DatoTemporal.Nombre);

Write('Introduzca los apellidos : ');
Readln(DatoTemporal.Apellidos);

Write('Introduzca el teléfono : ');
Readln(DadaTemporal.NumTelefono);

Write(Agenda, DatoTemporal); // Escribimos DatoTemporal
al archivo

Writeln; // Equivale a Writeln(); hace un salto de
línea

end;

Close(Agenda); // Cerramos la agenda

end.

Observamos en el código de que cuando el archivo
no existe entonces lo creamos automáticamente con la
función Rewrite. En caso contrario lo podemos abrir con
Reset. La función FileExists permite saber si el archivo
especificado existe ya que devuelve true en caso
afirmativo.

Pedimos al usuario que nos diga cuantas fichas desea
introducir y solicitamos que introduzca los datos. Después
almacenamos la variable DatoTemporal al archivo agenda con una
llamada a Write. No es posible emplear Writeln en archivos con
tipo. Cuando llamamos a Writeln sin parámetros en pantalla
se ve un salto de línea. Lo empleamos para distinguir los
diferentes registros que vamos introduciendo al
archivo.

Un archivo con tipo se entiende que está formado
por un numero determinado de datos del tipo del archivo, en
nuestro caso son registros de tipo TAgenda.

Para saber el número de registros que tiene
nuestro archivo emplearemos la función FileSize como
parámetro el alias del archivo. El resultado que nos
devuelve esta función es el nombre de registros del
archivo. Emplearemos esta función en el ejemplo siguiente
de lectura del archivo.

program LeerArchivoTipo;

uses SysUtils; // Para la función
FileExists

const

NombreArchivo = 'AGENDA.DAT';

type

TAgenda = record

Nombre, Apellidos : Shortstring;

NumTelefono : Cardinal;

end;

var

Agenda : file of TAgenda;

DatoTemporal : TAgenda;

i : Integer;

begin

Assign(Agenda, NombreArchivo); // Asignamos el
archivo

if not FileExists(NombreArchivo) then

begin

Writeln('El archivo ', NombreArchivo, ' no
existe.');

end

else

begin

Reset(Agenda); // Lo abrimos para lectura y
escritura

for i := 1 to FileSize(Agenda) do

begin

Read(Agenda, DatoTemporal);

Writeln('Nom : ', DatoTemporal.Nombre);

Writeln('Cognoms : ',
DatoTemporal.Apellidos);

Writeln('Teléfon : ',
DatoTemporal.NumTelefono);

Writeln;

end;

Close(Agenda);

end;

end.

En este caso, vamos leyendo los registros guardando los
valores a la variable temporal DatoTemporal. Después
escribimos esta información en la pantalla. Siempre que el
archivo exista, claro.

Tal como hemos comentado antes, los archivos con tipo
son archivos de acceso aleatorio y podemos escribir y leer datos
en el orden que queramos. Para poder acceder a una
posición determinada del archivo hay que emplear la
función Seek.

Esta función necesita dos
parámetros : el primero es el alias del archivo y el
segundo el número del elemento al cual queremos acceder.
Hay que tener en cuenta que el primer elemento es el cero y el
último es el valor de FileSize menos 1.

Por ejemplo, para leer el archivo en sentido inverso al
habitual, puesto que Read adelanta el cursor de lectura
automáticamente que vamos leyendo, necesitaremos la
función Seek.

program LeerAlReves;

// Aquí van las declaraciones del ejemplo
anterior : TAgenda, Agenda,

begin

Assign(Agenda, NombreArchivo); // Asignamos el
archivo

if not FileExists(NombreArchivo) then

begin

Writeln('El archivo', NombreArchivo, ' no
existe.');

end

else

begin

Reset(Agenda); // Lo abrimos para lectura y
escritura

for i := FileSize(Agenda)-1 *downto* 0
do

begin

Seek(Agenda, i); // Nos movemos al punto especificado
por i

Read(Agenda, DatoTemporal);

Writeln('Nombre : ',
DatoTemporal.Nombre);

Writeln('Apellidos : ',
DatoTemporal.Apellidos);

Writeln('Teléfono : ',
DatoTemporal.NumTelefono);

Writeln;

end;

Close(Agenda);

end;

end.

Si en algún momento queremos saber la
posición del cursor del archivo tenemos que emplear la
función FilePos y el alias del archivo. Esta
función devuelve un entero que, a diferencia de Seek,
puede estar comprendido entre 1 y el valor de
FileSize.

Archivos sin tipo

Los archivos sin tipo no tienen ningún formato
especial y por tanto podemos leer de ellos los datos que
queramos. Hay que tener en cuenta, que en ninguno de los tres
tipos de archivos se hace ningún tipo de
comprobación de los datos que se escriben o se leen. De
forma que si abrimos un archivo de un tipo determinado, por
ejemplo de tipo Longint, con un alias de tipo
Byte los datos que
leamos corresponderán a los bytes del Longint.

Las funciones Write y Read no se pueden emplear con
archivos sin tipo. Es por este motivo que tenemos que emplear las
funciones BlockWrite y BlockRead. Estas funciones leen o escriben
un numero n, o menos en caso de lectura, de bloques de
tamaño b, en bytes, que conviene especificar en las
funciones Rewrite y Reset. Si no especificamos el valor del
bloque a Rewrite o Reset por defecto vale 128 bytes.

Como ejemplo, implementaremos dos programas. En el
primero escribiremos en un archivo un string de 35 caracteres y
después un entero. En el segundo nos limitaremos a recoger
estos datos y a escribirlos en pantalla.

program EscribirArchivoSinTipo;

const

NombreArchivo = 'DATOS.DAT';

var

Datos : file;

Nombre : string[35];

Edad : Integer;

begin

Assign(Datos, NombreArchivo); // Asignamos el
archivo

Rewrite(Datos, 1); { Especificamos 1 byte como
tamaño del bloque por comodidad }

Write('Introduzca un nombre : ');
Readln(Nombre);

Write('Introduzca una edad : ');
Readln(Edad);

BlockWrite(Datos, Nombre, SizeOf(Nombre));

BlockWrite(Datos, Edad, SizeOf(Edad));

Close(Datos);

end.

Obsérvese, que especificamos el tamaño del
registro en 1 ya que después necesitaremos especificar el
tamaño de los datos que vamos a escribir. Para esto
emplearemos la función SizeOf que toma como
parámetro cualquier tipo de variable y devuelve su
tamaño en bytes. Por tanto Edad tiene un tamaño de
2 bytes y el tamaño de Nombre es de 36 bytes. Este es el
valor que se pasa como tercer parámetro y que entiende que
queremos copiar 36 bloques de un byte y después 2 bloques
de un byte. Si no hubiéramos especificado el tamaño
del bloque, se tomaría por defecto 128 bytes de forma que
al escribir el entero coparíamos 2 bloques de 128 bytes (o
sea 256 bytes). La función BlockWrite admite cualquier
tipo de variable como segundo parámetro.

Para leer, la función BlockRead funciona de forma
parecida a BlockWrite. En este caso el segundo parámetro
también puede ser de cualquier tipo, pero no una
constante.

program LeerArchivoSinTipo;

const

NombreArchivo = 'DATOS.DAT';

var

Datos : file;

Nombre : string[35];

Edad : Integer;

begin

Assign(Datos, NombreArchivo); // Asignamos el
archivo

Reset(Datos, 1); { Especificamos el tamaño del
bloque de 1 byte por comodidad }

BlockRead(Datos, Nombre, SizeOf(Nombre));

BlockRead(Datos, Edad, SizeOf(Edad));

Writeln(Nombre, ' tiene ', Edad, '
años');

Close(Datos);

end.

Otras funciones que podemos emplear para los archivos
sin tipo incluyen : FileSize, FilePos y Seek. Todas tres
funciones trabajan con el valor del bloque, el tamaño del
cual hemos especificado en Reset o Rewrite. También
podemos emplear Eof para detectar el fin del archivo.

Gestión de archivos

A diferencia de crear archivos, escribir o leer datos
también podemos realizar otras operaciones como pueden ser
eliminar un archivo o cambiarle el nombre.

Eliminar un archivo

Eliminar un archivo es tan simple como emplear la
función Erase y el alias del archivo. Hay que tener en
cuenta de que el archivo tiene que estar cerrado para evitar un
error en tiempo de ejecución. Además, cerrar un
archivo ya cerrado da error de ejecución. Para asegurarse
de que un archivo está cerrado podemos abrirlo con Reset y
cerrarlo con Close. Nuevas llamadas a Reset con un archivo ya
abierto no dan error, simplemente sitúan el cursor del
archivo al inicio. Una vez está cerrado ya podemos llamar
a Erase.

Renombrar un archivo

La función Rename permite renombrar un archivo.
Hay que tener en cuenta de que el archivo también tiene
que estar cerrado.

var

archivo : file;

begin

Assign(archivo, 'NombreViejo.dat'); // Suponemos que
existe

Rename(archivo, 'NombreNuevo.dat');

end.

Capturar errores de archivos con
IOResult

Hasta ahora hemos operado con archivos suponiendo que el
archivo existía o bien no había ningún error
durante las operaciones de lectura o de escritura.

Existe una forma muy sencilla de capturar los errores de
las operaciones con archivos mediante la variable IOResult y la
directiva de compilador {$I}

Una directiva de compilador es un comentario que justo
después de la llave hay el símbolo de dólar
$ y una o más letras. Las directivas modifican el
comportamiento del compilador en tiempo de compilación.
Después de una directiva el compilador puede modificar su
comportamiento de muchas formas. Muchas veces van asociadas a la
llamada compilación condicional, que veremos más
adelante, en otras se refiere a como se estructuran los datos,
etc.

En el caso de $I es una directiva de
activación/desactivación. Por defecto $I
está activada con lo cual los errores de entrada/salida de
archivos provocan un error de ejecución (runtime error).
Los errores runtime son irrecuperables, o sea, provocan la
finalización inmediata y anormal del programa. Muy a
menudo, el error puede ser debido al usuario con lo cual hay que
procurar de no genera un error runtime ya que inmediatamente
terminaría el programa. Podemos evitar estos errores con
la desactivación de la directiva $I. Para activar una
directiva de activación/desactivación (llamada
switch) tenemos
que incluir la siguiente directiva en medio del
código.

{$I+}

Para desactivarla :

{$I-}

Es importante reactivar la directiva $I cuando ya no sea
necesaria. Cuando está desactivada podemos acceder a la
variable IOResult. Esta variable tiene el valor cero si la
operación con archivos se ha llevado a cabo con éxito.
En caso contrario, almacenará un valor distinto de cero.
Vamos a aplicar esta técnica al programa de cambio de
nombre :

var

archivo : file;

begin

Assign(archivo, 'NombreViejo.dat'); // Assign no da
nunca error

{$I-} // Desactivamos la directiva $I

Rename(archivo, 'NombreNuevo.dat');

if IOResult <> 0 then

begin Writeln('Este archivo no existe o no se ha podido
renombrar');

end;

{$I+} // Importante reactivar la directiva cuando ya no
sea necesaria

end.

Así de simple es capturar los errores de
lectura/escritura. Suele ser habitual, por ejemplo, emplearlo en
la función Reset ya que si el archivo no existe se produce
un error que se notifica en IOResult.

Es posible referirse a la directiva $I con su nombre
largo $IOCHECKS. Además FreePascal permite la
activación y desactivación mediante on y OFF en los
nombres largos de directivas.

{$I+} // Activar

{$I-} // Desactivar

{$IOCHECKS on} // Activar, cuidado el espacio

{$IOCHECKS OFF} // Desactivar

{$IOCHECKS +} // Activar, cuidado el espacio

{$IOCHECKS -} // Desactivar

Hay más directivas de compilador que veremos
más adelante.

Funciones y
procedimientos

Diseño descendiente

Se llama diseño
descendiente el método de diseño de algoritmos que
se basa en la filosofía de "divide y vencerás".
Donde el programa se divide en partes más pequeñas
que se van desarrollando con una cierta independencia
respecto a las otras partes del programa.

De esta forma podemos definir una especie de
subprogramas o subrutinas para ser ejecutada diversas veces
dentro del programa en si. Los problemas
pueden ser descompuestos en otros problemas más
pequeños pero también más fáciles de
resolver. Esto permite, también, aprovechar mejor el
código y permite modificarlo más cómodamente
pues no habrá que modificar todo el programa, sólo
el código del subprograma.

En Pascal el diseño descendiente se lleva a cabo
con dos elementos muy importantes : las funciones y los
procedimientos.

Funciones vs Procedimientos

La diferencia más importante entre un procedimiento y
una función es el hecho de que una función es una
expresión y como a tal se puede emplear dentro de otras
expresiones. Un procedimiento, en cambio, representa una
instrucción y no puede ser empleada dentro de un contexto
de expresión.

Las funciones devuelven siempre un valor de un tipo
determinado. Ya hemos visto el caso de la función
FileExists de la unit SysUtils. Esta función devuelve un
tipo booleano por lo que podíamos incluirla dentro de una
expresión booleana como esta :

if not FileExists(NombreArchivo) then

La función Write es, por ejemplo, un
procedimiento y por tanto no es sintácticamente legal
incluirla dentro de una expresión.

Aun así, la sintaxis de Pascal se ha relajado a
lo largo del tiempo y es posible emplear una función como
si de un procedimiento se tratara. En este caso se pierde el
valor que devuelve la función aunque se ejecuta
igualmente.

Por ejemplo, si queremos que un programa se pare hasta
que el usuario pulse una tecla podemos emplear la función
Readkey de la forma siguiente :

Readkey; // El programa se parará hasta que el
usuario pulse una tecla

El carácter que devuelve ReadKey, la tecla que ha
pulsado el usuario, se perderá a menos que incluyamos
ReadKey en una expresión.

Contexto de las funciones y los
procedimientos

Las funciones y los procedimientos tienen que declararse
siempre antes del módulo principal del programa. Si una
función, o procedimiento, se emplea en otra ésta
tendrá que estar declarada antes o bien encontrarse en una
unit de la clausula uses.

Funciones

Tanto las funciones como los procedimientos tienen que
tener un nombre que las identifique, que seguirá las
reglas de los identificadores de Pascal, y opcionalmente pueden
tener un conjunto de parámetros que modifique de alguna
forma el comportamiento de la función.

La declaración de una función sigue el
modelo
siguiente :

function NombreFuncion ( listaParametros ) :
tipoRetorno;

NombreFuncion es el identificador de la función y
es el nombre que emplearemos para llamarla. ListaParametros
representa el conjunto de parámetros que se pueden pasar a
la función. Para declarar más de un
parámetro los tenemos que separar entre puntos y comas y
teniendo en cuenta de que el último parámetro no
lleva nunca punto y coma. Estos parámetros reciben el
nombre de parámetros formales y es importante
distinguirlos de parámetros reales que representan los
parámetros con los que se ha llamado la función.
Finalmente, tipoRetorno representa el tipo de datos que devuelve
la función.

Vamos a definir una función muy sencilla max que
devuelva el valor del máximo de dos parámetros
enteros a y b.

function max(a : integer; b : integer) :
integer;

{ Podíamos haber escrito los parámetros
así : (a, b : integer) }

begin

if a >= b then

begin

max := a

end

else

begin

max := b;

end;

end;

Obsérvese con detenimiento el código de la
función max. Para empezar, las sentencias van entre un
begin y un end con punto y coma ya que no es el bloque principal
del programa. Nótese también que para especificar
el valor que tiene que devolver la función hacemos una
asignación al identificador de la función,
max.

Como ejemplo de empleo de esta
función, haremos un programa que pida dos enteros al
usuario e indique cual de los dos es máximo.

program Funciones;

var

n, m : integer;

function max(a, b : integer) :
integer;

begin

if a >= b then

begin

max := a

end

else

begin

max := b;

end;

end;

begin

Write('Introduzca el valor entero M : ');
readln(m);

Write('Introduzca el valor entero N : ');
readln(n);

Writeln('El máximo entre ', M, ' y ', N, ' es ',
max(m, n));

end.

Como se puede ver en la última línea del
código, a diferencia de la declaración y tal como
hemos llamado hasta ahora las funciones o procedimientos con
más de un parámetro, hay que separar los distintos
parámetros entre comas. Hay que tener en cuenta que los
parámetros reales m y n del ejemplo no tienen nada que ver
con los parámetros formales de max (a y b). De hecho los
parámetros formales sirven sólo para implementar la
función e indicar el orden de estos. No hay que decir que
en vez de una variable podemos emplear un literal.

Vamos a definir una nueva función min a partir de
max.

function min(a, b : integer);

begin

min := -max(-a, -b);

end;

Esta implementación se basa en que los
números negativos se ordenan al revés que los
positivos, por lo que cambiando dos veces el signo es
suficiente.

Es importante siempre incluir una asignación al
valor de retorno de la función ya que en caso contrario el
valor de la función podría quedar indeterminado.
Una forma segura es incluir la asignación al final de la
función, si es posible, o asegurarse de que en todos los
casos posibles de salida hay al menos una asignación de
este tipo.

Procedimientos

Los procedimientos se declaran de forma parecida a las
funciones pero sin especificar el tipo de salida ya que no
tienen. Por ejemplo el procedimiento Creditos escribiría
unos créditos en la pantalla :

procedure Creditos;

begin

Writeln('(C) Manual de FreePascal 1.00'

Writeln('Roger Ferrer Ibáñez –
2001');

end;

En vez de emplear la palabra reservada function
emplearemos procedure. Tampoco hay que realizar ninguna
asignación al identificador del procedimiento pues los
procedimientos no devuelven nada. Tanto en funciones como en
procedimientos, en caso de que no tengamos parámetros no
habrá que poner los paréntesis. En el caso de las
funciones antes del punto y coma tendrá que ir el tipo de
retorno.

Como ejemplo tenemos la definición de ReadKey
es :

function Readkey : Char;

En el momento de llamar a una función o
procedimiento sin parámetros podemos omitir los
paréntesis de parámetros o, si los queremos
escribir, sin nada en medio (sin contar los caracteres en
blanco).

Creditos; // Ambas llamadas son correctas

Creditos(); // Esta llamada tiene una sintaxis
más parecida a C

Tipos de parámetros

Hasta ahora hemos visto que una función puede
devolver un valor y que por tanto la podemos incluir dentro de
una expresión. Muchas veces nos puede interesar
especificar parámetros que impliquen un cambio en estos
parámetros y esto no es posible de implementar en una
simple función.

Supongamos un procedimientos en el cual pasando dos
parámetros de tipo entero queremos que se intercambien los
valores. Una forma de hacerlo es mediante sumas y restas.
Nosotros implementaremos un caso más general mediante el
uso de una tercera variable temporal.

procedure Intercambiar(a, b : integer);

var

temporal : integer;

begin

temporal := b;

b := a;

a := temporal;

end;

Si escribimos un programa para verificar si el
procedimiento funciona, nos daremos cuenta de que los
parámetros no se han modificado después de la
llamada. O sea, no ha habido intercambio.

a := 10;

b := 24;

Intercambiar(a, b);

// Ahora a aún vale 10 y b aún vale
24 !!!

Esto es porque hemos empleado un tipo de
parámetro llamado parámetro por valor. Los
parámetros por valor no son nada más que una copia
de los parámetros originales que se crean sólo para
la función o procedimiento. Una vez termina esta copia
desaparece, por lo que toda modificación que hagamos
dentro de la función o procedimiento se
perderá.

Para resolver este problema podemos emplear
parámetros por referencia. En este caso la función
o procedimiento conoce exactamente la posición de memoria
del parámetro por lo que las modificaciones que hagamos
dentro de la función o procedimiento permanecerán
después de la llamada. Para indicar que un
parámetro es por referencia incluiremos la palabra
reservada var delante del parámetro. El procedimiento
intercambiar quedaría así :

procedure Intercambiar(var a : integer; var
b : integer);

{ Podíamos haber escrito como parámetros
(var a, b : integer); }

var

temporal : integer;

begin

temporal := b;

b := a;

a := temporal;

end;

Una de las ventajas de los parámetros por
referencia es que no es necesario crear una copia cada vez que se
llama la función o procedimiento, con lo cual ahorramos
algo de tiempo sobretodo si el dato es grande (como arrays muy
extensos o registros con muchos campos). El riesgo, pero, es
que cualquier modificación (por accidental que sea)
permanecerá después de la llamada. Para minimizar
este riesgo disponemos de un tercer tipo de parámetro
llamado parámetro constante. En vez de var llevan la
palabra reservada const. El compilador no permitirá
ningún tipo de modificación fortuita de los datos
pero los parámetros se pasaran por referencia y por tanto
la función o procedimiento será más
eficiente.

En el caso anterior intentar compilar procedure
Intercambiar(const a, b : integer); daría un error de
compilación pues entendería a y b como
constantes.

Es importante remarcar que en caso de pasar variables
por referencia no es posible especificar literales o constantes
en este tipo de parámetros ya que si sufrieran alguna
modificación dentro de la función o procedimiento
no se podría almacenar en ningún sitio.
Además, el compilador es más estricto a la hora de
evaluar la compatibilidad del tipo. Por ejemplo si el
parámetro por referencia es de tipo Integer y pasamos una
variable Byte el
compilador lo considerará ilegal aunque ambos sean enteros
y, en general, un Byte
es compatible con un Integer.

En el caso de parámetros constantes, el
compilador no es tan estricto y permite pasar literales y
constantes.

Variables locales y globales

En el ejemplo anterior de intercambiar hemos definido
una variable temporal de tipo entero dentro del cuerpo del
procedimiento. Estas variables declaradas dentro de una
función o procedimiento reciben el nombre de variables
locales y sólo son accesibles dentro de las funciones o
procedimientos. Por lo que, intentar asignar un valor a temporal
fuera de intercambiar es sintácticamente incorrecto ya que
no es visible en este ámbito.

Las variables declaradas al principio del programa
reciben el nombre de variables globales. Estas variables se
pueden emplear tanto en funciones y procedimientos como en el
bloque principal.

También el bloque principal puede tener sus
propias variables locales si las declaramos justo antes de
éste.

Esta consideración para variables es extensiva a
tipos y constantes.

program Variables;

var

a : integer;

procedure Asignar;

var

b, c : integer;

begin

a := 15; // CORRECTO a es una variable
global

b := 10; // CORRECTO b es una variable
local

c := b; // CORRECTO c es una variable
local

d := b; { INCORRECTO d es una variable del bloque
principal que no se puede acceder desde aquí }

end;

var

b, d : integer;

begin

a := 10; // CORRECTO a es una variable
global

b := 5; // CORRECTO b es una variable local del
bloque principal

Asignar; // ATENCI"N!! b no cambiará su valor
después de la llamada (b=5)

c := 4; { INCORRECTO!! c no es accesible desde
aquí ya que es una variable local de Asignar }

end.

A banda de los diferentes errores sintácticos hay
que remarcar la diferencia entre b del procedimiento Asignar y b
del bloque principal. Ambas variables son totalmente
independientes entre ellas. Este es el único caso en que
es posible la existencia de dos variables con nombres iguales. Si
la variable b hubiera sido declarada global, juntamente con a, el
compilador no nos hubiera permitido declarar b en ningún
otro punto pues habría una redeclaración de
identificador y esto no es posible pues el compilador no
sabría como distinguir las variables. Veremos más
adelante, que en diferentes units se pueden declarar los mismos
identificadores pues se puede especificar que identificador
queremos.

Partes: 1, 2, 3, 4, 5, 6
 Página anterior Volver al principio del trabajoPágina siguiente 

Nota al lector: es posible que esta página no contenga todos los componentes del trabajo original (pies de página, avanzadas formulas matemáticas, esquemas o tablas complejas, etc.). Recuerde que para ver el trabajo en su versión original completa, puede descargarlo desde el menú superior.

Todos los documentos disponibles en este sitio expresan los puntos de vista de sus respectivos autores y no de Monografias.com. El objetivo de Monografias.com es poner el conocimiento a disposición de toda su comunidad. Queda bajo la responsabilidad de cada lector el eventual uso que se le de a esta información. Asimismo, es obligatoria la cita del autor del contenido y de Monografias.com como fuentes de información.

Categorias
Newsletter