14 septiembre 2014

Pascal - 4c: Records (registros)

4.3. Registros (records)

4.3.1. ¿Qué es un registro?

La principal limitación de un array es que todos los datos que contiene deben ser del mismo tipo. Pero a veces nos interesa agrupar datos de distinta naturaleza, como pueden ser el nombre y la edad de una persona, que serían del tipo string y byte, respectivamente. En ese caso, podemos emplear los records o registros, que se definen indicando el nombre y el tipo de cada dato individual (cada campo), y se accede a estos campos indicando el nombre de la variable y el nombre del campo, separados por un punto:
(* RECORD1.PAS, Contacto con los "record" *)
(* Parte de CUPAS5, por Nacho Cabanes     *)
 
program Record1;
 
var
    dato: record
        nombre: string[30];
        edad: byte;
    end;
 
begin
      dato.nombre := 'Ignacio';
      dato.edad := 23;
      write('El nombre es ', dato.nombre );
      write(' y la edad ', dato.edad);
end.
 
(* Resultado:
El nombre es Ignacio y la edad 23
*)
 
La peculiaridad en la definición de un "record" es la aparición de una palabra end después de los nombres de los campos, lo que indica que hemos terminado de enumerar éstos.

4.3.2. Abreviando con "with"

Puede parecer engorroso el hecho de escribir "dato." antes de cada campo. También hay una forma de solucionarlo: cuando vamos a realizar varias operaciones sobre los campos de un mismo registro (record), empleamos la orden with, con la que el programa anterior quedaría
(* RECORD2.PAS, "record" y "with"     *)
(* Parte de CUPAS5, por Nacho Cabanes *)
 
program Record2;
var
    dato: record
        nombre: string[30];
        edad: byte;
    end;
 
begin
    with dato do
    begin
        nombre := 'Ignacio';
        edad := 23;
        write('El nombre es ', nombre );
        write(' y la edad ', edad);
    end;
end.
 
(* Resultado:
El nombre es Ignacio y la edad 23
*)
 
En este caso tenemos un nuevo bloque en el cuerpo del programa, delimitado por el "begin" y el "end" situados más a la derecha, y equivale a decir "en toda esta parte del programa me estoy refiriendo a la variable dato". Así, podemos nombrar los campos que queremos modificar o escribir, sin necesidad de repetir a qué variable pertenecen.
Nota: aquí vuelve a aparecer la escritura indentada: para conseguir una mayor legibilidad, escribimos un poco más a la derecha todo lo que depende de la orden "with". No es algo obligatorio, pero sí recomendable.

4.3.3. Arrays de registros

Es habitual no usar un único registro, sino un conjunto de ellos, de modo que tendríamos un array de registros. Por ejemplo, podríamos guardar los datos de 5 personas con una estructura como ésta:
(* ARR_REC.PAS, "array" de varios "record" *)
(* Parte de CUPAS5, por Nacho Cabanes      *)
 
program RecordArray;
 
var
    datos: array [1..10] of 
        record
            nombre: string[30];
            edad: byte;
        end;
 
begin
    datos[1].nombre := 'Ignacio';
    datos[1].edad := 23;
    write('El primer nombre es ', datos[1].nombre );
    write(' y la primera edad ', datos[1].edad);
end.
 
(* Resultado:
El primer nombre es Ignacio y la primera edad 23
*)
 

Podemos usar un array de registros para crear una pequeña agenda, que nos permita guardar datos de personas y ver los datos almacenados, empleando un menú básico:
(* AGENDA0.PAS, Ejemplo de "Agenda":  *)
(* Permite añadir datos y mostrarlos  *)
(* Parte de CUPAS5, por Nacho Cabanes *)
 
program Agenda0;
 
var
    gente: array [1..1000] of              { Los datos }
        record
            nombre: string;
            email: string;
            anyoNacimiento: integer;
        end;
    cantidad: integer;       { Cantidad de datos existentes }
    opcion: integer;                      { Opción escogida }
    i: integer;                         { Para bucles "for" }
 
{Cuerpo del programa principal}
begin
    cantidad := 0;
    repeat
        WriteLn('Agenda');
        WriteLn;
        WriteLn('1- Añadir una nueva persona');
        WriteLn('2- Ver nombres de todos');
        WriteLn('0- Salir');
        Write('Escoja una opción: ');
        ReadLn(opcion);
        WriteLn;
 
        case opcion of
            1: { Añadir datos de una persona }
                if cantidad < 1000 then
                begin
                    cantidad := cantidad + 1;
                    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;
                 end
                 else
                    WriteLn('Base de datos llena');
 
            2: { Ver nombres de todos }
                begin
                if cantidad = 0 then
                    WriteLn('No hay datos')
                else
                    for i := 1 to cantidad do
                        WriteLn(i, ' ', gente[i].nombre);
                WriteLn;
                end;
 
            0: { Salir de la aplicación }
                begin
                WriteLn;
                WriteLn('Saliendo...');
                WriteLn;
                end;
 
             else
                begin
                WriteLn;
                WriteLn('Opción incorrecta!');
                WriteLn;
                end;
        end;  { Fin de "case" }
 
    until opcion = 0;
end.
 
Este esqueleto es fácil de ampliar. Por ejemplo, podemos añadir la opción de buscar cualquier ficha que contenga un cierto texto, usando "pos" para ver si aparece en cualquier posición y un "boolean" para llevar la cuenta de si hemos encontrado alguna ficha correcta o no:
(* AGENDA0B.PAS, Ejemplo de "Agenda"         *)
(* Permite añadir datos, mostrarlos y buscar *)
(* Parte de CUPAS5, por Nacho Cabanes        *)
 
program Agenda0b;
 
var
    gente: array [1..1000] of              { Los datos }
        record
            nombre: string;
            email: string;
            anyoNacimiento: integer;
        end;
    cantidad: integer;       { Cantidad de datos existentes }
    opcion: integer;                      { Opción escogida }
    i: integer;                         { Para bucles "for" }
    textoBuscar: string;                   { Para búsquedas }
    encontrado: boolean;                              { Idem }
 
{Cuerpo del programa principal}
begin
    cantidad := 0;
    repeat
        WriteLn('Agenda');
        WriteLn;
        WriteLn('1- Añadir una nueva persona');
        WriteLn('2- Ver nombres de todos');
        WriteLn('3- Buscar una persona');
        WriteLn('0- Salir');
        Write('Escoja una opción: ');
        ReadLn(opcion);
        WriteLn;
 
        case opcion of
            1: { Añadir datos de una persona }
                if cantidad < 1000 then
                begin
                    cantidad := cantidad + 1;
                    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;
                 end
                 else
                    WriteLn('Base de datos llena');
 
            2: { Ver nombres de todos }
                begin
                if cantidad = 0 then
                    WriteLn('No hay datos')
                else
                    for i := 1 to cantidad do
                        WriteLn(i, ' ', gente[i].nombre);
                WriteLn;
                end;
 
            3: { Buscar una persona }
                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;
 
            0: { Salir de la aplicación }
                begin
                WriteLn;
                WriteLn('Saliendo...');
                WriteLn;
                end;
 
             else
                begin
                WriteLn;
                WriteLn('Opción incorrecta!');
                WriteLn;
                end;
        end;  { Fin de "case" }
 
    until opcion = 0;
end.
 

4.3.4. Registros variantes

Hasta ahora hemos visto los registros (records), utilizando campos fijos, pero no tiene por qué ser necesariamente así. Tenemos a nuestra disposición los registros variantes, en los que con un "case" podemos elegir unos campos u otros. La mejor forma de entenderlos es con un ejemplo.
(* REGVAR.PAS, registros variantes    *)
(* Parte de CUPAS5, por Nacho Cabanes *)
 
program RegistrosVariantes;
 
var
    punto: record  
          case cartesiano: boolean of  
              true : (X,Y,Z : real);  
              false : (R,theta,phi : real);
          end;  
 
begin
    punto.cartesiano := true;
    punto.x := 5;
    punto.y := -1;
    punto.z := 2.3;
    writeLn('Con coordenadas cartesianas, x vale ',punto.x:2:1);
 
    punto.cartesiano := false;
    punto.r := 12.3;
    punto.theta := -15;
    punto.phi := 45;
    writeLn('Con coordenadas esfericas, phi vale ',punto.phi:2:1);
end. 
 
 
(* Resultado:
Con coordenadas cartesianas, x vale 5.0
Con coordenadas esfericas, phi vale 45.0
*)
 
Realmente, no es necesario usar un campo para cambiar entre un juego de valores y otro, porque realmente se trata de varios conjuntos de capas que "se solapan", ocupando el mismo espacio en memoria:
(* REGVAR2.PAS, registros variantes (2) *)
(* Parte de CUPAS5, por Nacho Cabanes   *)
 
program RegistrosVariantes2;
 
var
    punto: record  
          case boolean of  
              true : (X,Y,Z : real);  
              false : (R,theta,phi : real);
          end;  
 
begin
    punto.x := 5;
    punto.y := -1;
    punto.z := 2.3;
    writeLn('Con coordenadas cartesianas, x vale ',punto.x:2:1);
 
    punto.r := 12.3;
    punto.theta := -15;
    punto.phi := 45;
    writeLn('Con coordenadas esfe ricas, phi vale ',punto.phi:2:1);
 
    writeLn('Como curiosidad, x vale ',punto.x:2:1);
end. 
 
 
(* Resultado:
Con coordenadas cartesianas, x vale 5.0
Con coordenadas esfericas, phi vale 45.0
Como curiosidad, x vale 12.3
*)
 
El riesgo, como se ve en este último ejemplo, es que al modificar un juego de valores, el otro se altera a la vez, ya que están solapados en memoria, pero los datos posiblemente no tendrán sentido si accedemos a ellos usando campos distintos de los que habíamos empleado para guardar la información.