Curso de Pascal. Tema 11: Manejo de ficheros.

Tema 11.3. Manejo de ficheros (3) - Ficheros con tipo.

Hemos visto cómo acceder a los ficheros de texto, tanto para leerlos como para escribir en ellos. Ahora 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).

En los de, texto se podría considerar que estaban formados por elementos iguales, de tipo "char", pero ahora vamos a llegar más allá, porque un fichero formado por datos de tipo "record" sería lo ideal para empezar a crear nuestra propia agenda.

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).

La idea es sencilla: si cada ficha ocupa 25 bytes, y queremos leer la número 8, bastaría con "saltarnos" 25*7=175 bytes.

Pero Turbo Pascal (y muchos de los compiladores que nacieron después de él,como Free 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...

Primero vamos a introducir varias fichas en un fichero con tipo:

 {--------------------------}
 {  Ejemplo en Pascal:      }
 {                          }
 {    Crea un fichero "con  }
 {    tipo"                 }
 {    CREAFT.PAS            }
 {                          }
 {  Este fuente procede de  }
 {  CUPAS, curso de Pascal  }
 {  por Nacho Cabanes       }
 {                          }
 {  Comprobado con:         }
 {    - Free Pascal 2.2.0w  }
 {    - Turbo Pascal 7.0    }
 {--------------------------}

 program IntroduceDatos;

 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.  


Debería resultar fácil. 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".

Ahora vamos a ver cómo leeríamos sólo la tercera ficha de este fichero de datos que acabamos de crear:

 {--------------------------}
 {  Ejemplo en Pascal:      }
 {                          }
 {    Lee de un fichero     }
 {    "con tipo"            }
 {    LEEFT.PAS             }
 {                          }
 {  Este fuente procede de  }
 {  CUPAS, curso de Pascal  }
 {  por Nacho Cabanes       }
 {                          }
 {  Comprobado con:         }
 {    - Free Pascal 2.2.0w  }
 {    - Turbo Pascal 7.0    }
 {    - Turbo Pascal 5.0    }
 {    - Surpas 1.00         }
 {--------------------------}

 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. 

Espero que el listado sea autoexplicativo. La única cosa que merece la pena comentar es eso del "seek(fichero, 2)": La posición de las fichas dentro de un fichero de empieza a numerar en 0, que corresponderá a la primera posición. Así, accederemos a la segunda posición con un 1, a la tercera con un 2, y en general a la "n" con "seek(fichero,n-1)".


Ejemplo: mini-agenda, con ficheros.

A partir de la mini-agenda que comenzamos en el tema 7 y que ampliamos en el tema 8, podríamos mejorarla para que los datos se guarden en un fichero con tipo. Los cambios podrían ser:

  1. Se cargan los datos al principio de la sesión, pero sólo si existe el fichero de datos; si el fichero no existe, el programa no debe fallar, sino, en todo caso, avisar.
  2. Tras cada modificación (por ahora, sólo tras añadir una nueva ficha), se vuelcan todos los datos a fichero, de forma que en caso de pérdida de corriente eléctrica o cualquier otro problema, todo esté guardado.


Podría ser algo como:

{--------------------------}
{  Ejemplo en Pascal:      }
{                          }
{    Ejemplo de "Agenda":  }
{    Permite añadir datos, }
{    mostrar, buscar.      }
{    Usa funciones.        }
{    AGENDA3.PAS           }
{                          }
{  Este fuente procede de  }
{  CUPAS, curso de Pascal  }
{  por Nacho Cabanes       }
{                          }
{  Comprobado con:         }
{    - Free Pascal 2.4.0   }
{--------------------------}

program Agenda3;

type
    tipoPersona = record
        nombre: string;
        email: string;
        anyoNacimiento: integer;
    end;

const
    capacidad = 1000;

var
    gente: array[1..capacidad] of tipoPersona;  { Los datos }
    cantidad: integer;       { Cantidad de datos existentes }
    terminado: boolean;

procedure MostrarMenu;
begin
    WriteLn('Agenda');
    WriteLn;
    WriteLn('1- Añadir una nueva persona');
    WriteLn('2- Ver nombres de todos');
    WriteLn('3- Buscar una persona');
    WriteLn('0- Salir');
end;

function LeerOpcion: integer;
var
    opcion: integer;
begin
    Write('Escoja una opción: ');
    ReadLn(opcion);
    WriteLn;
    if (opcion = 0) then terminado := true;
    LeerOpcion := opcion;    
end;


procedure CargarDatos;
var
    fichero: file of tipoPersona;
    i: integer;
begin
    assign(fichero, 'agenda.dat');
    {$I-} 
    reset(fichero);
    {$I+}
    if ioResult <> 0 then  
        WriteLn('No había fichero de datos. Se creará.')
    else
    begin
        cantidad := filesize(fichero);
        for i := 1 to cantidad do
            Read(fichero, gente[i]);
        close(fichero);
    end;
end;

procedure GrabarDatos;
var
    fichero: file of tipoPersona;
    i: integer;
begin
    assign(fichero, 'agenda.dat');
    {$I-} 
    rewrite(fichero);
    {$I+}
    if ioResult <> 0 then  
        WriteLn('No se ha podido grabar!')
    else
    begin
        for i := 1 to cantidad do
            Write(fichero, gente[i]);
        close(fichero);
    end;
end;

procedure NuevoDato;
begin
    if (cantidad < capacidad) then
    begin
        inc(cantidad);
        WriteLn('Introduciendo la persona ', cantidad);

        Write('Introduzca el nombre: ');
        ReadLn(gente[cantidad].nombre);

        Write('Introduzca el correo electrónico: ');
        ReadLn(gente[cantidad].email);

        Write('Introduzca el año de nacimiento: ');
        ReadLn(gente[cantidad].anyoNacimiento);

         WriteLn;
         GrabarDatos;
     end
     else
            WriteLn('Base de datos llena');
end;

procedure MostrarDatos;
var
    i: integer;
begin
    if cantidad = 0 then
        WriteLn('No hay datos')
    else
        for i := 1 to cantidad do
            WriteLn(i, ' ', gente[i].nombre);
    WriteLn;
end;

procedure BuscarDatos;
var
    textoBuscar: string;
    encontrado: boolean;
    i: integer;
begin
    Write('¿Qué texto busca? ');
    ReadLn( textoBuscar );
    encontrado := false;
    for i := 1 to cantidad do
        if pos (textoBuscar, gente[i].nombre) > 0 then
        begin
            encontrado := true;
            WriteLn( i,' - Nombre: ', gente[i].nombre,
              ', Email: ', gente[i].email,
              ', Nacido en: ', gente[i].anyoNacimiento);
        end;
    if not encontrado then
        WriteLn('No se ha encontrado.');
    WriteLn;
end;

procedure AvisarFin;
begin
    WriteLn;
    WriteLn('Saliendo...');
    WriteLn;
end;

procedure AvisarError;
begin
    WriteLn;
    WriteLn('Opción incorrecta!');
    WriteLn;
end;


{Cuerpo del programa principal}
begin
    terminado := false;
    cantidad := 0;
    CargarDatos;
    repeat

        MostrarMenu;
        case LeerOpcion of
            1: NuevoDato;
            2: MostrarDatos;
            3: BuscarDatos;
            0: AvisarFin;                
            else AvisarError;
        end;  { Fin de "case" }

    until terminado;
end.