TEMA 7. Registros - Departamento de Lenguajes y Sistemas

Anuncio
Autor: José C. Riquelme (Universidad de Sevilla)
TEMA 7. Registros
1. Introducción
En la programación de ordenadores el concepto de registro es muy importante. En
múltiples aplicaciones informáticas es necesario establecer una estructura de datos que
agrupe la información de diversos atributos de un único concepto. Un ejemplo habitual
es el concepto de fecha. Programas que manejen fechas son muy habituales en el mundo
real: ingresos de un hospital, informaciones comerciales, gestión de aeropuertos, etc.
¿Qué información debe guardar una fecha? Aunque hay diversas representaciones
posibles, la más elemental es mediante tres enteros que representen el día, el mes y el
año de una fecha. Como los tres datos son de tipo entero, podríamos representar
entonces una fecha como un array para guardar tres enteros. Pero qué ocurre si los datos
que queremos guardar bajo una misma estructura son de distinto tipo. Pensemos por
ejemplo, en la información de un paciente en un sistema de información clínico. ¿Qué
datos se guardan de cada paciente? Como mínimo se necesitarían sus datos personales,
nombre, apellidos y DNI los tres de tipo cadenas de caracteres. Pero sería normal
guardar bajo la denominación de paciente otros datos como edad (entero), sexo
(carácter) o datos clínicos como temperatura corporal (real), presión sanguínea (entero),
etc.
El tipo de datos registro es el que sirve entonces para guardar información de distinto
tipo en una estructura única. Otros ejemplos de datos de tipo registro podrían ser un
vuelo (destino, fecha, número de pasajeros, compañía, etc), un alumno (datos
personales, fecha de nacimiento, asignaturas, notas,…), un vehículo (marca, modelo,
matrícula, fecha de matriculación, …), un medicamento (nombre, precio, fecha de
caducidad, …) etc.
2. Registros en C
2.1 Declaración. En C los registros se representan mediante el tipo de dato
denomindado struct, por eso “españolizando” el término es habitual denominar
estructura al tipo registro de C. Al igual que ocurre con los arrays, el C permite definir
variables de tipo struct sin necesidad de definir previamente el tipo correspondiente. Sin
embargo, como hemos señalado en temas anteriores, es una buena costumbre definir
previamente un tipo antes de definir variables. Ejemplo de declaración es:
struct paciente{
Cadena nombre;
Cadena apellido;
int edad;
char sexo;
float temperatura;
};
Tema 7. Registros
Esto declara struct paciente como un conjunto de campos o atributos. Para definir
variables de tipo paciente se debería poner (en los lugares donde se declaran variables):
struct paciente pac1,pac2;
De todas formas como se ha señalado, lo adecuado es definir el tipo Paciente mediante
una clausula typedef:
typedef struct {
Cadena nombre;
Cadena apellido;
int edad;
char sexo;
float temperatura;
} Paciente;
De esta manera se declaran variables usando el tipo definido:
Paciente pac1, pac2;
Las variables de tipo struct se pueden inicializar cuando se declaran:
Paciente pac1={"Juan", "Gomez",23,'H', 37.4};
2.2 Uso de registros. A partir de una variable de tipo struct se pueden acceder a los
atributos que lo conforman usando el operador . (punto) y el nombre del campo. Cada
campo es del tipo que se declaró en el struct. Ejemplo:
1.
2.
3.
4.
5.
strcpy(pac1.nombre,"Juan");
n=strlen(pac1.apellido);
scanf("%d",&pac1.edad);
pac1.sexo='H';
suma = suma + pac1.temperatura;
En la sentencia 1, pac1.nombre es una variable de tipo Cadena y por tanto para
asignarle valor debe recurrirse a la función strcpy. En la 2, pac1.apellido es también
de tipo Cadena y para hallar su longitud se aplica la función strlen. Si se quiere leer la
edad de pac1, se necesita un formato %d por ser pac1.edad de tipo int en la sentencia
3. En la sentencia 4, pac1.sexo es de tipo char. Finalmente si se quisiera sumar las
temperaturas del paciente se podría poner una sentencia como la 5, donde suma es de
tipo float.
2.3. Argumentos de entrada de tipo struct en una función. Un programa tal que
dados dos registros de tipo Paciente calculara la suma de la suma de sus edades
mediante una función sería:
Autor: José C. Riquelme (Universidad de Sevilla)
#include <stdio.h>
#define MAXCAR 256
typedef char Cadena[MAXCAR];
typedef struct {
Cadena nombre;
Cadena apellido;
int edad;
char sexo;
float temperatura;
} Paciente;
int sumaEdades(const Paciente, const Paciente);
void main(void){
Paciente pac1={"Juan", "Gómez",23,'H', 37.4};
Paciente pac2={"Maria","Garcia",25,'M',38.4};
printf("la suma de las edades es %d\n",sumaEdades(pac1,pac2));
}
int sumaEdades(const Paciente p1, const Paciente p2){
return p1.edad+p2.edad;
}
En el programa anterior los argumentos reales pac1 y pac2 copian los valores de sus
campos a los argumentos formales p1 y p2 como parámetros de entrada que no van a ser
modificados en la función sumaEdades. Por eso se les pone la clausula const tanto en
la cabecera como en el prototipo.
Otro ejemplo, sería una función tal que dado un registro muestre sus datos en pantalla
en un determinado formato:
void escribePaciente(const Paciente p){
printf ("Paciente: %s %s de %d años, sexo: %c, temperatura: %f\n",
p.nombre, p.apellido, p.edad, p.sexo, p.temperatura);
}
2.4. Argumentos de entrada/salida de tipo struct en una función. Sin embargo,
cuando necesitemos que una función cambie los campos de una variable de tipo struct,
es necesario que los argumentos formales se declaren de tipo puntero o se devuelvan en
el nombre de la función. Por ejemplo, supongamos que se quiere escribir el código de
una función que lea los datos de un paciente desde teclado. Podemos hacerlo
devolviendo la estructura en el nombre de la función:
Paciente leePaciente(){
Paciente p;
printf("escriba H o M para el sexo: ");
Tema 7. Registros
scanf("%c",&p.sexo);
printf("escriba el nombre: ");
scanf("%s",p.nombre);
printf("escriba el apellido: ");
scanf("%s",p.apellido);
printf("escriba la edad: ");
scanf("%d",&p.edad);
printf("escriba la temperatura: ");
scanf("%f",&p.temperatura);
return p;
}
En este caso la invocación en el programa principal sería entonces:
pac1 = leePaciente();
O se puede hacer mediante un argumento de entrada/salida:
void leePaciente2(Paciente *p){
printf("escriba H o M para el sexo: ");
scanf("%c",&(*p).sexo); // scanf("%c",&p->sexo);
printf("escriba el nombre: ");
scanf("%s",(*p).nombre); // scanf("%s",p->nombre);
printf("escriba el apellido: ");
scanf("%s",(*p).apellido); // scanf("%s",p->apellido);
printf("escriba la edad: ");
scanf("%d",&(*p).edad); // scanf("%d",&p->edad);
printf("escriba la temperatura: ");
scanf("%f",&(*p).temperatura); // scanf("%f",&p->temperatura);
}
En el código anterior puede observarse como ahora p es un argumento de entrada/salida
por tanto debe definirse como un puntero a Paciente. De esta manera en el código de la
función siempre que nos referimos a p debemos ponerle un * por delante para indicar
“contenido de”. Así (*p).nombre quiere decir campo nombre del contenido del puntero
p. El lenguaje C proporciona el operador -> (guión medio y mayor que) con el
propósito de simplificar la escritura. Esto es, p->nombre quiere decir que p es un
puntero a struct y que estamos accediendo al campo nombre. Tenga en cuenta las
siguientes consideraciones:
1. (*p).temperatura y p->temperatura son de tipo float por lo tanto, para leer su
valor mediante scanf necesita & delante y el formato es %f.
2. (*p).nombre) y p->nombre son de tipo Cadena por lo tanto, para leer su valor
mediante scanf no debe llevar & delante y su formato es %s.
Autor: José C. Riquelme (Universidad de Sevilla)
3. El operador . tiene más prioridad que el operador * por eso es necesario poner
paréntesis en la expresión scanf("%d",&(*p).edad);
4. El operador -> tiene más prioridad que el operador & y por eso no hace falta
paréntesis en la expresión scanf("%f",&p->temperatura);
La invocación a esta función en el programa principal sería:
leePaciente(&pac1);
3. Arrays de registros
La manera habitual de trabajar con los registros es agrupándolos en arrays. De esta
manera un array de registros no es más que una colección de datos, como podría ser una
clase con respecto al registro Alumno, un aeropuerto con respecto Vuelo, o un hospital
respecto al registro Paciente.
3.1 Declaración. Como hemos resaltado numerosas veces, la mejor forma de usar un
array es mediante la declaración previa de su tipo con una cláusula typedef. Por
ejemplo:
#define MAXCAR 256
#define MAXPACIENTE 100
typedef char Cadena[MAXCAR];
typedef struct {
Cadena nombre;
Cadena apellido;
int edad;
char sexo;
float temperatura;
} Paciente;
typedef Paciente Hospital[MAXPACIENTE];
En la última línea se define el tipo Hospital como un array de elementos de tipo
Paciente con una dimensión o tamaño máximo de 100.
3.2 Uso de los arrays de registros. Para usar un array de struct hay que mezclar el uso
de los arrays con el uso de los campos de un struct.
Por ejemplo si definimos una variable usp de tipo Hospital, usp[3] es un dato de tipo
Paciente y usp[3].nombre es un dato de tipo Cadena que contiene el nombre del
paciente que ocupa la cuarta posición del array.
Tema 7. Registros
Ejemplo. Escriba un programa que desde el programa principal lea un número num y
después cargará un array con num registros de tipo Paciente con sus datos leídos desde
teclado usando la función anterior leePaciente:
void main(void){
Hospital usp;
int num,i;
printf("Introduzca el número de pacientes: ");
scanf("%d",&num);
for(i=0;i<num;i++){
usp[i]=leePaciente();
}
}
Nótese el uso de la función leePaciente para leer cada uno de los registros de tipo
Paciente desde teclado y como el resultado de esa lectura se va almacenando en las
posiciones del array usp. La variable usp es un array a todos los efectos por tanto
deberá ir siempre acompañada del número real de elementos, num en este es caso.
Si hubiésemos usado la función leePaciente2, la sentencia dentro del for anterior sería:
leePaciente(&usp[i]);
Por otra parte, los arrays de registros se usan en las funciones igual que los arrays de
tipos básicos, esto es, también son argumentos de entrada/salida y deben ir
acompañados de un argumento de tipo int con el número de elementos. Así por ejemplo,
el código de una función que calculara la edad media de un array de Paciente sería:
float edadMedia(const Hospital t, int n){
int i;
float suma=0.;
for(i=0;i<n;i++){
suma += t[i].edad;
}
return suma/n;
}
Y su invocación en el programa principal podría ser:
edM = edadMedia(usp,num);
con edM variable declarada float.
Autor: José C. Riquelme (Universidad de Sevilla)
Todos los esquemas (sumar, contar, filtrar, buscar, etc) vistos en el tema 5 deben ser
usados para los arrays de struct. Lo único que cambia es que la condición normalmente
irá sobre algún campo de los registros almacenados en el array. Por ejemplo, el código
de una función para filtrar los pacientes cuya temperatura sea mayor de una dada sería:
int temperaturaMayor(const Hospital t, int n, Hospital v, float f){
int i;
int m=0;
for(i=0;i<n;i++){
if (t[i].temperatura>f){
v[m]=t[i];
m++;
}
}
return m;
}
Su invocación podría ser:
nf=temperaturaMayor(usp,num,tf,37.5);
con nf declarada de tipo int y tf de tipo Hospital.
4. Problemas
1. Defina las constantes y tipos de datos más adecuados para el tipo siguiente:
Tipo Paciente:
 Código de identificación de tipo Cadena
 Array de entero con los 720 (24×30) valores de presión sanguínea tomados hora
a hora durante un mes
 Array de reales con como máximo 1.000 elementos que miden el nivel de
expresión genética del paciente sometido a cierta experimentación
 Número de genes (tamaño del array anterior)
2. Implemente una función tal que recibiendo un índice entero entre 0 y 719 (que sería
el índice del array de presión sanguínea) devuelva dos números enteros, el día y la hora,
a la que se tomó esa muestra. Tenga en cuenta que la primera hora es la 0 y el primer
día el 1. Por ejemplo el índice 0 es la primera hora del primer día y devolvería hora=0 y
dia=1; el índice 23 es la última hora del primer día y devolvería hora=23 y dia=1; el
número 27 la cuarta hora del segundo día y devolvería hora=3 y dia=2; y así
sucesivamente. Por ejemplo, el valor 52 daría hora=4 y dia=3. (Use los operadores de
división entera / y módulo de división entera % para este problema). La función por
tanto recibe un entero con un índice y devuelve dos valores enteros (día y hora).
3. Implemente una función que reciba un Paciente y devuelva el valor máximo de su
presión sanguínea.
Tema 7. Registros
4. Implemente una función que dado un Paciente y un valor (vpres) de presión
sanguínea devuelva la posición del array de presión sanguínea de ese Paciente donde se
encuentra por primera vez vpres.
5. Usando las tres funciones anteriores implemente una función tal que dado un
Paciente devuelva el día y la hora de su máxima presión sanguínea.
6. Implemente una función tal que dado un Paciente y un valor (vpres) de presión
sanguínea devuelva si supera o no en algún momento un valor mayor o igual a vpres en
su presión sanguínea. Deberá devolver un valor lógico de verdad o falso.
7. Implemente una función tal que dado un Paciente y un valor real (expgen) de
expresión genética devuelva cuántos valores del array de expresión genética de ese
Paciente son mayores de expgen.
8. Defina las constantes y tipos de datos más adecuados para el tipo siguiente:
Tipo Alumno:
 DNI de tipo Cadena
 Array de entero con los códigos de todas las asignaturas en las que el alumno se ha
matriculado alguna vez.
 Array de reales con las notas obtenidas en las asignaturas del array anterior.
Ocupan las mismas posiciones que el array de asignaturas. Esto es, si la asignatura
de código 345 ocupa la posición 3 en el array anterior, su nota estará en la posición
3 del array de notas.
 Número de asignaturas en las que el alumno se ha matriculado: tamaño de los
arrays anteriores. Máximo 50.
9. Implemente una función que reciba un Alumno y devuelva la nota mínima obtenida y
el código de la asignatura donde se obtuvo (suponga que solo hay una).
10. Implemente una función que dado un Alumno y un valor de nota (vnota) devuelva
un array con los códigos de las asignaturas con nota mayor o igual a vnota.
11. Implemente una función que reciba un Alumno y un entero representando un código
de asignatura y devuelva la nota de esa asignatura ó -1 si no ha estado matriculado.
12. Implemente una función tal que dado un Alumno y un valor real (vnota) devuelva si
todas las notas superan o no un valor mayor o igual a vnota. Deberá devolver un valor
booleano de verdad o falso.
13. Un alumno habrá terminado los estudios si se ha matriculado de al menos 40
asignaturas y las tiene todas aprobadas (nota mayor o igual a 5). Escriba una función tal
que dado un Alumno devuelva si ha terminado o no los estudios. Use la función del
apartado anterior.
14. Implemente una función tal que dado un Alumno devuelva la nota media de las
asignaturas en las que se ha matriculado.
Descargar