Tabla de fichas Indice del Tema 0202
0201 0202 0203 0204 0205 0206 0207 0208

ESTRUCTURAS EN C







Definición, Declaración e Iniciación de Estructuras.
En C se pueden definir estructuras de dos maneras: haciendo uso de un tipo previamente creado, o bien de forma directa, sin más que asignar un nombre a una colección de campos "empaquetados" en un envoltorio. En todo caso, es posible construir listas formadas por elementos de tipos estructurados; estas estructuras de datos se describen en la sección correspondiente.
Para definir un nuevo tipo de estructura en C se emplea la siguiente sintaxis:

struct Nombre_de_tipo { 
	tipo1 campo1 [,campo2,...]; 
	...
	tipon campom[,campop,...];
} ;

Esta definición de nuevo tipo puede ir acompañada por una o más declaraciones de variables:

struct Nombre_de_tipo { 
	tipo1 campo1 [,campo2,...];
	...
	tipon campom [,campop,...];
} variable1 [variable2,...] ;

Por último, también es posible definir y declarar una estructura sin necesidad de indicar un nombre para el tipo de esa estructura:

struct { 
	tipo1 campo1 [,campo2,...];
	...
	tipon campom[,campop,...];
} variable1[variable2,...] ;

Evidentemente, no tiene sentido definir una estructura anónima y carente de nombre. Véase como ejemplo el siguiente Ejercicio.

Representación interna

El compilador va asignando espacio para los campos por el mismo orden en que estos aparezcan en la definición del nuevo tipo de datos. Cada uno de los campos tiene una dirección distinta, aunque hay que tener en cuenta también la posible existencia de "rellenos" creados por el compilador para optimizar el acceso. El objetivo que se persigue es que los comienzos de campo estén alineados con comienzos de palabra de la memoria, para no tener que desplazar bytes antes de procesar la información. Esto supone un cierto consumo adicional de memoria, pero la velocidad con que se alimentan datos al procesador es la mayor posible. Téngase en cuenta que el problema del procesador es invariablemente la incapacidad del sistema de memoria para aportar información, y para recibir con velocidad suficiente la información producida por el procesador.

La estructura representada aquí se ha empleado en un Ejercicio posterior, dedicado al estudio de uniones. Esta imagen describe una estructura que posee tres campos, uno de tipo entero, otro de tipo float y otro formado por una colección de bits. Se supone que los tres campos poseen una extensión igual a la longitud de una palabra, y por tanto el compilador no ha insertado espacios de relleno.

Campos de bits.
Una interesante variedad de los campos definidos en una estructura son los campos formados por bits. Los campos de bits se declaran en la forma siguiente:

struct Nombre_tipo_estructura {
tipo_valido_bits nombre1 : longitud1;
tipo_valido_bits nombre2 : longitud2;
...
tipo_valido_bits nombreN : longitudN;
} nombre_variable;

En donde tipo_valido_bits denota uno de entre los tres tipos siguientes: int, unsignedo signed. Si la longitud es 1, el tipo tiene que ser obligatoriamente unsigned, porque un campo de 1 bit no puede tener signo (solo puede valer 0 o 1).

Los campos de bits tienen dos aplicaciones. La primera es el control de aparatos (posiblemente a través de relés) que requieren que el intercambio de información con la computadora se haga a través de bloques de bits. La segunda es la investigación del formato binario interno de los tipos de datos definidos en el lenguaje. Esta última aplicación solo es viable a través de otras estructuras de datos, las uniones.

Campos de relleno.
El compilador intenta en lo posible optimizar el código generado. En este sentido, hay que tener en cuenta que no todos los accesos a memoria tienen igual coste en términos de tiempo; concretamente, los accesos a datos que comiencen a principio de palabra son más rápidos que los demás, y el acceso a un byte individual (salvo en el caso anterior) es más lento que el acceso a la palabra completa que lo contiene. Consiguientemente, el compilador no sitúa en posiciones contiguas los campos consecutivos de una estructura, sino que intenta situar el comienzo de campo en un comienzo de palabra. Esto da lugar a la aparición de "campos de relleno", a los que no es posible acceder, y que tienen por misión optimizar el acceso a memoria. La creación de estos campos no depende del programador.Acceso a campos.
El mecanismo de acceso a un campo de una estructura exige especificar el nombre de la estructura considerada y el nombre del campo deseado, separados mediante un punto. Volviendo a la declaración habitual de una estructura,

struct { 
	tipo1 campo1 [,campo2,...];
	...
	tipon campom[,campop,...];
} variable1[variable2,...] ;

el código necesario para acceder al campoM de la variableT sería variableT.campoM en donde variableT denota la estructura y campoM denota el campo considerado. Los siguientes serían ejemplos válidos:

struct Ejemplo {
	char nombre[80];
	int edad;
}; /* Definición del nuevo tipo */
struct Ejemplo nombre_estructura; /* Declaración de una variable */
nombre_estructura.nombre = "José García";
nombre_estructura.edad = 34;
printf("La edad de %s es de %d años.\n",
			nombre_estructura.nombre,nombre_estructura.edad);



Asignación de Estructuras
El procedimiento de asignación de estructuras es el habitual, y funciona de la forma esperable. Un ejemplo podría ser el siguiente:

struct Ejemplo {
	char nombre[80];
	int edad;
}; 
struct Ejemplo est_1, est_2;
est_1.nombre = "José García";
est_1.edad = 34;
est_2 = est_1; /* Se copia correctamente todos los campos. */

Hay que tener especial cuidado con los campos que sean punteros.

Ejercicio.- Escribir un programa que muestre dos estructuras. La primera se construirá a partir un tipo previamente definido. La segunda se construirá en directo. Ambas deben poseer los mismos campos, y se iniciarán con valores idénticos. El programa mostrará el contenido de las dos estructuras.

#include<stdio.h>

/* Construcción de Estructuras */

/* Ejemplo de ESTRUCTURA ANONIMA. Obsérvese la iniciación */

struct {
	char nombre[80];
	int edad;
	float peso;
} ficha_anonima = {"José Pérez", 33, 80.0 };

/* Ejemplo de DECLARACION DE UN TIPO STRUCT */

struct Fichas  {
	char nombre[80];
	int edad;
	float peso;
};

struct Fichas otra_ficha = {"José Pérez", 33, 80.0 };

void main(void)
{
 printf("Datos de la ficha de un tipo anónimo:\n\n");
 printf("El nombre es %s\n",ficha_anonima.nombre);
 printf("La edad es %d\n",ficha_anonima.edad);
 printf("El peso es %f\n",ficha_anonima.peso);
 
 /*
  Esta asignación no está permitida, pese a que las estructuras
  son idénticas.
  
  otra_ficha = ficha_anonima;
  
  Tampoco se puede hacer una refundición de tipos:
 
  otra_ficha = (struct Fichas) ficha_anonima;
 
 */
 
 printf("\n\nDatos de la ficha de un tipo conocido:\n\n");
 printf("El nombre es %s\n",otra_ficha.nombre);
 printf("La edad es %d\n",otra_ficha.edad);
 printf("El peso es %f\n",otra_ficha.peso);
 
 printf("\n\nTerminación normal del programa.\n\n");
 
}



Haga clic aquí para volver a la parte superior de esta página.

Ejercicios propuestos



  1. Ejercicio 0202r01.- Construir una estructura de datos adecuada para una base de datos de personal, que contendrá campos para nombre, dos apellidos, dirección, teléfono y DNI (struct Registro, por ejemplo). Escribir un programa dotado de funciones que lean y escriba los campos de un registro de este tipo,sin efectuar comprobaciones de longitud.

    La solución puede ser la siguiente:
    #include<stdio.h>
    #define MAX_PERSONAS 10
    
    typedef struct Registro {
     char nombre[20];
     char ape_1[20];
     char ape_2[20];
     char direc[40];
     char telef[15];
     char DNI[15];
    } Registro;
    
    typedef Registro * RegistroRef;
    
    Registro bdd[MAX_PERSONAS];
    
    void leer(RegistroRef f);
    void escribir(RegistroRef f);
    
    int main(int argc,charRef argv[]) {
     Registro una_ficha;
     printf("\nPor favor, escriba los datos solicitados:\n\n");
     leer(&una_ficha);
     printf("\n\nLos datos introducidos eran como sigue:\n\n");
     escribir(&una_ficha);
     printf("\n\nTerminación normal del programa.\n\n");
     return 0;
    }
    
    
    
    void leer(RegistroRef f){
     printf("\nPor favor, escriba su nombre           : ");gets(f->nombre);
     printf("\nPor favor, escriba su primer apellido  : ");gets(f->ape_1);
     printf("\nPor favor, escriba su segundo apellido : ");gets(f->ape_2);
     printf("\nPor favor, escriba su dirección        : ");gets(f->direc);
     printf("\nPor favor, escriba su teléfono         : ");gets(f->telef);
     printf("\nPor favor, escriba su DNI              : ");gets(f->DNI);
    }
    void escribir(RegistroRef f){
     printf("\nSu nombre es           >%s<\n", f->nombre);
     printf("\nSu primer apellido es  >%s<\n", f->ape_1);
     printf("\nSu segundo apellido es >%s<\n", f->ape_2);
     printf("\nSu dirección es        >%s<\n", f->direc);
     printf("\nSu teléfono es         >%s<\n", f->telef);
     printf("\nSu DNI es              >%s<\n", f->DNI);
    }
    
    /*
     Este programa puede dar problemas. De hecho el compilador lo indica al ejecutar:
     
    Por favor, escriba los datos solicitados:
    
    
    warning: this program uses gets(), which is unsafe.
    Por favor, escriba su nombre           : Juan
    
    Por favor, escriba su primer apellido  : Perez
    
    Por favor, escriba su segundo apellido : Lopez
    
    Por favor, escriba su dirección        : Agua, 7
    
    Por favor, escriba su teléfono         : 555555555
    
    Por favor, escriba su DNI              : 111111111
    
    
    Los datos introducidos eran como sigue:
    
    
    Su nombre es           >Juan<
    
    Su primer apellido es  >Perez<
    
    Su segundo apellido es >Lopez<
    
    Su dirección es        >Agua, 7<
    
    Su teléfono es         >555555555<
    
    Su DNI es              >111111111<
    
    
    Terminación normal del programa.
    */

    Notas
    El programa no plantea dificultades al ser alfanuméricos todos los campos. Se utiliza la función gets(), contraindicada, para comprobar el peligro que supone. En efecto, basta insertar un nombre (o cualquier otro campo) de longitud superior a la normal para que se produzcan efectos extraños en el mejor de los casos. Los ejercicios siguientes aportan mejores soluciones.

  2. Ejercicio 0202r02.- Construir una estructura de datos adecuada para fichas de alumnos, que contendrá campos para nombre, dos apellidos, dirección, teléfono y DNI . Escribir un programa dotado de funciones que lean y escriba los campos de una variable de este tipo, efectuando comprobaciones de longitud.

    La solución puede ser la siguiente:
    
    #include<stdio.h>
    #include<string.h>
    #include<coti/utiles.h>
    
    #define MAX_PERSONAS 10
    
    typedef struct Registro {
    	char nombre[20];
    	char ape_1[20];
    	char ape_2[20];
    	char direc[40];
    	char telef[15];
    	char DNI[15];
    } Registro;
    
    typedef Registro * RegistroRef;
    
    Registro bdd[MAX_PERSONAS];
    
    void leer(RegistroRef);
    void escribir(RegistroRef);
    
    int main(int argc, charRef argv[]) {
    	Registro una_ficha;
    	printf("\nPor favor, escriba los datos solicitados: \n\n");
    	leer(&una_ficha);
    	printf("\n\nLos datos introducidos eran como sigue:\n\n");
    	escribir(&una_ficha);
    	printf("\n\nTerminación normal del programa.\n\n");
    	return 0;
    }
    
    void leer(RegistroRef f) {
    	charRef temp;
    
    	temp = pedir("\nPor favor, escriba su nombre           : ");
    	strncpy(f->nombre, temp, sizeof(f->nombre));
    	free(temp);
    	printf("\n");
    
    	temp = pedir("\nPor favor, escriba su primer apellido : ");
    	strncpy(f->ape_1, temp, sizeof(f->ape_1));
    	free(temp);
    	printf("\n");
    
    	temp = pedir("\nPor favor, escriba su segundo apellido : ");
    	strncpy(f->ape_2, temp, sizeof(f->ape_2));
    	free(temp);
    	printf("\n");
    
    	temp = pedir("\nPor favor, escriba su dirección        : ");
    	strncpy(f->direc, temp, sizeof(f->direc));
    	free(temp);
    	printf("\n");
    
    	temp = pedir("\nPor favor, escriba su teléfono         : ");
    	strncpy(f->telef, temp, sizeof(f->telef));
    	free(temp);
    	printf("\n");
    
    	temp = pedir("\nPor favor, escriba su DNI              : ");
    	strncpy(f->DNI, temp, sizeof(f->DNI));
    	free(temp);
    	printf("\n");
    
    }
    void escribir(RegistroRef f) {
    	printf("\nSu nombre es           >%s<\n", f->nombre);
    	printf("\nSu primer apellido es  >%s<\n", f->ape_1);
    	printf("\nSu segundo apellido es >%s<\n", f->ape_2);
    	printf("\nSu dirección es        >%s<\n", f->direc);
    	printf("\nSu teléfono es         >%s<\n", f->telef);
    	printf("\nSu DNI es              >%s<\n", f->DNI);
    }
    
    
    /*
    Resultado de la ejecución:
    
    Por favor, escriba los datos solicitados:
    
    
    Por favor, escriba su nombre           : Juan
    
    Por favor, escriba su primer apellido  : Pérez
    
    Por favor, escriba su segundo apellido : López
    
    Por favor, escriba su dirección        : Agua, 7
    
    Por favor, escriba su teléfono         : 55555555
    
    Por favor, escriba su DNI              : 11111111 
    
    
    Los datos introducidos eran como sigue:
    
    
    Su nombre es           >Juan<
    
    Su primer apellido es  >Pérez<
    
    Su segundo apellido es >López<
    
    Su dirección es        >Agua, 7<
    
    Su teléfono es         >55555555<
    
    Su DNI es              >11111111<
    
    
    Terminación normal del programa.
    
    */
    

    Notas
    Este ejercicio aporta la función leer_cadena(), que resuelve el problema de los desbordamientos matriciales. Se utiliza como base la función fgets(), que impone un límite (su segundo argumento) sobre el número de caracteres leídos realmente. En todo caso, se vacía el búfer de teclado una vez efectuada la lectura. Obsérvese que fgets() proporciona también, al final de la cadena, el carácter '\n' pulsado por el usuario para denotar el fin de la información. Este carácter puede producir problemas, puesto que realmente no debe formar parte de la información leída, así que lo descartamos asignando el valor '\0', el marcador de fin de cadenas, a la posición n-1 de la cadena proporcionada. De este modo se cambia el '\n' por un '\0', que es el valor esperado.

  3. Ejercicio 0202r03.- Tomando como base la struct Registro anterior, incluir campos de edad y peso. Se pide escribir un programa dotado de funciones que lean y escriba los campos de una variable de este tipo, efectuando comprobaciones de longitud. El programa no admitirá campos vacíos e impondrá una verificación de los valores numéricos.

    La solución puede ser la siguiente:
    #include<stdio.h>
    #include<stdlib.h>
    
    
    #define MAX_PERSONAS 10
    
    typedef struct Registro {
     char nombre[20];
     char ape_1[20];
     char ape_2[20];
     char direc[40];
     char telef[15];
     char DNI[15];
     int edad;
     float peso;
    } Registro;
    
    typedef Registro * RegistroRef;
    
    Registro bdd[MAX_PERSONAS];
    
    void leer(RegistroRef f);
    void escribir(RegistroRef f);
    
    int leer_cadena(charRef p, int longitud_maxima);
    int leerint(charRef p, int valor_min, int valor_max);
    float leerfloat(charRef p, float valor_min, float valor_max);
    double leerdouble(charRef p, double valor_min, double valor_max);
    
    int main(int argc,charRef argv[]) {
     Registro una_ficha;
     printf("\nPor favor, escriba los datos solicitados:\n\n");
     leer(&una_ficha);
     printf("\n\nLos datos introducidos eran como sigue:\n\n");
     escribir(&una_ficha);
     printf("\n\nTerminación normal del programa.\n\n");
     return 0;
    }
    
    
    
    void leer(RegistroRef f){
     int temp;
     do
      {
       printf("\nPor favor, escriba su nombre              : ");
       temp = leer_cadena(f->nombre, sizeof(f->nombre));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su primer apellido     : ");
       temp = leer_cadena(f->ape_1, sizeof(f->ape_1));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su segundo apellido    : ");
       temp = leer_cadena(f->ape_2, sizeof(f->ape_2));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su dirección           : ");
       temp = leer_cadena(f->direc, sizeof(f->direc));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su teléfono            : ");
       temp = leer_cadena(f->telef, sizeof(f->telef));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su DNI                 : ");
       temp = leer_cadena(f->DNI, sizeof(f->DNI));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     f->edad = leerint("\nPor favor, escriba su edad             :",1, 99);
     f->peso = leerfloat("\nPor favor, escriba su peso           :", 1.0, 120.0);
    }
    void escribir(RegistroRef f){
     printf("\nSu nombre es           >%s<\n", f->nombre);
     printf("\nSu primer apellido es  >%s<\n", f->ape_1);
     printf("\nSu segundo apellido es >%s<\n", f->ape_2);
     printf("\nSu dirección es        >%s<\n", f->direc);
     printf("\nSu teléfono es         >%s<\n", f->telef);
     printf("\nSu DNI es              >%s<\n", f->DNI);
     printf("\nSu edad es             >%d<\n", f->edad);
     printf("\nSu peso es             >%6.2f<\n", f->peso);
    }
    
    
    int leer_cadena(charRef p, int longitud_maxima)
    {
     int n;
     if (p==NULL || longitud_maxima==0)
      return 0;
     fgets(p, longitud_maxima, stdin);
     fpurge(stdin);
     n = strlen(p);
     p[n-1] = '\0';
     return n;
    }
    
    int leerint(charRef p, int valor_min, int valor_max){
     char temp[80];
     int resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un entero (%d <= n <= %d): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtol(temp, NULL, 10);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    float leerfloat(charRef p, float valor_min, float valor_max){
     char temp[80];
     float resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un float (%6.2f <= n <= %6.2f): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    double leerdouble(charRef p, double valor_min, double valor_max){
     char temp[80];
     double resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un double (%6.2lf <= n <= %6.2lf): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    /*
    Resultados de la ejecución del programa:
    
    Por favor, escriba los datos solicitados:
    
    
    Por favor, escriba su nombre              : 
    
    Perdón, este campo no puede estar vacío.
    
    Por favor, escriba su nombre              : José Juan
    
    Por favor, escriba su primer apellido     : Martín
    
    Por favor, escriba su segundo apellido    : Martín
    
    Por favor, escriba su dirección           : 13, Rue de Auteuil
    
    Por favor, escriba su teléfono            : 555-555555
    
    Por favor, escriba su DNI                 : 11111111
    
    Por favor, escriba su edad             : 22
    
    Por favor, escriba su peso           : 62.8
    
    
    Los datos introducidos eran como sigue:
    
    
    Su nombre es           >José Juan<
    
    Su primer apellido es  >Martín<
    
    Su segundo apellido es >Martín<
    
    Su dirección es        >13, Rue de Auteuil<
    
    Su teléfono es         >555-555555<
    
    Su DNI es              >11111111<
    
    Su edad es             >22<
    
    Su peso es             > 62.80<
    
    
    Terminación normal del programa.
    */

    Notas
    En este ejercicio se han añadido los campos numéricos edad y peso, que exigirán efectuar una transformación de alfanumérico a binario. Esto se hace mediante las funciones leerint(), leerfloat() y leerdouble(), construidas con este fin. Ambas admiten como argumento una indicación que se mostrará al usuario, y proporcionan como resultado un int o un float, respectivamente. Además, es preciso indicar a estas funciones el intervalo al que debe pertenecer el valor solicitado. Esto nos permite evitar muchos errores, en especial los asociados al desb ordamiento de listas y matrices, tan frecuente en C. Obsérvese que la función strtof(), encargada de traducir del formato alfanumérico al de coma flotante, produce un double como resultado. Esto implica la necesidad de transformar el resultado, convirtiéndolo en un float. La función leerdouble(), desde luego, no requiere esta transformación. Se impone que los campos alfanuméricos no puedan estar vacíos, lo cual se ha logrado haciendo que la función leer_cadena() proporcione devuelva la longitud de la cadena proporcionada a través de su primer argumento. Si el usuario pulsa <INTRO> como respuesta a la petición de información efectuada, el resultado será un único carácter (sería un '\n', que hemos convertido en '\0'). Esto se puede detectar en el bucle while(temp==1) empleado, y así se garantiza que el usuario inserte al menos una letra como respuesta, antes de pulsar <INTRO>.

  4. Ejercicio 0202r04.- Tomando como base la struct Registro anterior, construir una lista estática de campos (struct Registro bdd[MAX_PERSONAS]) y un programa dotado de las opciones y funciones necesarias para dar valor a esos campos y mostrarlos en pantalla con formato encolumnado.

    La solución puede ser la siguiente:
    #include<stdio.h>
    #include<stdlib.h>
    
    
    #define MAX_PERSONAS 3
    
    typedef struct Registro {
     char nombre[15];
     char ape_1[15];
     char ape_2[15];
     char direc[15];
     char telef[9];
     char DNI[8];
     int edad;
     float peso;
    } Registro;
    
    typedef Registro * RegistroRef;
    
    Registro bdd[MAX_PERSONAS];
    int num_columnas[] = {15, 15, 15, 15, 9, 8, 8, 8}; 
    
    void leer(RegistroRef f);
    void escribir(RegistroRef f);
    
    int leer_cadena(charRef p, int longitud_maxima);
    float leerfloat(charRef p, float valor_min, float valor_max);
    double leerdouble(charRef p, double valor_min, double valor_max);
    void escribir_encolumnado(RegistroRef f, intRef p);
    
    int main(int argc,charRef argv[]) {
     int i;
     printf("\nPor favor, escriba los datos solicitados:\n\n");
     for (i=0;i<MAX_PERSONAS;i++)
      {
       printf("\n\nDatos del registro nº %d:\n\n",i);
       leer(&bdd[i]);
      }
     printf("\n\nLos datos introducidos eran como sigue:\n\n");
     for (i=0;i<MAX_PERSONAS;i++)
      escribir_encolumnado(&bdd[i], num_columnas);
     printf("\n\nTerminación normal del programa.\n\n");
     return 0;
    }
    
    
    
    void leer(RegistroRef f){
     int temp;
     do
      {
       printf("\nPor favor, escriba su nombre              : ");
       temp = leer_cadena(f->nombre, sizeof(f->nombre));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su primer apellido     : ");
       temp = leer_cadena(f->ape_1, sizeof(f->ape_1));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su segundo apellido    : ");
       temp = leer_cadena(f->ape_2, sizeof(f->ape_2));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su dirección           : ");
       temp = leer_cadena(f->direc, sizeof(f->direc));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su teléfono            : ");
       temp = leer_cadena(f->telef, sizeof(f->telef));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su DNI                 : ");
       temp = leer_cadena(f->DNI, sizeof(f->DNI));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     f->edad = leerint("\nPor favor, escriba su edad                :",1, 99);
     f->peso = leerfloat("\nPor favor, escriba su peso                :", 1.0, 120.0);
    }
    void escribir_encolumnado(RegistroRef f, intRef p){
    /*
     La lista señalada por p con tiene las longitudes de los distintos
     campos, que se usarán para cortar las cadenas construidas por
     concatenación del valor del campo con una cadena de 80 caracteres
     llamada relleno.
    */
     char relleno[80];
     char temp[256];
     memset(relleno,' ',79);
     relleno[79]  = '\0';
     strcpy(temp, f->nombre);
     strcat(temp, relleno);
     temp[p[0]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el primer campo */
     strcpy(temp, f->ape_1);
     strcat(temp, relleno);
     temp[p[1]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el segundo campo */
     strcpy(temp, f->ape_2);
     strcat(temp, relleno);
     temp[p[2]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el tercer campo */
     strcpy(temp, f->direc);
     strcat(temp, relleno);
     temp[p[3]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el cuarto campo */
     strcpy(temp, f->telef);
     strcat(temp, relleno);
     temp[p[4]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el quinto campo */
     strcpy(temp, f->DNI);
     strcat(temp, relleno);
     temp[p[5]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el sexto campo */
     sprintf(temp,"%8.2d", f->edad);
     printf("%s", temp);
     sprintf(temp,"%8.2f", f->peso);
     printf("%s\n", temp);
    }
    
    
    int leer_cadena(charRef p, int longitud_maxima)
    {
     int n;
     if (p==NULL || longitud_maxima==0)
      return 0;
     fgets(p, longitud_maxima, stdin);
     fpurge(stdin);
     n = strlen(p);
     p[n-1] = '\0';
     return n;
    }
    
    int leerint(charRef p, int valor_min, int valor_max){
     char temp[80];
     int resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un entero (%d <= n <= %d): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtol(temp, NULL, 10);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    float leerfloat(charRef p, float valor_min, float valor_max){
     char temp[80];
     float resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un float (%6.2f <= n <= %6.2f): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    double leerdouble(charRef p, double valor_min, double valor_max){
     char temp[80];
     double resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un double (%6.2lf <= n <= %6.2lf): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    /*
    Por favor, escriba los datos solicitados:
    
    
    
    Datos del registro nº 0:
    
    
    Por favor, escriba su nombre              : Mateo
    
    Por favor, escriba su primer apellido     : Mateo
    
    Por favor, escriba su segundo apellido    : Mateo
    
    Por favor, escriba su dirección           : Rue de Mateo
    
    Por favor, escriba su teléfono            : 1111
    
    Por favor, escriba su DNI                 : 1111
    
    Por favor, escriba su edad                : 11
    
    Por favor, escriba su peso                : 11
    
    
    Datos del registro nº 1:
    
    
    Por favor, escriba su nombre              : Marcos
    
    Por favor, escriba su primer apellido     : Marcos
    
    Por favor, escriba su segundo apellido    : Marcos 
    
    Por favor, escriba su dirección           : Rue de Marcos
    
    Por favor, escriba su teléfono            : 22
    
    Por favor, escriba su DNI                 : 22
    
    Por favor, escriba su edad                : 22
    
    Por favor, escriba su peso                : 22
    
    
    Datos del registro nº 2:
    
    
    Por favor, escriba su nombre              : Lucas
    
    Por favor, escriba su primer apellido     : Lucas
    
    Por favor, escriba su segundo apellido    : Lucas
    
    Por favor, escriba su dirección           : Rue de Lucas
    
    Por favor, escriba su teléfono            : 33
    
    Por favor, escriba su DNI                 : 33
    
    Por favor, escriba su edad                : 33
    
    Por favor, escriba su peso                : 33
    
    
    Los datos introducidos eran como sigue:
    
    Mateo          Mateo          Mateo          Rue de Mateo   1111     1111          11   11.00
    Marcos         Marcos         Marcos         Rue de Marcos  22       22            22   22.00
    Lucas          Lucas          Lucas          Rue de Lucas   33       33            33   33.00
    
    
    Terminación normal del programa.
    */

    Notas
    Este programa aporta novedades significativas. En primer lugar, se ha construido una lista de registros, en lugar de emplera una única variable. Esto permitiría, mediante una constante MAX_PERSONAS adecuada, disponer de gran cantidad de registros. Por otra parte, se ha construido una lista, num_columnas, que contiene las longitudes deseadas para los campos cuando éstos se impriman en pantalla. La importancia de los informes en columnas es patente, y se emplearán con frecuencia en el tratamiento de ficheros, en la sección correspondiente. Se ha creado, por tanto, un método llamado escribir_encolumnado() que permite mostra en pantalla un registro con las especificaciones de longitud de campos indicadas. La estructura del programa es sencilla y plantea dificultades sin por alguna razón cambiase la implementación de la estructura de datos empleada. Esto es un problema grave, que exigiría hacer cambios en muchos lugares del programa. Es muy preferible recurrir a pequeñas funciones adecuadas para acceder a la base de datos, construidas y empleadas de modo que los cambios habidos en la implementación afecten únicamente a unos pocos puntos (unas pocas funciones, claro) del programa. Si mantenemos constantes las signaturas de todas las funciones (el número, tipo y orden de sus argumentos), podemos estar seguros de que los cambios habidos en las estructuras de datos subyacentes no afectarán para nada al resto del programa. Esto supone un enorme ahorro de trabajo.
    El procedimiento empleado para construir campos de longitud predeterminada, sin duda mejorable, consiste en añadir (mediante strcat()) una colección de espacios en blanco a una cadena que contiene inicialment el valor del campo en cuestión. Una vez creada esa cadena temporal formada por el valor del campo y el "relleno" de espacios, se inserta un marcador de fin de cadena ('\0') en el lugar que convenga para así definir la longitud práctica de temp. Los trozos se van imprimiendo en pantalla a medida que se construyen, aunque nada impediría añadirlos a una cadena "almacén" que se imprimiría finalmente en pantalla o en disco. Resulta imprescindible la especificación de longitudes de los campos, almacenadas en num_columnas y pasadas a escribir_encolumnado() como argumento.
    Obsérvese también el uso de la función sprintf() para crear cadenas que contienen los campos de edad y de peso, y que se añaden oportunamente a lo ya expuesto en pantalla. Una vez más, nada impediría añadir esta información a una cadena global que finalmente se enviaría a pantalla o a un archivo de disco.

  5. Ejercicio 0202r05.- Construir una base de datos basada en la struct Registro bdd[MAX_PERSONAS] anterior. El programa debe permitir la realización de altas, bajas y modificaciones.

    La solución puede ser la siguiente:
    main.c
    /*
     * main.c
     *
     *  Created on: 03/09/2009
     *      Author: coti
     */
    
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<coti/utiles.h>
    #include "tui.h"
    
    
    int main(int argc, charRef argv[]) {
    	Registro bdd[MAX_PERSONAS];
    	int num_columnas[] = { 15, 15, 15, 15, 9, 8, 8, 8 };
    	enum {
    		Altas, Bajas, Modificaciones, Ver, Salir
    	};
    	char menu[][20] = { "Altas", "Bajas", "Modificaciones", "Ver registros",
    			"Salir" };
    	char opcion;
    	int i, caso;
    	iniciacion(bdd);
    	do {
    		/* Visualizar menú y leer la opción */
    		printf("\n");
    		for (i = 0; i < NUM_OPCIONES; i++)
    			printf(" (%d) %s", i, menu[i]);
    		printf(" : ");
    		scanf("%c%*c", &opcion);
    		fpurge(stdin);
    
    		/* Mostrar en pantalla la opción seleccionada */
    		caso = opcion - '0';
    		switch (caso) {
    		case Altas:
    			dar_altas(bdd);
    			break;
    		case Bajas:
    			dar_bajas(bdd);
    			break;
    		case Modificaciones:
    			hacer_modificaciones(bdd);
    			break;
    		case Ver:
    			ver(bdd, num_columnas);
    			break;
    		case Salir:
    			printf("\nSaliendo...%s\n", menu[caso]);
    			break;
    		default:
    			printf("\n\nOpción incorrecta\n\n");
    			break;
    		}
    	} while (caso != Salir);
    	printf("\n\nFin del programa\n\n");
    	return 0;
    }
    
    tui.h
    /*
     * tui.h
     *
     *  Created on: 04/09/2009
     *      Author: coti
     */
    
    #ifndef TUI_H_
    #define TUI_H_
    #include<coti/utiles.h>
    
    #define MAX_PERSONAS 3
    #define NUM_OPCIONES 5
    
    typedef struct Registro {
    	char nombre[15];
    	char ape_1[15];
    	char ape_2[15];
    	char direc[15];
    	char telef[9];
    	char DNI[8];
    	int edad;
    	float peso;
    } Registro;
    
    typedef Registro * RegistroRef;
    
    void leer(RegistroRef f);
    void escribir_encolumnado(RegistroRef f, intRef p);
    
    void iniciacion(RegistroRef f);
    void ver(RegistroRef f, intRef nc);
    void dar_altas(RegistroRef f);
    void dar_bajas(RegistroRef f);
    void hacer_modificaciones(RegistroRef f);
    
    
    #endif /* TUI_H_ */
    
    tui.c
    /*
     * tui.c
     *
     *  Created on: 04/09/2009
     *      Author: coti
     */
    
    #include "tui.h"
    
    void iniciacion(RegistroRef f) {
    	int i;
    	for (i = 0; i < MAX_PERSONAS; i++)
    		strcpy(f[i].nombre, "*");
    }
    
    void ver(RegistroRef f, intRef nc) {
    	int i;
    	printf("\n\nEl contenido de los registros ocupados es como sigue:\n\n");
    	for (i = 0; i < MAX_PERSONAS; i++)
    		if (f[i].nombre[0] != '*')
    			escribir_encolumnado(f + i, nc);
    }
    
    void dar_altas(RegistroRef f) {
    	int i, n, num_disponibles_para_alta = 0;
    	printf("\n\nProceso de altas\n\n");
    	for (i = 0; i < MAX_PERSONAS; i++)
    		if (f[i].nombre[0] == '*') {
    			printf("El registro %d puede causar alta.\n", i);
    			num_disponibles_para_alta++;
    		}
    	if (num_disponibles_para_alta == 0) {
    		printf("\nNo hay registros disponibles para la alta.");
    		return;
    	}
    
    	do {
    		n = pedirInt("Escriba el número del registro que quiere rellenar: ", 0,
    				MAX_PERSONAS - 1);
    	} while (f[n].nombre[0] != '*');
    	leer(&f[n]);
    }
    
    void dar_bajas(RegistroRef f) {
    	int i, n, m, num_disponibles_para_baja = 0;
    	printf("\n\nProceso de bajas\n\n");
    	for (i = 0; i < MAX_PERSONAS; i++)
    		if (f[i].nombre[0] != '*') {
    			printf("El registro %d puede causar baja.\n", i);
    			num_disponibles_para_baja++;
    		}
    	if (num_disponibles_para_baja == 0) {
    		printf("\nNo hay registros disponibles para la baja.");
    		return;
    	}
    	do {
    		n = pedirInt("Escriba el número del registro que quiera eliminar"
    			" (confirmación posterior): ", 0, MAX_PERSONAS - 1);
    	} while (f[n].nombre[0] == '*');
    	m = confirmar("¿Desea realmente dar de baja este registro?");
    	if (m)
    		strcpy(f[n].nombre, "*");
    	else
    		printf("\nNo se ha efectuado la baja\n");
    }
    
    void leer(RegistroRef f) {
    	charRef temp;
    
    	temp = pedir_nv("\nPor favor, escriba su nombre              : ");
    	strncpy(f->nombre, temp, sizeof(f->nombre));
    	free(temp);
    
    	temp = pedir_nv("\nPor favor, escriba su primer apellido     : ");
    	strncpy(f->ape_1, temp, sizeof(f->ape_1));
    	free(temp);
    	temp = pedir_nv("\nPor favor, escriba su segundo apellido    : ");
    	strncpy(f->ape_2, temp, sizeof(f->ape_2));
    	free(temp);
    
    	temp = pedir_nv("\nPor favor, escriba su dirección           : ");
    	strncpy(f->direc, temp, sizeof(f->direc));
    	free(temp);
    
    	temp = pedir_nv("\nPor favor, escriba su teléfono            : ");
    	strncpy(f->telef, temp, sizeof(f->telef));
    	free(temp);
    
    	temp = pedir("\nPor favor, escriba su DNI                : ");
    	strncpy(f->DNI, temp, sizeof(f->DNI));
    	free(temp);
    
    	f->edad = pedirInt("\nPor favor, escriba su edad                :", 1, 99);
    	f->peso = pedirFloat("\nPor favor, escriba su peso                :", 1.0,
    			120.0);
    }
    void escribir_encolumnado(RegistroRef f, intRef p) {
    	/*
    	 La lista señalada por p con tiene las longitudes de los distintos
    	 campos, que se usarán para cortar las cadenas construidas por
    	 concatenación del valor del campo con una cadena de 80 caracteres
    	 llamada relleno.
    	 */
    	char relleno[80];
    	char temp[256];
    	memset(relleno, ' ', 79);
    	relleno[79] = '\0';
    	strcpy(temp, f->nombre);
    	strcat(temp, relleno);
    	temp[p[0]] = '\0'; /* Se especifica la posición del fin de cadena */
    	printf("%s", temp);/* Se imprime en pantalla el primer campo */
    	strcpy(temp, f->ape_1);
    	strcat(temp, relleno);
    	temp[p[1]] = '\0'; /* Se especifica la posición del fin de cadena */
    	printf("%s", temp);/* Se imprime en pantalla el segundo campo */
    	strcpy(temp, f->ape_2);
    	strcat(temp, relleno);
    	temp[p[2]] = '\0'; /* Se especifica la posición del fin de cadena */
    	printf("%s", temp);/* Se imprime en pantalla el tercer campo */
    	strcpy(temp, f->direc);
    	strcat(temp, relleno);
    	temp[p[3]] = '\0'; /* Se especifica la posición del fin de cadena */
    	printf("%s", temp);/* Se imprime en pantalla el cuarto campo */
    	strcpy(temp, f->telef);
    	strcat(temp, relleno);
    	temp[p[4]] = '\0'; /* Se especifica la posición del fin de cadena */
    	printf("%s", temp);/* Se imprime en pantalla el quinto campo */
    	strcpy(temp, f->DNI);
    	strcat(temp, relleno);
    	temp[p[5]] = '\0'; /* Se especifica la posición del fin de cadena */
    	printf("%s", temp);/* Se imprime en pantalla el sexto campo */
    	sprintf(temp, "%8d", f->edad);
    	printf("%s", temp);
    	sprintf(temp, "%8.2f", f->peso);
    	printf("%s\n", temp);
    }
    
    void hacer_modificaciones(RegistroRef f) {
    	int i, n, m, num_disponibles_para_modificaciones = 0;
    	Registro temp;
    	printf("\n\nProceso de modificaciones\n\n");
    	for (i = 0; i < MAX_PERSONAS; i++)
    		if (f[i].nombre[0] != '*') {
    			printf("El registro %d puede ser modificado.\n", i);
    			num_disponibles_para_modificaciones++;
    		}
    	if (num_disponibles_para_modificaciones == 0) {
    		printf("\nNo hay registros disponibles para hacer modificaciones.");
    		return;
    	}
    	do {
    		n = pedirInt("Escriba el número del registro que quiera modificar: ",
    				0, MAX_PERSONAS - 1);
    	} while (f[n].nombre[0] == '*');
    	printf("\nIntroduzca los nuevos datos (confirmación posterior):\n\n");
    	leer(&temp);
    	m = confirmar("Desea guardar los cambios?");
    	if (m)
    		f[n] = temp;
    	else
    		printf("\nModificaciones descartadas\n");
    }
    
    
    /* Resultado de la ejecución del programa: Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 0 Proceso de altas El registro 0 puede causar alta. El registro 1 puede causar alta. El registro 2 puede causar alta. Escriba el número del registro que quiere rellenar: 0 Por favor, escriba su nombre : Juan José Por favor, escriba su primer apellido : Pérez Por favor, escriba su segundo apellido : López Por favor, escriba su dirección : 13, Rue des Oiseaux Por favor, escriba su teléfono : 555-5555 Por favor, escriba su DNI : 555555555 Por favor, escriba su edad : 55 Por favor, escriba su peso : 55 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 0 Proceso de altas El registro 1 puede causar alta. El registro 2 puede causar alta. Escriba el número del registro que quiere rellenar: 2 Por favor, escriba su nombre : José Por favor, escriba su primer apellido : García Por favor, escriba su segundo apellido : García Por favor, escriba su dirección : Calle del Pez, 13 Por favor, escriba su teléfono : 666-666666 Por favor, escriba su DNI : 666666666 Por favor, escriba su edad : 66 Por favor, escriba su peso : 66 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 3 El contenido de los registros ocupados es como sigue: Juan José Pérez López 13, Rue des O 555-555 555555 55 55.00 José García García Calle del Pez 666-666 666666 66 66.00 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 2 Proceso de modificaciones El registro 0 puede ser modificado. El registro 2 puede ser modificado. Escriba el número del registro que quiera modificar: 2 Introduzca los nuevos datos (confirmación posterior): Por favor, escriba su nombre : José R. Por favor, escriba su primer apellido : Pérez Por favor, escriba su segundo apellido : García Por favor, escriba su dirección : 13, Rue d'O Por favor, escriba su teléfono : 666-666666666 Por favor, escriba su DNI : 66666666 Por favor, escriba su edad : 57 Por favor, escriba su peso : 56 Escriba 1 para confirmar cambios, 2 para descartar: 1 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 2 Proceso de modificaciones El registro 0 puede ser modificado. El registro 1 puede ser modificado. El registro 2 puede ser modificado. Escriba el número del registro que quiera modificar: 3 Escriba el número del registro que quiera modificar: 2 Introduzca los nuevos datos (confirmación posterior): Por favor, escriba su nombre : L Por favor, escriba su primer apellido : L Por favor, escriba su segundo apellido : L Por favor, escriba su dirección : L Por favor, escriba su teléfono : L Por favor, escriba su DNI : L Por favor, escriba su edad : 1 Por favor, escriba su peso : 1 Escriba 1 para confirmar cambios, 2 para descartar: 2 Modificaciones descartadas Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 3 El contenido de los registros ocupados es como sigue: Juan José Pérez López 13, Rue des O 555-555 555555 55 55.00 José R. Pérez García 13, Rue d'O 666-666 666666 57 56.00 José García García Calle del Pez 666-666 666666 66 66.00 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 1 Proceso de bajas El registro 0 puede causar baja. El registro 1 puede causar baja. El registro 2 puede causar baja. Escriba el número del registro que quiera eliminar (confirmación posterior): 2 Escriba 1 para confirmar la baja, 2 para descartar: 1 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 3 El contenido de los registros ocupados es como sigue: Juan José Pérez López 13, Rue des O 555-555 555555 55 55.00 José R. Pérez García 13, Rue d'O 666-666 666666 57 56.00 Altas (0) Bajas (1) Modificaciones (2) Ver registros (3) Salir (4) : 4 Saliendo... Fin del programa */

    Notas
    Este programa es algo más ambicioso, y muestra una sencilla base de datos con las posibilidades habituales. Como puede observarse, los detalles de implementación se extienden a todas las funciones, lo cual supondría la necesidad d ehacer grandes cambios si se modificarla la implementación.
    El proceso de altas, bajas y modificaciones se basa en insertar un asterisco en el campo de nombre del registro en cuestión: estos registros se consideran disponibles para almacenar información; los otros sólo pueden ser dados de baja o modificados. Se garantiza que no sea posible dar de alta un registro ocupado, ni dar de baja un registro vacío. Tanto las altas como las bajas se realizan a través de la función leer(), que admite como argumento un puntero de registro. Adicionalmente, se permite abortar los procesos de baja, por si el usuario cometiera un error. En ambos procesos se ofrece una lista formada por los números de los registros disponibles en uno en otro sentido (vacíos, para dar altas, o llenos, para dar bajas). Las modificaciones se realizan con un procedimiento análogo a las altas; una vez introducidos los datos, se ofrece al usuario la posibilidad de descartar las modificaciones.
    Se muestra el resultado de una ejecución del programa; se observará que entran en acción todos los mecanismos de protección implantados (no se admiten campos alfanuméricos vacíos, se cortan los caracteres sobrantes, se comprueban los números de los registros, etc). Se ha hecho uso de una lista estática de registros, según lo indicado.

  6. Ejercicio 0202r06.- Construir un programa como el anterior, basado esta vez en una lista estática de punteros de estructuras (RegistroRef bdd[MAX_PERSONAS]). El programa dará inicialmente el valor NULL a todos los punteros de la lista; se considera que un registro está vacío cuando su puntero es nulo. Las altas reservarán dinámicamente memoria para un registro (con malloc o calloc) y las bajas eliminarán realmente el registro (mediante free).

    La solución puede ser la siguiente:
    #include<stdio.h>
    #include<stdlib.h>
    
    
    #define MAX_PERSONAS 3
    #define NUM_OPCIONES 5
    
    typedef struct Registro {
     char nombre[15];
     char ape_1[15];
     char ape_2[15];
     char direc[15];
     char telef[9];
     char DNI[8];
     int edad;
     float peso;
    } Registro;
    
    typedef Registro * RegistroRef;
    
    RegistroRef bdd[MAX_PERSONAS];
    /*
     Las funciones siguientes permiten determinar, respectivamente,
     si un registro está vacío o no, y el puntero de cualquier
     registro de la base de datos. Dependen de la implementación,
     pero si cambia la implementación no varía la signatura de las
     funciones y por tanto no varía la estructura del programa.
     
     Cambia la implementación.
     
     No cambia el programa.
    */
    
    int registro_vacio(int i);
    RegistroRef pdr(int i);
    void llenar_registro(int n);
    
    int leer_cadena(charRef p, int longitud_maxima);
    float leerfloat(charRef p, float valor_min, float valor_max);
    double leerdouble(charRef p, double valor_min, double valor_max);
    
    void leer(RegistroRef f);
    void escribir_encolumnado(RegistroRef f, intRef p);
    
    void iniciacion();
    void ver(intRef nc);
    void dar_altas();
    void dar_bajas();
    void hacer_modificaciones();
    
    
    int main(int argc, charRef argv[])
     {
      int num_columnas[] = {15, 15, 15, 15, 9, 8, 8, 8}; 
      enum {Altas, Bajas, Modificaciones, Ver, Salir};
      char menu[][20] = {"Altas", "Bajas", "Modificaciones", "Ver registros", "Salir"};
      char opcion;
      int i, caso;
      iniciacion();
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d) ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Mostrar en pantalla la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Altas:  dar_altas();
              break;
          case Bajas:  dar_bajas();
              break;
          case Modificaciones: hacer_modificaciones();
              break;
          case Ver:  ver(num_columnas);
              break;
          case Salir:  printf("\nSaliendo...\n", menu[caso]);
              break;
          default:  printf("\n\nOpción incorrecta\n\n");
              break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
    /*
     Las funciones
     
      registro_vacio()
      pdr()
      iniciacion()
     
     dependen directamente de la estructura de datos empleada
     en la implementación. Si cambia la estructura de datos,
     cambia la implementación de estas funciones.
     
     Pero su signatura no cambia!
     
    */
    int registro_vacio(int i){
     return bdd[i] == NULL;
    }
    
    RegistroRef pdr(int i){
     return bdd[i];
    }
    
    void llenar_registro(int n)
    {
     RegistroRef temp;
     temp = malloc(sizeof(struct Registro));
     if (temp)
      {
       leer(temp);
       bdd[n] = temp;
      }
    }
    
    void vaciar_registro(int n)
    {
     free(bdd[n]);
     bdd[n] = NULL;
    }
    
    void iniciacion() {
     int i;
     for(i=0;i<MAX_PERSONAS;i++)
      bdd[i] = NULL;
    }
    
    void ver(intRef nc)
    {
     int i;
     int ocupados = 0;
     printf("\n\nListado de la base de datos:\n\n");
     for (i=0;i<MAX_PERSONAS;i++)
      if (!registro_vacio(i))
       {
        ocupados++;
        escribir_encolumnado(pdr(i), nc);
       }
     if (ocupados == 0)
      printf("\nTodos los registros están vacíos.\n\n");
     else
      printf("\nHay %d registros ocupados.\n\n", ocupados);
    }
    
    void dar_altas()
    {
     int i, n, num_disponibles_para_alta = 0;
     printf("\n\nProceso de altas\n\n");
     for(i=0;i<MAX_PERSONAS;i++)
      if (registro_vacio(i))
       {
        printf("El registro %d puede causar alta.\n", i);
        num_disponibles_para_alta++;
       }
     if (num_disponibles_para_alta == 0)
      {
       printf("\nNo hay registros disponibles para la alta.");
       return;
      }
      
     do
      {
       printf("Escriba el número del registro que quiere rellenar: ");
       scanf("%d", &n);
       fpurge(stdin);
      } while (!registro_vacio(n));
     llenar_registro(n);
    }
    
    void dar_bajas() {
     int i, n, m, num_disponibles_para_baja = 0;
     printf("\n\nProceso de bajas\n\n");
     for(i=0;i<MAX_PERSONAS;i++)
      if (!registro_vacio(i))
       {
        printf("El registro %d puede causar baja.\n", i);
        num_disponibles_para_baja ++;
       }
     if (num_disponibles_para_baja == 0)
      {
       printf("\nNo hay registros disponibles para la baja.");
       return;
      }
     do
      {
       printf("Escriba el número del registro que quiera eliminar (confirmación posterior): ");
       scanf("%d", &n);
       fpurge(stdin);
      } while (registro_vacio(n));
     do
      {
       printf("Escriba 1 para confirmar la baja, 2 para descartar: ");
       scanf("%d", &m);
       fpurge(stdin);
      } while (m != 1 && m != 2);
      if (m == 1)
       {
        vaciar_registro(n);
       }
      else
       printf("\nNo se ha efectuado la baja\n");
    }
    
    void leer(RegistroRef f){
     int temp;
     do
      {
       printf("\nPor favor, escriba su nombre              : ");
       temp = leer_cadena(f->nombre, sizeof(f->nombre));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su primer apellido     : ");
       temp = leer_cadena(f->ape_1, sizeof(f->ape_1));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su segundo apellido    : ");
       temp = leer_cadena(f->ape_2, sizeof(f->ape_2));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su dirección           : ");
       temp = leer_cadena(f->direc, sizeof(f->direc));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su teléfono            : ");
       temp = leer_cadena(f->telef, sizeof(f->telef));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su DNI                 : ");
       temp = leer_cadena(f->DNI, sizeof(f->DNI));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     f->edad = leerint("\nPor favor, escriba su edad                :",1, 99);
     f->peso = leerfloat("\nPor favor, escriba su peso                :", 1.0, 120.0);
    }
    void escribir_encolumnado(RegistroRef f, intRef p){
    /*
     La lista señalada por p con tiene las longitudes de los distintos
     campos, que se usarán para cortar las cadenas construidas por
     concatenación del valor del campo con una cadena de 80 caracteres
     llamada relleno.
    */
     char relleno[80];
     char temp[256];
     memset(relleno,' ',79);
     relleno[79]  = '\0';
     strcpy(temp, f->nombre);
     strcat(temp, relleno);
     temp[p[0]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el primer campo */
     strcpy(temp, f->ape_1);
     strcat(temp, relleno);
     temp[p[1]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el segundo campo */
     strcpy(temp, f->ape_2);
     strcat(temp, relleno);
     temp[p[2]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el tercer campo */
     strcpy(temp, f->direc);
     strcat(temp, relleno);
     temp[p[3]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el cuarto campo */
     strcpy(temp, f->telef);
     strcat(temp, relleno);
     temp[p[4]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el quinto campo */
     strcpy(temp, f->DNI);
     strcat(temp, relleno);
     temp[p[5]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el sexto campo */
     sprintf(temp,"%8d", f->edad);
     printf("%s", temp);
     sprintf(temp,"%8.2f", f->peso);
     printf("%s\n", temp);
    }
    
    
    int leer_cadena(charRef p, int longitud_maxima)
    {
     int n;
     if (p==NULL || longitud_maxima==0)
      return 0;
     fgets(p, longitud_maxima, stdin);
     fpurge(stdin);
     n = strlen(p);
     p[n-1] = '\0';
     return n;
    }
    
    int leerint(charRef p, int valor_min, int valor_max){
     char temp[80];
     int resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un entero (%d <= n <= %d): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtol(temp, NULL, 10);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    float leerfloat(charRef p, float valor_min, float valor_max){
     char temp[80];
     float resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un float (%6.2f <= n <= %6.2f): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    double leerdouble(charRef p, double valor_min, double valor_max){
     char temp[80];
     double resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un double (%6.2lf <= n <= %6.2lf): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    void hacer_modificaciones(){
     int i, n, m, num_disponibles_para_modificaciones = 0;
     struct Registro temp;
     printf("\n\nProceso de modificaciones\n\n");
     for(i=0;i<MAX_PERSONAS;i++)
      if (!registro_vacio(i))
       {
        printf("El registro %d puede ser modificado.\n", i);
        num_disponibles_para_modificaciones++;
       }
     if (num_disponibles_para_modificaciones == 0)
      {
       printf("\nNo hay registros disponibles para hacer modificaciones.");
       return;
      }
     do
      {
       printf("Escriba el número del registro que quiera modificar: ");
       scanf("%d", &n);
       fpurge(stdin);
      } while (registro_vacio(n) || n<0 || n>=MAX_PERSONAS);
     printf("\nIntroduzca los nuevos datos (confirmación posterior):\n\n");
     leer(&temp);
     do
      {
       printf("Escriba 1 para confirmar cambios, 2 para descartar: ");
       scanf("%d", &m);
       fpurge(stdin);
      } while (m != 1 && m != 2);
      if (m == 1)
       *pdr(n) = temp;
      else
       printf("\nModificaciones descartadas\n");
    }
    
    

    Notas
    Este programa es, en principio, una ligera modificación del anterior. En efecto, se trata de cambiar la implementación de la estructura de datos, pasando de una lista de estructuras a una lista de punteros de estructura. Los cambios efectuados son considerables, porque se ha recurrido al uso de varias funciones que actúan como intermediarios entre las necesidades del programa y la implementación interna usada para almacenar los datos. En este sentido, las funciones registro_vacio(), pdr() y llenar_registro() se han utilizado como primitivas en todas las demás funciones. Además, se ha pasado a utilizar una variable global, que emplean las tres funciones anteriores a instancias de todas las demás. El objetivo perseguido es aislar lo cambios que produciría un cambio en la estructura de datos básica. De no hacerse así, un cambio de implementación de esta estructura produciría cambios en todo el programa.
    Obsérvese que se han eliminado argumentos en varias funciones, al hacerlos innecesarios el uso de las primitivas de acceso indicadas anteriormente. Con esto se pretende evitar cambios en las signaturas y por tanto aislar los cambios en zonas concretas del programa.
    Las funciones registro_vacio(), pdr(), llenar_registro() y vaciar_registro() dependen necesariamente de la implementación, pero ahi acaba el problema: todas las demás funciones se pueden construir empleando a las anteriores como primitivas. En términos de la metodología orientada a objetos, el uso de métodos de acceso aísla a los métodos de los atributos. El comportamiento del programa es totalmente idéntico al obtenido en el ejercicio anterior.

  7. Ejercicio 0202r07.- Modificar el programa anterior para que haga uso de una lista dinámica de estructuras (bdd es un bloque formado por estructuras y reservado dinámicamente).

    La solución puede ser la siguiente:
    #include<stdio.h>
    #include<stdlib.h>
    
    
    #define MAX_PERSONAS 3
    #define NUM_OPCIONES 5
    
    struct Registro {
     char nombre[15];
     char ape_1[15];
     char ape_2[15];
     char direc[15];
     char telef[9];
     char DNI[8];
     int edad;
     float peso;
    } Registro;
    
    typedef Registro * RegistroRef;
    
    RegistroRef bdd;
    /*
     Las funciones siguientes permiten determinar, respectivamente,
     si un registro está vacío o no, y el puntero de cualquier
     registro de la base de datos. Dependen de la implementación,
     pero si cambia la implementación no varía la signatura de las
     funciones y por tanto no varía la estructura del programa.
     
     Cambia la implementación.
     
     No cambia el programa.
    */
    
    int registro_vacio(int i);
    RegistroRef pdr(int i);
    void llenar_registro(int n);
    
    int leer_cadena(charRef p, int longitud_maxima);
    float leerfloat(charRef p, float valor_min, float valor_max);
    double leerdouble(charRef p, double valor_min, double valor_max);
    
    void leer(RegistroRef f);
    void escribir_encolumnado(RegistroRef f, intRef p);
    
    void iniciacion();
    void ver(intRef nc);
    void dar_altas();
    void dar_bajas();
    void hacer_modificaciones();
    
    
    int main(int argc, charRef argv[])
     {
      int num_columnas[] = {15, 15, 15, 15, 9, 8, 8, 8}; 
      enum {Altas, Bajas, Modificaciones, Ver, Salir};
      char menu[][20] = {"Altas", "Bajas", "Modificaciones", "Ver registros", "Salir"};
      char opcion;
      int i, caso;
      iniciacion();
      do
       {
       /* Visualizar menú y leer la opción */
        printf("\n");
        for(i=0;i<NUM_OPCIONES;i++)
         printf("%s (%d) ", menu[i], i);
        printf(" : ");
        scanf("%c", &opcion);
        fpurge(stdin);
        
       /* Mostrar en pantalla la opción seleccionada */
        caso = opcion - '0';
        switch(caso)
         {
          case Altas:  dar_altas();
              break;
          case Bajas:  dar_bajas();
              break;
          case Modificaciones: hacer_modificaciones();
              break;
          case Ver:  ver(num_columnas);
              break;
          case Salir:  printf("\nSaliendo...\n", menu[caso]);
              break;
          default:  printf("\n\nOpción incorrecta\n\n");
              break;
         }
       }
       while(caso != Salir);
      printf("\n\nFin del programa\n\n");
      return 0;
     }
    
    /*
     Las funciones
     
      registro_vacio()
      pdr()
      iniciacion()
     
     dependen directamente de la estructura de datos empleada
     en la implementación. Si cambia la estructura de datos,
     cambia la implementación de estas funciones.
     
     Pero su signatura no cambia!
     
    */
    int registro_vacio(int i){
     return bdd[i].nombre[0] == '*';
    }
    
    RegistroRef pdr(int i){
     return bdd+i;
    }
    
    void llenar_registro(int n)
    {
     struct Registro temp;
     leer(&temp);
     bdd[n] = temp;
    }
    
    void vaciar_registro(int n)
    {
     strcpy(bdd[n].nombre, "*");
    }
    
    void iniciacion() {
     int i;
     bdd = malloc(sizeof(struct Registro)*MAX_PERSONAS);
     for(i=0; i<MAX_PERSONAS;i++)
      strcpy(bdd[i].nombre, "*");
    }
    /* Las demás funciones están construidas tomando como base a las cuatro
    anteriores, luego no se ven afectadas por los cambios efectuados en la
    implementación.
    */
    void ver(intRef nc)
    {
     int i;
     int ocupados = 0;
     printf("\n\nListado de la base de datos:\n\n");
     for (i=0;i<MAX_PERSONAS;i++)
      if (!registro_vacio(i))
       {
        ocupados++;
        escribir_encolumnado(pdr(i), nc);
       }
     if (ocupados == 0)
      printf("\nTodos los registros están vacíos.\n\n");
     else
      printf("\nHay %d registros ocupados.\n\n", ocupados);
    }
    
    void dar_altas()
    {
     int i, n, num_disponibles_para_alta = 0;
     printf("\n\nProceso de altas\n\n");
     for(i=0;i<MAX_PERSONAS;i++)
      if (registro_vacio(i))
       {
        printf("El registro %d puede causar alta.\n", i);
        num_disponibles_para_alta++;
       }
     if (num_disponibles_para_alta == 0)
      {
       printf("\nNo hay registros disponibles para la alta.");
       return;
      }
      
     do
      {
       printf("Escriba el número del registro que quiere rellenar: ");
       scanf("%d", &n);
       fpurge(stdin);
      } while (!registro_vacio(n));
     llenar_registro(n);
    }
    
    void dar_bajas() {
     int i, n, m, num_disponibles_para_baja = 0;
     printf("\n\nProceso de bajas\n\n");
     for(i=0;i<MAX_PERSONAS;i++)
      if (!registro_vacio(i))
       {
        printf("El registro %d puede causar baja.\n", i);
        num_disponibles_para_baja ++;
       }
     if (num_disponibles_para_baja == 0)
      {
       printf("\nNo hay registros disponibles para la baja.");
       return;
      }
     do
      {
       printf("Escriba el número del registro que quiera eliminar (confirmación posterior): ");
       scanf("%d", &n);
       fpurge(stdin);
      } while (registro_vacio(n));
     do
      {
       printf("Escriba 1 para confirmar la baja, 2 para descartar: ");
       scanf("%d", &m);
       fpurge(stdin);
      } while (m != 1 && m != 2);
      if (m == 1)
       {
        vaciar_registro(n);
       }
      else
       printf("\nNo se ha efectuado la baja\n");
    }
    
    void leer(RegistroRef f){
     int temp;
     do
      {
       printf("\nPor favor, escriba su nombre              : ");
       temp = leer_cadena(f->nombre, sizeof(f->nombre));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su primer apellido     : ");
       temp = leer_cadena(f->ape_1, sizeof(f->ape_1));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su segundo apellido    : ");
       temp = leer_cadena(f->ape_2, sizeof(f->ape_2));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su dirección           : ");
       temp = leer_cadena(f->direc, sizeof(f->direc));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su teléfono            : ");
       temp = leer_cadena(f->telef, sizeof(f->telef));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     do
      {
       printf("\nPor favor, escriba su DNI                 : ");
       temp = leer_cadena(f->DNI, sizeof(f->DNI));
       if (temp==1)
        printf("\nPerdón, este campo no puede estar vacío.\n");
      } while(temp==1);
     f->edad = leerint("\nPor favor, escriba su edad                :",1, 99);
     f->peso = leerfloat("\nPor favor, escriba su peso                :", 1.0, 120.0);
    }
    void escribir_encolumnado(RegistroRef f, intRef p){
    /*
     La lista señalada por p con tiene las longitudes de los distintos
     campos, que se usarán para cortar las cadenas construidas por
     concatenación del valor del campo con una cadena de 80 caracteres
     llamada relleno.
    */
     char relleno[80];
     char temp[256];
     memset(relleno,' ',79);
     relleno[79]  = '\0';
     strcpy(temp, f->nombre);
     strcat(temp, relleno);
     temp[p[0]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el primer campo */
     strcpy(temp, f->ape_1);
     strcat(temp, relleno);
     temp[p[1]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el segundo campo */
     strcpy(temp, f->ape_2);
     strcat(temp, relleno);
     temp[p[2]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el tercer campo */
     strcpy(temp, f->direc);
     strcat(temp, relleno);
     temp[p[3]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el cuarto campo */
     strcpy(temp, f->telef);
     strcat(temp, relleno);
     temp[p[4]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el quinto campo */
     strcpy(temp, f->DNI);
     strcat(temp, relleno);
     temp[p[5]] = '\0'; /* Se especifica la posición del fin de cadena */
     printf("%s", temp);/* Se imprime en pantalla el sexto campo */
     sprintf(temp,"%8d", f->edad);
     printf("%s", temp);
     sprintf(temp,"%8.2f", f->peso);
     printf("%s\n", temp);
    }
    
    
    int leer_cadena(charRef p, int longitud_maxima)
    {
     int n;
     if (p==NULL || longitud_maxima==0)
      return 0;
     fgets(p, longitud_maxima, stdin);
     fpurge(stdin);
     n = strlen(p);
     p[n-1] = '\0';
     return n;
    }
    
    int leerint(charRef p, int valor_min, int valor_max){
     char temp[80];
     int resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un entero (%d <= n <= %d): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtol(temp, NULL, 10);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    float leerfloat(charRef p, float valor_min, float valor_max){
     char temp[80];
     float resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un float (%6.2f <= n <= %6.2f): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    double leerdouble(charRef p, double valor_min, double valor_max){
     char temp[80];
     double resultado;
     do
      { 
       if (p!=NULL)
        printf("%s ", p);
       else
        printf("Por favor, escriba un double (%6.2lf <= n <= %6.2lf): ", valor_min, valor_max);
       fgets(temp, 80, stdin);
       fpurge(stdin);
       resultado = strtof(temp, NULL);
      } while (resultado < valor_min || resultado > valor_max);
     return resultado;
    }
    
    void hacer_modificaciones(){
     int i, n, m, num_disponibles_para_modificaciones = 0;
     struct Registro temp;
     printf("\n\nProceso de modificaciones\n\n");
     for(i=0;i<MAX_PERSONAS;i++)
      if (!registro_vacio(i))
       {
        printf("El registro %d puede ser modificado.\n", i);
        num_disponibles_para_modificaciones++;
       }
     if (num_disponibles_para_modificaciones == 0)
      {
       printf("\nNo hay registros disponibles para hacer modificaciones.");
       return;
      }
     do
      {
       printf("Escriba el número del registro que quiera modificar: ");
       scanf("%d", &n);
       fpurge(stdin);
      } while (registro_vacio(n) || n<0 || n>=MAX_PERSONAS);
     printf("\nIntroduzca los nuevos datos (confirmación posterior):\n\n");
     leer(&temp);
     do
      {
       printf("Escriba 1 para confirmar cambios, 2 para descartar: ");
       scanf("%d", &m);
       fpurge(stdin);
      } while (m != 1 && m != 2);
      if (m == 1)
       *pdr(n) = temp;
      else
       printf("\nModificaciones descartadas\n");
    }
    
    

    Notas
    En este programa se solicita modificar las estructuras básicas de datos empleadas en el programa anterior, y se recogen claro está los beneficios derivados del uso de funciones de acceso. La estructura del programa queda totalmente intacta, y basta concentrar nuestros esfuerzos en unas pocas funciones que dependen realmente de la implementación, y que se emplean como primitivas en todas las demás. De este modo los cambios quedan restringidos a lo mínino, y la estructura del programa, insistimos no se ve afectada para nada. El resultado de utilizar este programa es, como podrá apreciarse, totalmente idéntico al obtenido en los ejercicios anteriores. El secreto de lo reducido de los cambios que han sido necesarios está en el uso de unas pocas funciones primitivas que hacen de puente entre las tareas del programa (altas, bajas, modificaciones) y las estructura empleadas para almacenar los datos. Los cambios de implementación afectan únicamente a los métodos de acceso (el "puente") y el resto del programa no se ve afectado. Dicho en términos orientados a objetos, el acceso indirecto a los atributos de una clase supone el aislamiento de la implementación interna, con notables ventajas si cambia esta última.

  8. Ejercicio 0202r08.- Transformar struct Registro en struct NodoRegistro, con vistas a crear una lista enlazada de registros. Analizar el consumo de memoria derivado del uso de una lista de punteros y de una lista enlazada.

    La solución puede ser la siguiente:
    struct NodoRegistro {
     struct NodoRegistro * sig;
     char nombre[15];
     char ape_1[15];
     char ape_2[15];
     char direc[15];
     char telef[9];
     char DNI[8];
     int edad;
     float peso;
    }

    Notas
    En cuanto al consumo de memoria, es interesante comprobar que una lista de punteros de struct Nodo y una lista enlazada formada por elementos del tipo NodoRegistro ocupan exactamente el mismo espacio si están llenas. En efecto, el consumo "basal" de la lista de punteros es mayor, porque es preciso reservar espacio -estático o dinámico- para la lista de punteros. Esto no ocurre en el caso de una lista enlazada, que efectivamente tiene un consumo nulo de memoria cuando está vacía. Cuando la lista de punteros está llena, esto es, cuando cada elemento de la lista señala un nodo de tipo struct Registro, se tiene el mismo consumo de memoria que si se emplearan nodos de tipo struct NodoRegistro para almacenar la misma información. La desventaja de una lista de punteros es el consumo "basal", y su ventaja es la rapidez de acceso al i-ésimo elemento. La desventaja de una lista enlazada es su relativa lentitud de acceso (forzosamente secuencial) a los elementos que la componen; su ventaja es doble: menor consumo en baja ocupación, y menos probabilidades de sufrir problemas por fragmentación del cúmulo. Téngase en cuenta que se puede rehacer la lista de punteros mediante realloc() para ampliarla, pero cuanto mayor sea, más difícil será encontrar un bloque libre del tamaño adecuado.