Curso-lenguaje-C/fundamentos-programacion/PEC4/README.md
2024-04-19 18:40:15 +02:00

18 KiB

PEC 4

Volver a la página principal de "Fundamentos de la Programación"

Índice

11 Tipos de datos estructurados: tupla

Las tuplas son colecciones heterogéneas de datos, en las que cada elemento de la colección puede ser de un tipo diferente.

La diferencia básica entre los vectores o matrices y las tuplas es esto, que en las tuplas los elementos pueden ser de distinto tipo. En C, las tuplas no existen como tipo de dato, pero se pueden simular con estructuras.

11.1 Tipos de dato tupla

Un tupla (record o struct en inglés) es un objeto que contiene otros objetos que denominaremos campos. Cada uno de los campos tiene un nombre para identificarlo y es de un tipo de datos.

Por ejemplo, un documento de identidad puede ser representado como una tupla con los campos nombre, apellidos, día de nacimiento, mes de nacimiento, año de nacimiento, número de documento, dirección, teléfono, email, etc. Cada uno de estos campos es un tipo de dato diferente.

En lenguaje algoritmico y en C:

algorithm alg12_01
  var
    prename: string;
    name: string;
    birthDay: integer;
    birthMonth: integer;
    birthYear: integer;
    idNumber: integer;
    idChar: char;
  end var
end algorithm

#include <stdio.h>

#define MAX_NAME_LEN 25

int main(int argc, char** argv) {
  /* Variable definition*/
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  int birthDay;
  int birthMonth;
  int birthYear;
  int idNumber;
  char idChar;
}

Con esta información sería complicado controlar las distintas variables, por que se pueden mezclar los datos. Por ejemplo, si se quiere cambiar el nombre de una persona, se tendría que cambiar el valor de la variable name y no se podría saber si se ha cambiado correctamente.

Para solucionar este problema, se pueden agrupar los datos en una estructura. En C, se puede definir una estructura de la siguiente manera:

type
  tPeople = record
  prename: string;
  name: string;
  birthDay: integer;
  birthMonth: integer;
  birthYear: integer;
  idNumber: integer;
  idChar: char;
  end record
end type

algorithm alg12_02
  var
    you: tPeople;
  end var
end algorithm

#include <stdio.h>

#define MAX_NAME_LEN 25

typedef struct {
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  int birthDay;
  int birthMonth;
  int birthYear;
  int idNumber;
  char idChar;
} tPeople;

int main(int argc, char** argv) {
  /* Variable definition */
  tPeople you;
}

Ahora, para acceder a un campo de una variable de tipo estructurada se emplea el nombre de la variable y el nombre del campo unido por el delimitador .. Por ejemplo, para acceder al campo name de la variable you se escribe you.name.

Por ejemplo, para rellenar una ficha de una persona:

type
  tPeople = record
    prename: string;
    name: string;
    birthDay: integer;
    birthMonth: integer;
    birthYear: integer;
    idNumber: integer;
    idChar: char;
  end record
end type

algorithm loadData
  var
    you: tPeople;
  end var

  {read phase}
  you.prename := readString();
  you.name := readString();
  you.birthDay := readInteger();
  you.birthMonth := readInteger();
  you.birthYear := readInteger();
  you.idNumber := readInteger();
  you.idChar := readChar();

  {write phase}
  writeString(you.name);
  writeString(you.prename);
  writeInteger(you.idNumber);
  writeChar(you.idChar);
  writeInteger(you.birthDay);
  writeInteger(you.birthMonth);
  writeInteger(you.birthYear);
end algorithm


#include <stdio.h>
#define MAX_NAME_LEN 25

/* Type definition */
typedef struct {
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  int birthDay;
  int birthMonth;
  int birthYear;
  int idNumber;
  char idChar;
} tPeople;

int main(int argc, char** argv) {

  /* Variable definition */
  tPeople you;

  /*read phase*/
  scanf("%s %s %d %d %d %d %c", you.prename, you.name, &you.birthDay, &you.birthMonth, &you.birthYear,
  &you.idNumber, &you.idChar);

  /*write phase*/
  printf("Name: %s, %s \n", you.name, you.prename);
  printf("Id: %d-%c \n", you.idNumber, you.idChar);
  printf("BirthDay: %d - %d - %d \n", you.birthDay, you.birthMonth, you.birthYear);
  return 0;
}

Otro ejemplo, para ordenar los datos de dos personas:

type
  tPeople = record
    prename: string;
    name: string;
    birthDay: integer;
    birthMonth: integer;
    birthYear: integer;
    idNumber: integer;
    idChar: char;
  end record
end type

algorithm ageOrder
  var
    old, young, peopleAux: tPeople;
  end var

  {read phase rst tPeople}
  old.prename := readString();
  old.name := readString();
  old.birthDay := readInteger();
  old.birthMonth := readInteger();
  old.birthYear := readInteger();
  old.idNumber := readInteger();
  old.idChar := readChar();

  {read phase second tPeople}
  young.prename := readString();
  young.name := readString();
  young.birthDay := readInteger();
  young.birthMonth := readInteger();
  young.birthYear := readInteger();
  young.idNumber := readInteger();
  young.idChar := readChar();

  {age sort phase}
  if (old.birthYear > young.birthYear) or (old.birthYear = young.birthYear and old.birthMonth > young.birthMonth) or (old.birthYear = young.birthYear and old.birthMonth = young.birthMonth and old.birthDay > young.birthDay) then

    {copy old to peopleAux}
    peopleAux := old;

    {copy young to old}
    old := young;

    {copy peopleAux to young}
    young := peopleAux;

  end if

  {write phase old tPeople}
  writeString(old.name);
  writeString(old.prename);
  writeInteger(old.idNumber);
  writeChar(old.idChar);
  writeInteger(old.birthDay);
  writeInteger(old.birthMonth);
  writeInteger(old.birthYear);

  {write phase young tPeople}
  writeString(young.name);
  writeString(young.prename);
  writeInteger(young.idNumber);
  writeChar(young.idChar);
  writeInteger(young.birthDay);
  writeInteger(young.birthMonth);
  writeInteger(young.birthYear);
end algorithm

#include <stdio.h>
#include <string.h>

#define MAX_NAME_LEN 25

/* Type definition */
typedef struct {
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  int birthDay;
  int birthMonth;
  int birthYear;
  int idNumber;
  char idChar;
} tPeople;

int main(int argc, char** argv) {

  /* Variable definition */
  tPeople young, old, peopleAux;
  /*read phase first tPeople*/
  scanf("%s %s %d %d %d %d %c", old.prename, old.name, &old.birthDay, &old.birthMonth, &old.birthYear,
  &old.idNumber, &old.idChar);
  /*read phase second tPeople*/
  scanf("%s %s %d %d %d %d %c", young.prename, young.name, &young.birthDay, &young.birthMonth,
  &young.birthYear, &young.idNumber, &young.idChar);
  /* age sort phase */
  if ( (old.birthYear > young.birthYear) ||
    ( (old.birthYear == young.birthYear) && (old.birthMonth > young.birthMonth))
  || ((old.birthYear == young.birthYear) && (old.birthMonth == young.birthMonth) && ( old.birthDay >
  young.birthDay))) {
  /*copy old to peopleAux*/
  strcpy(peopleAux.prename, old.prename);
  strcpy(peopleAux.name, old.name);
  peopleAux.birthDay = old.birthDay;
  peopleAux.birthMonth = old.birthMonth;
  peopleAux.birthYear = old.birthYear;
  peopleAux.idNumber = old.idNumber;
  peopleAux.idChar = old.idChar;
  /*copy young to old*/
  strcpy(old.prename,young.prename);
  strcpy(old.name,young.name);
  old.birthDay = young.birthDay;
  old.birthMonth = young.birthMonth;
  old.birthYear = young.birthYear;
  old.idNumber = young.idNumber;
  old.idChar = young.idChar;
  /*copy peopleAux to young*/
  strcpy(young.prename, peopleAux.prename);
  strcpy(young.name, peopleAux.name);
  young.birthDay = peopleAux.birthDay;
  young.birthMonth = peopleAux.birthMonth;
  young.birthYear = peopleAux.birthYear;
  young.idNumber = peopleAux.idNumber;
  young.idChar = peopleAux.idChar;
  }

  /*write phase old tPeople*/
  printf("Name: %s, %s \n", old.name, old.prename);
  printf("Id: %d-%c \n", old.idNumber, old.idChar);
  printf("BirthDay: %d - %d - %d \n", old.birthDay, old.birthMonth, old.birthYear);
  /*write phase young tPeople*/
  printf("Name: %s, %s \n", young.name, young.prename);
  printf("Id: %d-%c \n", young.idNumber, young.idChar);
  printf("BirthDay: %d - %d - %d \n", young.birthDay, young.birthMonth, young.birthYear);
  return 0;
}

Las tuplas no se pueden copiar enteras, se debe copiar campo a campo.

11.2 Representación en memoria

Todos los campos de un dato estructurado se mantienen en la memoria ocupando posiciones consecutivas. La dirección de memoria de un dato estructurado es la dirección de memoria del primer campo.

Por ejemplo:

type
  tDate = record
    day: integer;
    month: integer;
    year: integer;
  end record
end type

algorithm alg12_05
  var
    today: tDate;
  end var
end algorithm

#include <stdio.h>

/* Types definition */
typedef struct {
int day;
int month;
int year;
} tDate;
int main(int argc, char** argv) {
  /* Variable definition */
  tDate today;
}

La variable today ocupa 12 bytes en memoria, 4 bytes para cada campo. La dirección de memoria de today es la dirección de memoria del campo day.

Debe comprenderse que el lugar dentro de la tupla donde comienza un determinado campo no tiene ninguna relación con el tipo de este campo, solo depende del espacio ocupado por los campos anteriores.

Si hubiéramos definido una tupla para guardar documentos de identidad, los dos campos serían de tipos diferentes:

type
  tDni = record
  idNumber: integer;
  idChar: char;
  end record
end type

algorithm alg12_06
  var
    identification: tDni;
  end var
end algorithm

#include <stdio.h>

/* Type definition*/
typedef struct {

  int idNumber;
  char idChar;
} tDni;

int main(int argc, char** argv) {
  /* Variable definition */
  tDni identification;
}

Cada campo estaría almacenado ocupando las posiciones correspondientes a su tipo base. En este caso, el campo idNumber ocuparía 4 bytes y el campo idChar ocuparía 1 byte. La dirección de memoria de identification sería la dirección de memoria del campo idNumber.

11.3 Estructuras complejas

Hemos visto estructuras simples, pero se pueden hacer estructuras más complejas.

11.3.1 Tuplas que contienen tuplas

Cuando se define una tupla no existe ninguna limitación para declarar sus campos. Nada impide definir una tupla que contenga otra tupla.

Por ejemplo:

type
  tDate = record
    day: integer;
    month: integer;
    year: integer;
  end record
  tDni = record
    idNumber: integer;
    idChar: char;
  end record
  tPeople = record
    prename: string;
    name: string;
    birth: tDate;
    id: tDni;
  end record
end type

algorithm alg12_07
  var
    you: tPeople;
  end var
end algorithm

#include <stdio.h>
#define MAX_NAME_LEN 25

typedef struct {
  int day;
  int month;
  int year;
} tDate;

typedef struct {
  int idNumber;
  char idChar;
} tDni;

typedef struct {
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  tDate birth;
  tDni id;
} tPeople;

int main(int argc, char** argv) {
  /* Variable definition */
  tPeople you;
}

En este caso, la variable you ocupa 56 bytes en memoria, 25 bytes para cada campo de tipo string, 4 bytes para cada campo de tipo integer y 1 byte para cada campo de tipo char. La dirección de memoria de you es la dirección de memoria del campo prename.

El uso de estas tuplas con tuplas (tuplas jerárquicas) obliga también a utilizar una notación jerárquixa para acceder a estos campos.

Por ejemplo:

  • you.name es el apellido almacenado.
  • you.birth.day es el día de nacimiento almacenado.
  • you.id.idNumber es el número de documento almacenado.

Si el campo es de un tipo básico, ya tenemos acceso al valor de este tipo. Si el campo en cuestión es una tupla, tenemos acceso a la tupla completa y para llegar a uno de los campos deberemos emplear de nuevo el operador punto, seguido del nombre del campo al que se quiera acceder.

11.3.2 Vectores de tuplas

Si en un programa los datos que hacen referencia a una entidad (por ejemplo, persona) son lo suficientemente complejos como para crear tuplas, es muy probable que se tenga que trabajar con colecciones de este tipo de objectos. La estructura de datos adecuada son los vectores (o matriz).

Ejemplo con los datos de los estudiantes de un aula:

type
  tDate = record
    day: integer;
    month: integer;
    year: integer;
  end record

  tDni = record
    idNumber: integer;
    idChar: char;
  end record

  tPeople = record
    prename: string;
    name: string;
    birth: tDate;
    id: tDni;
  end record
end type

algorithm alg12_08
  var
    studentGroup: vector[50] of tPeople;
  end var
algorithm

#define MAX_NAME_LEN 25
/* Types definition */
typedef struct {
  int day;
  int month;
  int year;
} tDate;

typedef struct {
  int idNumber;
  char idChar;
} tDni;

typedef struct {
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  tDate birth;
  tDni id;
} tPeople;

int main(int argc, char** argv) {
  /* Variable definition */
  tPeople studentGroup[50];
}

Para hacer referencia a una posición determinada del vector, nos estaremos refiriendo a un record completo y, para acceder a los campos, deberemos emplear el punto y el identificador del campo.

Por ejemplo:

  • studentGroup[12] es la tupla que contiene toda la información del estudiante que está en la posición 12 del vector.
  • studentGroup[12].name es el apellido del estudiante que está en la posición 12 del vector.
  • studentGroup[12].birth.day es el día de nacimiento del estudiante que está en la posición 12 del vector.

En el caso de que la declaración de la variable sea una matriz, solo se deberá tener en cuenta la doble indexación de la matriz.

Por ejemplo, una escuela de 6 grupos de 50 estudiantes:

type
  tDate = record
    day: integer;
    month: integer;
    year: integer;
  end record

  tDni = record
    idNumber: integer;
    idChar: char;
  end record

  tPeople = record
    prename: string;
    name: string;
    birth: tDate;
    id: tDni;
  end record
end type

algorithm alg12_09
  var
    school: matrix[6][50] of tPeople;
  end var
algorithm


#define MAX_NAME_LEN 25
/* Types definition */
typedef struct {
  int day;
  int month;
  int year;
} tDate;

typedef struct {
  int idNumber;
  char idChar;
} tDni;

typedef struct {
  char prename[MAX_NAME_LEN];
  char name[MAX_NAME_LEN];
  tDate birth;
  tDni id;
} tPeople;

int main(int argc, char** argv) {
  /* Variable definition */
  tPeople school[6][50];
}

En este caso, bien en lenguaje algorítmico o en C:

  • school[2] hace referencia a un vector, un grupo completo de estudiantes (el de segunda posición).
  • school[2][12] hace referencia a una tupla, la correspondiente al estudiante 12 del grupo 2.
  • school[2][12].name serían los apellidos del estudiante 12 del grupo 2.
  • school[2][12].birth.year sería el año de nacimiento del estudiante 12 del grupo 2.

11.3.3 Tuplas que contienen vectores

Muchas veces se necesita definir tuplas para representar una determinada entidad y una de las informaciones es realmente una colección de informaciones más simples.

Por ejemplo, si queremos crear un dato estructurado para mantener la información de una asignatura. Tendremos el nombre de la asignatura, las calificaciones de 5 actividades de evaluación continua (A, B, etc), la calificación del examen y la calificación final. Un record corresponde a estas necesidades:

const
  NUM_ACTIVITIES: integer = 5;
end const

type
  tSubject = record
    name: string;
    activitiesMarks: vector[NUM_ACTIVITIES] of char;
    test: real;
    nalMark: real;
  end record
end type

algorithm alg12_10
  var
    computerScience: tSubject;
  end var
algorithm


#include <stdio.h>

#define MAX_NAME_LEN 25
#define NUM_ACTIVITIES 5

/* Type definition */
typedef struct {
  char name[MAX_NAME_LEN];
  char activitiesMarks[NUM_ACTIVITIES];
  oat test;
  oat nalMark;
} tSubject;

int main(int argc, char** argv) {
  /* Variable definition */
  tSubject computerScience;
}

También en este caso tendremos que ir con cuidado con las referencias.

  • computerScience hace referencia a la tupla completa; toda la información de la asignatura.
  • computerScience.activitiesMarks hace referencia a un vector que contiene las calificaciones de todas las actividades.
  • computerScience.activitiesMarks[3] en lenguaje algorítmico hace referencia a la calificación de la actividad 3 obtenida en la asignatura (recordemos que la primera tiene el índice 1), y en lenguaje C hace referencia a la calificación de la actividad 4 obtenida en la asignatura (recordemos que la primera posición tiene el índice 0). En este ejemplo, la nota de cada actividad está definida por un caracter: 'A', 'B', 'C', 'D', 'E', 'N'.

11.3.4 Y, generalizando

Esta estructura de datos puede ir creciendo en función de nuestras necesidades. Podemos crear un record que tenga algún campo donde se almacene una colección de datos del mismo tipo (Un vector) y que cada dato sea a la vez un dato estructurado.

Cualquier entidad real de una cierta dimensión se puede representar mediante una estructura de datos de complejidad creciente. Por ejemplo, en un hospital pequeñito tendremos algunos conjuntos de información por entidades que merece la pena tener en records:

  • Información sobre la plantila de médicos (Datos personales, la especialidad, cuenta domiciliación bancaria, nómina, etc).
  • Información sobre los pacientes (Datos personales, historial médico, pruebas realizadas, diagnóstico, tratamiento, etc).
  • Información sobre las habitaciones (Número, planta, tipo, precio, etc).

Y en estas tuplas veremos otras informaciones que pueden ser estructuradas:

  • Los datos personales de un médico (Nombre, apellidos, dirección, teléfono, email, etc).
  • Los datos personales de un paciente (Nombre, apellidos, dirección, teléfono, email, etc).
  • Los datos de una prueba médica (Tipo, fecha, resultado, etc).

Se deben crear solo los datos estructurados adecuados para el tratamiento de nuestra informaicón. Debemos equilibrar los esfuerzos y los resultados.

Volver arriba