Subido por mavn060

cap 8 estructuras y uniones

Anuncio
CAPÍTULO
8
Estructuras y uniones
8.1. Introducción
Cuando estudiamos arreglos en los capítulos 5 y 6, observamos que
representan un tipo de datos estructurado y permiten resolver un gran
número de problemas en forma efectiva. Definimos a los arreglos
como una colección finita, homogénea y ordenada de elementos. En
este capítulo estudiaremos dos tipos de datos estructurados que se
distinguen fundamentalmente de los arreglos porque sus elementos
pueden ser heterogéneos, es decir, pueden pertenecer —aunque no
necesariamente— a tipos de datos diferentes. Estas estructuras de
datos reciben el nombre de estructuras y uniones.
288
Capítulo 8. Estructuras y uniones
8.2. Estructuras
Las estructuras, conocidas generalmente con el nombre de registros, representan un tipo de datos estructurado. Se utilizan tanto para resolver problemas que
involucran tipos de datos estructurados, heterogéneos, como para almacenar información en archivos —como veremos en el siguiente capítulo. Las estructuras
tienen varios componentes, cada uno de los cuales puede constituir a su vez un
tipo de datos simple o estructurado. Sin embargo, los componentes del nivel más
bajo de un tipo estructurado, siempre son tipos de datos simples. Formalmente
definimos a una estructura de la siguiente manera:
“Una estructura es una colección de elementos finita y heterogénea.”
Finita porque se puede determinar el número de componentes y heterogénea porque todos los elementos pueden ser de tipos de datos diferentes. Cada componente
de la estructura se denomina campo y se identifica con un nombre único. Los
campos de una estructura pueden ser de tipos de datos diferentes como ya hemos
mencionado, simples o estructurados; por lo tanto, también podrían ser nuevamente
una estructura. Para hacer referencia a un campo de una estructura siempre debemos utilizar tanto el nombre de la variable tipo estructura como el nombre del
campo. En la figura 8.1 se muestra la representación gráfica de una estructura.
Estructura
Nombre de la variable tipo estructura
Segundo campo
Primer campo
FIGURA 8.1
Representación gráfica de una estructura
N-ésimo campo
8.2 Estructuras
289
EJEMPLO 8.1
Consideremos que por cada alumno de una universidad debemos almacenar la siguiente información:
• Matrícula del alumno (entero).
• Nombre del alumno (cadena de caracteres).
• Carrera del alumno (cadena de caracteres).
• Promedio del alumno (real).
• Dirección del alumno (cadena de caracteres).
La estructura de datos adecuada para almacenar esta información es la estructura. Cabe aclarar que no es posible utilizar un arreglo para resolver este problema, porque sus componentes deben ser del mismo tipo de datos. En la figura 8.2
se muestra la representación gráfica de este ejemplo.
Alumno
Matrícula
Nombre
Carrera
Promedio
Domicilio
FIGURA 8.2
Representación gráfica de la estructura del ejemplo 8.1
El primer campo de la estructura es Matrícula; el segundo, Nombre; el tercero,
Carrera, y así sucesivamente. Si queremos acceder entonces al primer campo de
la estructura debemos escribir variable-de-tipo-estructura-Alumno.Matrícula.
Si en cambio queremos hacer referencia al domicilio del alumno escribimos
variable-de-tipo-estructura-Alumno.Domicilio.
8.2.1. Declaración de estructuras
Observemos a continuación diferentes formas de declarar estructuras con la explicación correspondiente.
EJEMPLO 8.2
En el siguiente programa podemos observar la forma en que se declara la estructura del ejemplo 8.1, así como también diferentes formas en que los campos reciben valores.
8
290
Capítulo 8. Estructuras y uniones
Programa 8.1
#include <stdio.h>
#include <string.h>
/* Estructuras-1.
El programa muestra la manera en que se declara una estructura, así como la
➥forma en que se tiene acceso a los campos de las variables de tipo estructura
➥tanto para asignación de valores como para lectura y escritura. */
struct alumno
/* Declaración de la estructura. */
{
int matricula;
char nombre[20];
char carrera[20];
/* Campos de la estructura. */
float promedio;
char direccion[20];
};
/* Observa que la declaración de una estructura termina con punto y
➥coma. */
void main(void)
{
/* Observa que las variables de tipo estructura se declaran como cualquier otra
➥variable. a1, a2 y a3 son variables de tipo estructura alumno. */
➥struct alumno a1 = {120, ”María”, ”Contabilidad”, 8.9, ”Querétaro”}, a2, a3;
/* Los campos de a1 reciben valores directamente. */
char nom[20], car[20], dir[20];
int mat;
float pro;
/* Los campos de a2 reciben valores por medio de una lectura. */
printf(”\nIngrese la matrícula del alumno 2: ”);
scanf(”%d”, &a2.matricula);
fflush(stdin);
printf(”Ingrese el nombre del alumno 2:”);
gets(a2.nombre);
printf(”Ingrese la carrera del alumno 2: ”);
gets(a2.carrera);
printf(”Ingrese el promedio del alumno 2: ”);
scanf(”%f”, &a2.promedio);
fflush(stdin);
printf(”Ingrese la dirección del alumno 2: ”);
gets(a2.direccion);
/* Los campos de a3 reciben valores por medio de asignaciones. */
printf(”\nIngrese la matrícula del alumno 3: ”);
scanf(”%d”, &mat);
a3.matricula = mat;
fflush(stdin);
printf(”Ingrese el nombre del alumno 3: ”);
gets(nom);
8.2 Estructuras
strcpy(a3.nombre, nom);
printf(”Ingrese la carrera del alumno 3: ”);
gets(car);
strcpy(a3.carrera, car);
printf(”Ingrese el promedio del alumno 3: ”);
scanf(”%f”, &pro);
a3.promedio = pro;
fflush(stdin);
printf(”Ingrese la dirección del alumno 3: ”);
gets(dir);
strcpy(a3.direccion, dir);
/* Observe la forma en que se imprimen los campos de a1 y a2. */
printf(”\nDatos del alumno 1\n”);
printf(”%d\n”, a1.matricula);
puts(a1.nombre);
puts(a1.carrera);
printf(”%.2f\n”, a1.promedio);
puts(a1.direccion);
printf(”\nDatos del alumno 2\n”);
printf(”%d\n”, a2.matricula);
puts(a2.nombre);
puts(a2.carrera);
printf(”%.2f\n”, a2.promedio);
puts(a2.direccion);
/* Observa otra forma de escribir los campos de la variable de tipo estructura
a3. */
printf(”\nDatos del alumno 3\n”);
printf(”%d \t %s \t %s \t %.2f \t %s”, a3.matricula, a3.nombre, a3.carrera,
a3.promedio, a3.direccion);
}
EJEMPLO 8.3
En el siguiente programa podemos observar diferentes formas en que los campos
de las variables declaradas como apuntadores de una estructura reciben valores, así como también el acceso a los campos de estas variables.
Programa 8.2
#include <string.h>
/* Estructuras-2.
El programa muestra la manera en que se declara una estructura, así como la
➥forma en que se tiene acceso a los campos de los apuntadores de tipo estructura
➥tanto para lectura como para escritura. Se utiliza además una función que
➥recibe como parámetro un apuntador de tipo estructura. */
struct alumno
/* Declaración de la estructura. */
{
291
8
292
Capítulo 8. Estructuras y uniones
int matricula;
char nombre[20];
char carrera[20];
float promedio;
char direccion[20];
/* Campos de la estructura alumno. */
};
void Lectura(struct alumno *);
/* Prototipo de función. */
void main(void)
{
struct alumno a0 = {120, ”María”, ”Contabilidad”, 8.9, ”Querétaro”};
struct alumno *a3, *a4, *a5, a6;
/* Observa que las variables *a3, *a4 y *a5 se declaran como apuntadores de
➥tipo estructura alumno. a6 es una variable de tipo estructura alumno. */
a3 = &a0;
/* En este caso al apuntador de tipo estructura alumno a3
➥se le asigna la dirección de la variable de tipo estructura alumno, a0. */
a4 = new (struct alumno);
/* Nota que al apuntador a4 es necesario asignarle una dirección de memoria.
➥Para tener acceso a los campos de un apuntador de tipo estructura, utiliza uno
➥de los dos formatos siguientes:
apuntador->campo
o bien
(*apuntador).campo
En la lectura de los campos de la variable a4 se utilizan como ejemplo ambos
➥formatos. */
printf(”\nIngrese la matrícula del alumno 4: ”);
scanf(”%d”, &(*a4).matricula);
fflush(stdin);
printf(”Ingrese el nombre del alumno 4: ”);
gets(a4->nombre);
printf(”Ingrese la carrera del alumno 4: ”);
gets((*a4).carrera);
printf(”Ingrese promedio del alumno 4: ”);
scanf(”%f”, &a4->promedio);
fflush(stdin);
printf(”Ingrese la dirección del alumno 4: ”);
gets(a4->direccion);
a5 = new (struct alumno);
Lectura(a5);
/* En este caso se pasa el apuntador de tipo estructura alumno
a5 a la función Lectura. */
Lectura(&a6); /* En este caso se pasa la variable de tipo estructura alumno a6,
➥a la función Lectura. Observa que en este caso debemos utilizar el operador de
➥dirección para preceder a la variable. */
printf(”\nDatos del alumno 3\n”);
/* Observa la forma de escribir los campos de los apuntadores de tipo
➥estructura. */
8.2 Estructuras
printf(”%d\t%s\t%s\t%.2f\t%s”, a3->matricula, a3->nombre, a3->carrera,
➥a3->promedio, a3->direccion);
printf(”\nDatos del alumno 4\n”);
printf(”%d\t%s\t%s\t%.2f\t%s”, a4->matricula, a4->nombre, a4->carrera,
➥a4->promedio, a4->direccion);
printf(”\nDatos del alumno 5\n”);
printf(”%d\t%s\t%s\t%f\t%s”, a5->matricula, a5->nombre, a5->carrera,
➥a5->promedio, a5->direccion);
printf(”\nDatos del alumno 6\n”);
/* Observa la forma de escribir los campos de la variable tipo estructura. */
printf(”%d\t%s\t%s\t%.2f\t%s”, a6.matricula, a6.nombre, a6.carrera,
➥a6.promedio, a6.direccion);
}
void Lectura(struct alumno *a)
/* Esta función permite leer los campos de un apuntador de tipo estructura
➥alumno. */
{
printf(”\nIngrese la matrícula del alumno: ”);
scanf(”%d”, &(*a).matricula);
fflush(stdin);
printf(”Ingrese el nombre del alumno: ”);
gets(a->nombre);
fflush(stdin);
printf(”Ingrese la carrera del alumno: ”);
gets((*a).carrera);
printf(”Ingrese el promedio del alumno: ”);
scanf(”%f”, &a->promedio);
fflush(stdin);
printf(”Ingrese la dirección del alumno: ”);
gets(a->direccion);
}
8.2.2. Creación de sinónimos o alias
La instrucción typedef permite al usuario definir alias o sinónimos, es decir, nuevos tipos de datos equivalentes a los ya existentes. El objetivo de esta instrucción
consiste en utilizar nombres más apropiados y más cortos para los tipos de datos,
puesto que evitamos escribir la palabra struct en la declaración de las variables.
La instrucción typedef se puede utilizar tanto con tipos de datos simples como
con estructurados. Con los tipos de datos simples su uso no resulta muy práctico,
más bien es redundante. Por ejemplo, si tenemos que declarar cinco variables de
tipo entero, C1, C2, C3, C4 y C5, para un problema en particular, lo hacemos
de esta forma:
293
8
294
Capítulo 8. Estructuras y uniones
. . .
int C1, C2, C3, C4, C5;
. . .
Si en cambio utilizáramos la instrucción typedef, tendríamos que escribir las
siguientes instrucciones:
. . .
typedef int contador;
/* Se declara un tipo de datos definido por el
➥usuario,
contador en este caso, equivalente al tipo de dato
➥int. */
. . .
void main(void)
. . .
contador C1, C2, C3, C4, C5;
/* Posteriormente, ya sea en el programa principal o en una función,
➥declaramos las variables C1, C2, C3, C4 y C5 como de tipo contador. */
. . .
En los tipos de datos estructurados, específicamente en las estructuras, su uso es
importante ya que elimina la necesidad de escribir reiteradamente la palabra struct
cada vez que hacemos referencia a una variable o apuntador de tipo estructura.
Observemos a continuación la modificación que realizamos al programa 8.2.
. . .
typedef struct
{
int matricula;
char nombre[20];
char carrera[20];
float promedio;
char direccion[20];
} alumno;
/* Declaración de la estructura utilizando typedef.*/
/* alumno es el nuevo tipo de datos creado por el
➥usuario. */
void Lectura (alumno *); /* Prototipo de función. Observa que al haber creado
➥el tipo de datos definido por el usuario alumno, se elimina la necesidad de
➥escribir la palabra struct antes de alumno en el parámetro. */
void main(void)
{
alumno a0 = {120, ”María”, ”Contabilidad”, 8.9, ”Querétaro”}, *a3, *a4, *a5, a6;
/* En este caso se evita escribir la palabra struct en la declaración de las
➥variables tipo alumno. */
. . .
8.2 Estructuras
295
8.2.3. Estructuras anidadas
8
Las estructuras representan un tipo de datos estructurado, que tiene por lo tanto
varios componentes. Cada uno de estos componentes puede a su vez ser un tipo
de datos simple o estructurado. Las estructuras anidadas se presentan cuando
en la declaración de una estructura, por lo menos uno de sus componentes es una
estructura. Observemos el siguiente ejemplo.
EJEMPLO 8.4
Consideremos que en una empresa requieren almacenar la siguiente información
de cada empleado:
• Nombre del empleado (cadena de caracteres).
• Departamento de la empresa (cadena de caracteres).
• Sueldo (real).
• Domicilio
• Calle (cadena de caracteres).
• Número (entero).
• Código Postal (entero).
• Localidad (cadena de caracteres).
A continuación se observa la representación gráfica de esta estructura:
Empleado
Domicilio
Nombre
Departamento
Sueldo
Calle
Número
CP
Localidad
FIGURA 8.3
Representación gráfica de una estructura anidada
El programa muestra la manera como se declara una estructura anidada, así
como la forma de acceso a los campos de cada una de las variables o apuntadores de tipo estructura, tanto para lectura como para escritura. Observa que para
la lectura de los datos de algunas variables y apuntadores de tipo estructura se
utiliza una función.
296
Capítulo 8. Estructuras y uniones
Programa 8.3
#include <stdio.h>
#include <string.h>
/* Estructuras-3.
El programa muestra la manera en que se declara una estructura anidada, así
➥como la forma de acceso a los campos de las variables o apuntadores de tipo
➥estructura, tanto para lectura como para escritura. Se utiliza además una
➥función que recibe como parámetro un apuntador de tipo estructura. */
typedef struct
/* Declaración de la estructura domicilio utilizando
➥un typedef. */
{
char calle[20];
int numero;
int cp;
char localidad[20];
} domicilio;
struct empleado
/* Declaración de la estructura anidada empleado. */
{
char nombre[20];
char departamento[20];
float sueldo;
domicilio direccion;
/* direccion es un campo de tipo estructura
➥domicilio de la estructura empleado. */
};
void Lectura(struct empleado *a)
/* Función que permite leer los campos de un apuntador de tipo estructura
➥empleado. */
{
printf(”\nIngrese el nombre del empleado: ”);
gets(a->nombre);
fflush(stdin);
printf(”Ingrese el departamento de la empresa: ”);
gets(a->departamento);
printf(”Ingrese el sueldo del empleado: ”);
scanf(”%f”, &a->sueldo);
fflush(stdin);
printf(”—-Ingrese la dirección del empleado—-”);
printf(”\n\tCalle: ”);
gets(a->direccion.calle);
printf(”\tNúmero: ”);
scanf(”%d”, &a->direccion.numero);
printf(”\tCódigo Postal: ”);
scanf(”%d”, &a->direccion.cp);
fflush(stdin);
printf(”\tLocalidad: ”);
8.2 Estructuras
gets(a->direccion.localidad);
}
void main(void)
{
struct empleado e0 = {”Arturo”, ”Compras”, 15500.75, ”San Jerónimo”, 120,
➥3490, ”Toluca”};
struct empleado *e1, *e2, e3, e4;
/* Se declaran diferentes variables y apuntadores de la estructura empleado
➥para que el lector pueda apreciar también las diferentes formas en que los
➥campos reciben valores. */
/* En el programa principal se leen los campos de una variable, e3, y un
➥apuntador de tipo estructura, *e1. */
e1 = new (struct empleado);
printf(”\nIngrese el nombre del empleado 1: ”);
scanf(”%s”, &(*e1).nombre);
fflush(stdin);
printf(”Ingrese el departamento de la empresa: ”);
gets(e1->departamento);
printf(”Ingrese el sueldo del empleado: ”);
scanf(”%f”, &e1->sueldo);
printf(”—-Ingrese la dirección del empleado—-”);
printf(”\n\tCalle: ”);
fflush(stdin);
gets(e1->dirección, calle);
printf(”\tNúmero: ”);
scanf(”%d”, &e1->direccion.numero);
printf(”\tCódigo Postal: ”);
scanf(”%d”, &e1->direccion.cp);
printf(”\tLocalidad: ”);
fflush(stdin);
gets(e1->direccion.localidad);
printf(”\nIngrese el nombre del empleado 3: ”);
scanf(”%s”, &e3.nombre);
fflush(stdin);
printf(”Ingrese el departamento de la empresa: ”);
gets(e3.departamento);
printf(”Ingrese el sueldo del empleado: ”);
scanf(”%f”, &e3.sueldo);
printf(”—-Ingrese la dirección del empleado—-”);
printf(”\n\tCalle: ”);
fflush(stdin);
gets(e3.direccion.calle);
printf(”\tNúmero: ”);
scanf(”%d”, &e3.direccion.numero);
printf(”\tCódigo Postal: ”);
scanf(”%d”, &e3.direccion.cp);
printf(”\tLocalidad: ”);
fflush(stdin);
gets(e3.direccion.localidad);
297
8
298
Capítulo 8. Estructuras y uniones
/* En la función Lectura se leen los campos de una variable, e4, y un apuntador
➥de tipo estructura, *e2. */
e2 = new (struct empleado);
Lectura(e2);
Lectura(&e4);
printf(”\nDatos del empleado 1\n”);
printf(”%s\t%s\t%.2f\t%s\t%d\t%d\t%s”,
e1->nombre,
e1->departamento,
➥e1>sueldo,
e1->direccion.calle,
e1->direccion.numero,
e1->direccion.cp,
➥e1->direccion.localidad);
printf(”\nDatos del empleado 4n”);
printf(”%s\t%s\t%.2f\t%s\t%d\t%d\t%s”, e4.nombre, e4.departamento, e4.sueldo,
➥e4.direccion.calle, e4.direccion.numero, e4.direccion.cp, e4.direccion.localidad);
}
8.2.4. Estructuras con arreglos
Existen numerosos casos en la vida real en los que para resolver un problema de
manera eficiente necesitamos utilizar estructuras combinadas con arreglos.
Observemos el siguiente ejemplo, en el que uno de los campos de la estructura
es a su vez otro arreglo.
EJEMPLO 8.5
En una escuela almacenan la información de sus alumnos utilizando arreglos
unidimensionales. La siguiente información de cada alumno se guarda en una
estructura:
• Matrícula (entero).
• Nombre y apellido (cadena de caracteres).
• Promedios de las materias (arreglo unidimensional de reales).
Dato:
ARRE[N]
(donde ARRE es un arreglo unidimensional de tipo ALUMNO,
1 ≤ N ≤ 100).
Escribe un programa en C que obtenga lo siguiente:
a) La matrícula y el promedio de cada alumno.
b) Las matrículas de los alumnos cuya calificación en la tercera materia sea
mayor a 9.
c) El promedio general de la materia 4.
8.2 Estructuras
299
Programa 8.4
8
#include <stdio.h>
#include <string.h>
/* Escuela.
El programa genera información estadística de los alumnos de una escuela. */
typedef struct
/* Declaración de la estructura alumno utilizando un
➥typedef. */
{
int matricula;
char nombre[30];
float cal[5];
/* Observa que el campo de la estructura alumno es un arreglo
➥unidimensional. */
} alumno;
void
void
void
void
Lectura(alumno, int T);
F1(alumno *, int TAM);
F2(alumno *, int TAM);
F3(alumno *, int TAM);
/* Prototipos de funciones. */
void main(void)
{
alumno ARRE[50];
/* Se declara un arreglo unidimensional de tipo alumno. */
int TAM;
do
{
printf(”Ingrese el tamaño del arreglo: ”);
scanf(”%d”, &TAM);
}
while (TAM > 50 || TAM < 1); /* Se verifica que el tamaño del arreglo sea
➥correcto. */
Lectura(ARRE, TAM);
F1(ARRE, TAM);
F2(ARRE, TAM);
F3(ARRE, TAM);
}
void Lectura(alumno A[], int T)
/* La función Lectura se utiliza para leer un arreglo unidimensional de tipo
➥estructura alumno de T elementos. */
{
int I, J;
for (I=0; I<T; I++)
{
printf(”\nIngrese los datos del alumno %d”, I+1);
printf(”\nIngrese la matrícula del alumno: ”);
scanf(”%d”, &A[I].matricula);
300
Capítulo 8. Estructuras y uniones
fflush(stdin);
printf(”Ingrese el nombre del alumno:”);
gets(A[I].nombre);
for (J=0; J<5; J++)
{
printf(”\tIngrese la calificación %d
scanf(”%f”, &A[I].cal[J]);
}
del alumno %d: ”, J+1, I+1);
}
}
void F1(alumno A[], int T)
/* La función F1 obtiene la matrícula y el promedio de cada alumno. */
{
int I, J;
float SUM, PRO;
for (I=0; I<T; I++)
{
printf(”\nMatrícula del alumno: %d”, A[I].matricula);
SUM = 0.0;
for (J=0; J<5; J++)
SUM = SUM + A[I].cal[J];
PRO = SUM / 5;
printf(”\t\tPromedio: %.2f”, PRO);
}
}
void F2(alumno A[], int T)
/* La función F2 obtiene las matrículas de los alumnos cuya calificación en la
➥tercera materia es mayor a 9. */
{
int I;
printf(”\nAlumnos con calificación en la tercera materia > 9”);
for (I=0; I<T; I++)
if (A[I].cal[2] > 9)
printf(”\nMatrícula del alumno: %d”, A[I].matricula);
}
void F3(alumno A[], int T)
/* Esta función obtiene el promedio general del grupo de la materia 4. */
{
int I;
float PRO, SUM = 0.0;
for (I=0; I<T; I++)
SUM = SUM + A[I].cal[3];
PRO = SUM / T;
printf(”\n\nPromedio de la materia 4: %.2f”, PRO);
}
8.3 Uniones
8.3. Uniones
Las uniones representan también un tipo de datos estructurado. Son similares a las
estructuras. Sin embargo, se distinguen fundamentalmente de éstas porque sus
miembros comparten el mismo espacio de almacenamiento en la memoria interna rápida de la computadora. Son muy útiles para ahorrar memoria. Sin embargo,
es necesario considerar que sólo pueden utilizarse en aquellas aplicaciones en
que sus componentes no reciban valores al mismo tiempo. Es decir, sólo uno de
sus componentes puede recibir valor a la vez. El espacio de memoria reservado
para una unión corresponde a la capacidad del campo de mayor tamaño.
Formalmente definimos una unión de la siguiente manera:
“Una unión es una colección de elementos finita y heterogénea, en la cual sólo
uno de sus componentes puede recibir valor a la vez.”
8.3.1. Declaración de uniones
La declaración de uniones es similar a la de estructuras. Observemos a continuación en el siguiente ejemplo la forma de declarar uniones.
EJEMPLO 8.6
Supongamos que debemos almacenar la siguiente información de cada alumno de
una universidad:
• Matrícula del alumno (entero).
• Nombre del alumno (cadena de caracteres).
• Carrera del alumno (cadena de caracteres).
• Promedio del alumno (real).
• Teléfono celular (cadena de caracteres).
• Correo electrónico (cadena de caracteres).
El programa muestra la manera en que se declara una unión, así como la forma
en que se tiene acceso a los campos de cada una de las variables de tipo unión,
tanto para lectura como para escritura. Observa que en algunas variables de tipo
estructura se utiliza una función para la lectura de los datos.
301
8
302
Capítulo 8. Estructuras y uniones
Programa 8.5
#include <stdio.h>
#include <string.h>
/* Uniones.
El programa muestra la manera como se declara una unión, así como la forma de
➥acceso a los campos de las variables de tipo unión tanto para asignación
➥de valores como para lectura y escritura. */
union datos
{
char celular[15];
char correo[20];
};
/* Declaración de una unión. */
typedef struct
/* Declaración de una estructura utilizando typedef. */
{
int matricula;
char nombre[20];
char carrera[20];
float promedio;
union datos personales;
/* Observa que uno de los campos de la estructura alumno es una unión. */
} alumno;
void Lectura(alumno a);
/*
Prototipo de función. */
void main(void)
{
alumno a1 = {120, ”María”, ”Contabilidad”, 8.9, ”5-158-40-50”}, a2, a3;
/* Observa que sólo el primer componente de una unión puede recibir valores por
➥medio de este tipo de asignaciones. */
/* Para que puedas observar las diferentes formas en que los campos de las
➥variables de tipo estructura alumno reciben valores, ingresamos los valores
➥de los campos de tres formas diferentes. Los campos de a1 reciben valores
➥directamente, los campos de a2 se leen en el programa principal, y los campos
➥de a3 reciben valores a través de una función. */
printf(”Alumno 2\n”);
printf(”Ingrese la matrícula: ”);
scanf(”%d”, &a2.matricula);
fflush(stdin);
printf(”Ingrese el nombre: ”);
gets(a2.nombre);
fflush(stdin);
printf(”Ingrese la carrera: ”);
gets(a2.carrera);
printf(”Ingrese el promedio: ”);
scanf(”%f”, &a2.promedio);
fflush(stdin);
8.3 Uniones
printf(”Ingrese el correo electrónico: ”);
gets(a2.personales.correo);
/* Observa que en la variable a2 de tipo estructura alumno el segundo campo de la
➥unión recibe un valor. */
printf(”Alumno 3\n”);
Lectura(&a3); /* Se llama a una función para leer los campos de la variable a3. */
/* Impresión de resultados. */
printf(”\nDatos del alumno 1\n”);
printf(”%d\n”, a1.matricula);
puts(a1.nombre);
puts(a1.carrera);
printf(”%.2f\n”, a1.promedio);
puts(a1.personales.celular);
/* Observa que escribe el valor del teléfono celular asignado. */
➥puts(a1.personales.correo);
}
/* Observa que si tratamos de imprimir el campo correo, escribe basura. */
strcpy(a0.personales.correo, “hgimenez@hotmail.com”);
/* Se ingresa ahora un valor al segundo campo de la unión de la variable a0. */
puts(a0.personales.celular);
/* Ahora escribe basura en el campo del teléfono celular. */
puts(a0.personales.correo);
/* Escribe el contenido del campo (hgimenez@hotmail.com). */
printf(”\nDatos del alumno 2\n”);
printf(”%d\n”, a2.matricula);
puts(a2.nombre);
puts(a2.carrera);
printf(”%.2f\n”, a2.promedio);
puts(a2.personales.celular);
puts(a2.personales.correo);
/* Escribe basura. */
/* Escribe el contenido del segundo campo. */
printf(”Ingrese el teléfono celular del alumno 2: ”);
fflush(stdin);
gets(a2.personales.celular);
puts(a2.personales.celular);
puts(a2.personales.correo);
printf(”\nDatos del alumno 3\n”);
printf(”%d\n”, a3.matricula);
puts(a3.nombre);
puts(a3.carrera);
printf(”%.2f\n”, a3.promedio);
puts(a3.personales.celular);
puts(a3.personales.correo);
}
/* Escribe el teléfono celular ingresado. */
/* Ahora escribe basura. */
/* Escribe basura. */
303
8
304
Capítulo 8. Estructuras y uniones
void Lectura(alumno *a)
/* La función Lectura se utiliza para leer los campos de una variable de tipo
➥estructura alumno. */
{
printf(”\nIngrese la matrícula: ”);
scanf(”%d”, &(*a).matricula);
fflush(stdin);
printf(”Ingrese el nombre: ”);
gets(a->nombre);
fflush(stdin);
printf(”Ingrese la carrera: ”);
gets((*a).carrera);
printf(”Ingrese el promedio: ”);
scanf(”%f”, &a->promedio);
printf(”Ingrese el teléfono celular: ”);
fflush(stdin);
gets(a->personales.celular);
}
Problemas resueltos
Problema PR8.1
Una comercializadora farmacéutica distribuye productos a distintas farmacias de
la Ciudad de México. Para ello almacena en un arreglo unidimensional, ordenado
de menor a mayor en función de la clave, toda la información relativa a sus productos:
• Clave del producto (entero).
• Nombre del producto (cadena de caracteres).
• Existencia (entero).
• Precio unitario (real).
Dato:
(donde INV es un arreglo unidimensional de tipo PRODUCTO de N
elementos, 1 ≤ N ≤ 100).
INV [N]
Realice un programa en C que construya los siguientes módulos:
a) Ventas. El módulo registra la venta de diferentes productos a un cliente
—farmacia—. Obtiene el total de la venta y actualiza el inventario correspondiente. El fin de datos para la venta de un cliente es 0.
b) Reabastecimiento. Este módulo permite incorporar productos —cantidades—
al inventario. El fin de datos es 0.
Problemas resueltos
305
c) Nuevos Productos. El módulo permite incorporar nuevos productos al
inventario. Los productos se encuentran ordenados en el arreglo por su
clave. El fin de datos es 0.
d) Inventario. El módulo permite imprimir el inventario completo.
En la siguiente figura se muestra la representación gráfica de la estructura de datos necesaria para resolver este problema.
INV: Arreglo unidimensional de tipo estructura producto
INV
clave nombre precio existencia clave nombre precio existencia . . . clave nombre precio existencia
Producto 1
Producto 2
...
Producto 100
Estructura producto
FIGURA 8.4
Representación gráfica de la estructura de datos necesaria para el problema PR8.1
Programa 8.6
#include <stdio.h>
#include <string.h>
/* Comercializadora farmacéutica.
El programa maneja información sobre ventas, inventario, reabastecimiento y
➥nuevos productos de una comercializadora farmacéutica. */
typedef struct
{
int clave;
char nombre[15];
float precio;
int existencia;
} producto;
void
void
void
void
void
/* Declaración de la estructura producto. */
Lectura(producto *, int);
/*
Ventas(producto *, int);
Reabastecimiento(producto *, int);
Nuevos_Productos(producto *, int *);
Inventario(producto *, int);
Prototipos de funciones. */
8
306
Capítulo 8. Estructuras y uniones
void main(void)
{
producto INV[100];
/* Se declara un arreglo unidimensional de tipo estructura producto. */
int TAM, OPE;
do
{
printf(”Ingrese el número de productos: ”);
scanf(”%d”, &TAM);
}
while (TAM > 100 | | TAM < 1);
/* Se verifica que el número de productos ingresados sea correcto. */
Lectura(INV, TAM);
printf(”\nIngrese operación a realizar. \n\t\t1 – Ventas \n\t\t 2 –
➥Reabastecimiento \n\t\t
3 - Nuevos Productos \n\t\t 4 – Inventario \n\t\t
0 - Salir: ”);
scanf(”%d”, &OPE);
while (OPE)
{
switch (OPE)
{
case 1: Ventas(INV, TAM);
break;
case 2: Reabastecimiento(INV, TAM);
break;
case 3: Nuevos_Productos(INV, &TAM);
/* Se pasa el parámetro por referencia, porque se puede modificar el
➥número
de elementos del arreglo en la función. */
break;
case 4: Inventario(INV, TAM);
break;
};
printf(”\nIngrese operación a realizar. \n\t\t1 – Ventas \n\t\t 2 –
➥Reabastecimiento
\n\t\t 3 - Nuevos Productos \n\t\t 4 – Inventario \n\t\t
0 - Salir: ”);
scanf(”%d”, &OPE);
}
}
void Lectura(producto A[], int T)
/* Esta función se utiliza para leer un arreglo unidimensional de tipo
➥estructura producto de T elementos. */
{
int I;
for (I=0; I<T; I++)
{
printf(”\nIngrese información del producto %d”, I+1);
printf(”\n\tClave: ”);
scanf(”%d”, &A[I].clave);
fflush(stdin);
Problemas resueltos
printf(”\tNombre:”);
gets(A[I].nombre);
printf(”\tPrecio:”);
scanf(”%f”, &A[I].precio);
printf(”\tExistencia: ”);
scanf(”%d”, &A[I].existencia);
}
}
void Ventas(producto A[], int T)
/* Esta función se utiliza para manejar las venta a un cliente. Se ingresan
➥productos y cantidades, el fin de datos está dado por el cero. Además de
➥obtener el total de las ventas, se actualiza el inventario. */
{
int CLA, CAN, I, RES;
float TOT, PAR;
printf(”\nIngrese clave del producto -0 para salir-: ”);
scanf(”%d”, &CLA);
TOT = 0.0;
while (CLA)
{
printf(”\tCantidad: ”);
scanf(”%d”, &CAN);
I = 0;
while ((I < T) && (A[I].clave < CLA))
/* Se realiza una búsqueda para localizar la clave del producto. */
I++;
if ((I == T) | | (A[I].clave > CLA))
printf(”\nLa clave del producto es incorrecta”);
else
if (A[I].existencia >= CAN)
/* Se analiza si el stock es suficiente para satisfacer el pedido. */
{
A[I].existencia -= CAN;
/* Se actualiza el stock del producto. */
PAR = A[I].precio * CAN;
TOT += PAR;
}
else
{
printf(”\nNo existe en inventario la cantidad solicitada. Solo hay %d”,
A[I].existencia);
printf(” \nLos lleva 1 - Si
0 – No?: ”);
scanf(”%d”, &RES);
if (RES)
{
PAR = A[I].precio * A[I].existencia;
A[I].existencia = 0;
/* El stock queda en cero. */
TOT += PAR;
}
}
307
8
308
Capítulo 8. Estructuras y uniones
printf(”\nIngrese la siguiente clave del producto -0 para salir-:);
scanf(”%d”, &CLA);
}
printf(”\nTotal de la venta:
}
%f”, TOT);
void Reabastecimiento(producto A[], int T)
/* Esta función se utiliza para reabastecer al inventario. */
{
int CLA,CAN,I;
printf(”\nIngrese clave del producto -0 para salir-: ”);
scanf(”%d”, &CLA);
while (CLA)
{
I = 0;
while ((I < T) && (A[I].clave < CLA))
I++;
if ((I==T) || (A[I].clave > CLA))
printf(”\nLa clave del producto ingresada es incorrecta”);
else
{
printf(”\tCantidad: ”);
scanf(”%d”, &CAN);
A[I].existencia += CAN;
}
printf(”\nIngrese otra clave del producto -0 para salir-: ”);
scanf(”%d”, &CLA);
}
}
void Nuevos_Productos(producto A[], int *T)
/* Esta función se utiliza para incorporar nuevos productos al inventario.
➥Dado que los productos se encuentran ordenados por clave, puede suceder que
➥al insertar un nuevo producto haya que mover los elementos del arreglo para
➥que continúen ordenados. */
{
int CLA, I, J;
printf(”\nIngrese clave del producto -0 para salir-: ”);
scanf(”%d”, &CLA);
while ((*T < 30) && (CLA))
{
I=0;
while ((I < *T) && (A[I].clave < CLA))
/* Búsqueda de la posición que le corresponde a CLA en el arreglo. */
I++;
if (I == *T)
/* Se inserta el elemento en la última posición. */
{
A[I].clave = CLA;
printf(”\tNombre:”);
fflush(stdin);
gets(A[I].nombre);
Problemas resueltos
printf(”\tPrecio:”);
scanf(”%f”, &A[I].precio);
printf(”\tCantidad: ”);
scanf(”%d”, &A[I].existencia);
*T = *T + 1;
}
else
if (A[I].clave == CLA)
printf(”\nEl producto ya se encuentra en el inventario”);
else
{
for (J=*T; J>I; J--)
/* Se inserta el nuevo producto en el arreglo. Se mueven una posición
➥a la derecha los elementos
del arreglo que tengan una clave de producto mayor a la ingresada. */
A[J] = A[J-1];
A[I].clave = CLA;
printf(”\tNombre:”);
fflush(stdin);
gets(A[I].nombre);
printf(”\tPrecio:”);
scanf(”%f”, &A[I].precio);
printf(”\tCantidad: ”);
scanf(”%d”, &A[I].existencia);
*T = *T + 1;
}
printf(”\nIngrese otra clave de producto -0 para salir-: ”);
scanf(”%d”, &CLA);
}
if (*T == 30)
printf(”\nYa no hay espacio para incorporar nuevos productos”);
}
void Inventario(producto A[], int T)
/* Esta función se utiliza para escribir la información almacenada en —el
➥inventario— un arreglo unidimensional de tipo estructura producto de T
➥elementos. */
{
int I;
for (I=0; I<T; I++)
{
printf(”\nClave: %d”, A[I].clave);
printf(”\tNombre: %s”, A[I].nombre);
printf(”\tPrecio: %d”, A[I].precio);
printf(”\tExistencia: %d \n”, A[I].existencia);
}
}
309
8
310
Capítulo 8. Estructuras y uniones
Problema PR8.2
En una escuela guardan la información de sus alumnos utilizando arreglos unidimensionales. Se registra la siguiente información de cada alumno en una estructura:
• Matrícula del alumno (entero).
• Nombre y apellido (cadena de caracteres).
• Materias y promedios (arreglo unidimensional de estructura).
• Materia (cadena de caracteres).
• Promedio (real).
Dato:
ALU[N],
donde ALU es un arreglo unidimensional de tipo ALUMNO (1
≤ N ≤ 50).
Escribe un programa en C que obtenga lo siguiente:
a) La matrícula y el promedio general de cada alumno.
b) Las matrículas de los alumnos cuya calificación en la tercera materia sea
mayor a 9.
c) El promedio general de la materia 4.
Nota: El problema es similar al del ejemplo 8.5. Varía en la forma de almacenar
las calificaciones del alumno. Además de éstas, aparece el nombre de la materia.
En la siguiente figura se muestra la representación gráfica de la estructura de datos necesaria para resolver este problema.
ALU: Arreglo unidimensional de tipo estructura alumno
Estructura alumno
matrinom
Alumno 1
cal
matrinom
matpro
mat pro
ALU
cal
Alumno 2
. . . matri nom
...
cal
Alumno 50
Estructura matpro
cal: Arreglo unidimensional de tipo estructura matpro
FIGURA 8.5
Representación gráfica de la estructura de datos necesaria para resolver el problema PR8.2
Problemas resueltos
311
Programa 8.7
8
#include <stdio.h>
#include <string.h>
/* Escuela.
El programa genera información importante de los alumnos de una escuela. */
typedef struct
{
char mat[20];
int pro;
} matpro;
typedef struct
{
int matri;
char nom[20];
matpro cal[5];
/* Declaración de la estructura matpro. */
/* Materia. */
/* Promedio. */
/* Declaración de la estructura alumno. */
/* Matrícula. */
/* Nombre del alumno. */
/* Observa que cal es un arreglo unidimensional de tipo
➥estructura
matpro —la estructura definida en primer término. */
} alumno;
void
void
void
void
Lectura(alumno * , int);
F1(alumno *, int);
F2(alumno *, int);
F3(alumno *, int);
/* Prototipos de funciones. */
void main(void)
{
alumno ALU[50];
/* ALU es un arreglo unidimensional de tipo alumno. */
int TAM;
do
{
printf(”Ingrese el tamaño del arreglo: ”);
scanf(”%d”, &TAM);
}
while (TAM > 50 | | TAM < 1); /* Se verifica que el tamaño del arreglo sea
➥correcto. */
Lectura(ALU, TAM);
F1(ALU, TAM);
F2(ALU, TAM);
F3(ALU, TAM);
}
void Lectura(alumno A[], int T)
/* Esta función se utiliza para leer la información de un arreglo unidimensional
➥de tipo estructura alumno de T elementos. */
{
312
Capítulo 8. Estructuras y uniones
int I, J;
for(I=0; I<T; I++)
{
printf(”\nIngrese los datos del alumno %d”, I+1);
printf(”\nIngrese la matrícula del alumno: ”);
scanf(”%d”, &A[I].matri);
fflush(stdin);
printf(”Ingrese el nombre del alumno:”);
gets(A[I].nom);
for (J=0; J<5; J++)
{
printf(”\tMateria %d: ”, J+1);
fflush(stdin);
gets(A[I].cal[J].mat);
printf(”\tPromedio %d: ”, J+1);
scanf(”%d”, &A[I].cal[J].pro);
}
}
}
void F1(alumno A[], int T)
/* Esta función se utiliza para obtener la matrícula y el promedio general de
➥cada alumno. */
{
int I, J;
float SUM;
for (I=0; I<T; I++)
{
printf(”\nMatrícula del alumno : %d”, A[I].matri);
SUM = 0.0;
for (J=0; J<5; J++)
SUM = SUM + A[I].cal[J].pro;
SUM = SUM / 5;
printf(”\tPromedio: %.2f”, SUM);
}
}
void F2(alumno A[], int T)
/* Esta función se utiliza para obtener las matrículas de los alumnos cuya
➥calificación en la tercera materia es mayor a 9. */
{
int I;
printf(”\nAlumnos con calificación mayor a 9 en la tercera materia”);
for (I=0; I<T; I++)
if (A[I].cal[2].pro > 9)
printf(”\nMatrícula del alumno : %d”, A[I].matri);
}
void F3(alumno A[], int T)
/* Esta función se utiliza para obtener el promedio general de la cuarta materia. */
{
Problemas resueltos
int I;
float SUM = 0.0;
for (I=0; I<T; I++)
SUM = SUM + A[I].cal[3].pro;
SUM = SUM / T;
printf(”\n\nPromedio de la cuarta materia: %.2f”, SUM);
}
Problema PR8.3
En un hospital almacenan la siguiente información de sus pacientes:
•
•
•
•
•
Nombre y apellido (cadena de caracteres).
Edad (entero).
Sexo (caracter).
Condición (entero).
Domicilio (estructura).
• Calle (cadena de caracteres).
• Número (entero).
• Colonia (cadena de caracteres).
• Código Postal (cadena de caracteres).
• Ciudad (cadena de caracteres).
• Teléfono (cadena de caracteres).
Dato:
HOSPITAL[N] (donde HOSPITAL es un arreglo unidimensional de tipo
estructura PACIENTE, 1 ≤ N ≤ 100).
Nota: Condición se refiere al estado de salud en que ingresa el paciente. Los valores
que toma condición van de 1 a 5, y 5 representa el máximo grado de gravedad.
Escribe un programa en C que genere lo siguiente:
a) El porcentaje tanto de hombres como de mujeres registrados en el hospital.
b) El número de pacientes de cada una de las categorías de condición.
c) El nombre y teléfono de todos los pacientes que tuvieron una condición de
ingreso de máxima gravedad (5).
En la siguiente figura se muestra la representación gráfica de la estructura de datos
necesaria para resolver este problema.
313
8
314
Capítulo 8. Estructuras y uniones
HOSPITAL: Arreglo unidimensional de tipo estructura paciente
Estructura paciente
HOSPITAL
nom edad sexo con
dom
tel nom edad sexo con
dom
tel . . . nom edad sexo con
Paciente 2
Paciente 1
...
dom
tel
Paciente 100
dom
cal num col cp ciu
Estructura domicilio
FIGURA 8.6
Representación gráfica de la estructura de datos necesaria para resolver el problema PR8.3
Programa 8.8
#include <stdio.h>
#include <string.h>
/* Hospital.
El programa genera información acerca de los pacientes de un hospital. */
typedef struct
{
char cal[20];
int num;
char col[20];
char cp[5];
char ciu[20];
} domicilio;
typedef struct
{
char nom[20];
int edad;
char sexo;
int con;
domicilio dom;
char tel[10];
} paciente;
void
void
void
void
/* Declaración de la estructura domicilio. */
/*
/*
/*
/*
/*
Calle. */
Número. */
Colonia. */
Código Postal. */
Ciudad. */
/* Declaración de la estructura paciente. */
/* Nombre y apellido. */
/* Condición. */
/* Observa que el campo dom es de tipo estructura
➥domicilio. */
/* Teléfono. */
Lectura(paciente *, int);
F1(paciente *, int);
F2(paciente *, int);
F3(paciente *, int);
void main(void)
{
/* Prototipos de funciones. */
Problemas resueltos
paciente HOSPITAL[100];
/* Arreglo unidimensional de tipo estructura
➥paciente. */
int TAM;
do
{
printf(”Ingrese el número de pacientes: ”);
scanf(”%d”, &TAM);
}
/* Se verifica que el tamaño del arreglo sea
while (TAM > 50 | | TAM < 1);
➥correcto. */
Lectura(HOSPITAL, TAM);
F1(HOSPITAL, TAM);
F2(HOSPITAL, TAM);
F3(HOSPITAL, TAM);
}
void Lectura(paciente A[], int T)
/* Esta función se utiliza para leer un arreglo unidimensional de tipo
➥estructura paciente de T elementos. */
{
int I;
for (I=0; I<T; I++)
{
printf(”\n\t\tPaciente %d”, I+1);
fflush(stdin);
printf(”\nNombre: ”);
gets(A[I].nom);
printf(”Edad: ”);
scanf(”%d”, &A[I].edad);
printf(”Sexo (F-M): ”);
scanf(”%c”, &A[I].sexo);
printf(”Condición (1..5): ”);
scanf(”%d”, &A[I].con);
fflush(stdin);
printf(”\tCalle: ”);
gets(A[I].dom.cal);
printf(”\tNúmero: ”);
scanf(”%d”, &A[I].dom.num);
fflush(stdin);
printf(”\tColonia: ”);
gets(A[I].dom.col);
fflush(stdin);
printf(”\tCódigo Postal: ”);
gets(A[I].dom.cp);
fflush(stdin);
printf(”\tCiudad: ”);
gets(A[I].dom.ciu);
fflush(stdin);
printf(”Teléfono: ”);
gets(A[I].tel);
}
}
void F1(paciente A[], int T)
315
8
316
Capítulo 8. Estructuras y uniones
/* Esta función se utiliza para obtener el porcentaje tanto de hombres como de
➥mujeres registrados en el hospital. */
{
int I,FEM, MAS, TOT;
for (I=0; I<T; I++)
switch (A[I].sexo)
{
case ‘F’: FEM++;
break;
case ‘M’: MAS++;
break;
}
TOT = FEM + MAS;
printf(”\nPorcentaje de Hombres: %.2f%”, (float)MAS / TOT * 100);
printf(”\nPorcentaje de Mujeres: %.2f%”, (float)FEM / TOT * 100);
}
void F2(paciente A[], int T)
/* Esta función se utiliza para obtener el número de pacientes que ingresaron al
➥hospital en cada una de las categorías de condición. */
{
int I, C1 = 0, C2 = 0, C3 = 0, C4 = 0, C5 = 0;
for (I=0; I<T; I++)
switch (A[I].con)
{
case 1: C1++;
break;
case 2: C2++;
break;
case 3: C3++;
break;
case 4: C4++;
break;
case 5: C5++;
break;
}
printf(”\nNúmero pacientes en condición 1: %d”, C1);
printf(”\nNúmero pacientes en condición 2: %d”, C2);
printf(”\nNúmero pacientes en condición 3: %d”, C3);
printf(”\nNúmero pacientes en condición 4: %d”, C4);
printf(”\nNúmero pacientes en condición 5: %d”, C5);
}
void F3(paciente A[], int T)
/* La función F3 se utiliza para generar el nombre y teléfono de todos los
➥pacientes que tuvieron una condición de ingreso de máxima gravedad (5). */
{
int I;
printf(”\nPacientes ingresados en estado de gravedad”);
for (I=0; I<T; I++)
if (A[I].con == 5)
printf(”\nNombre: %s\tTeléfono: %s”, A[I].nom, A[I].tel);
}
Problemas resueltos
317
Problema PR8.4
8
Una empresa de bienes raíces de Lima, Perú, lleva información sobre las propiedades que tiene disponibles tanto para venta como para renta.
•
•
•
•
•
Clave de la propiedad (cadena de caracteres).
Superficie cubierta (real).
Superficie terreno (real).
Características (cadena de caracteres).
Ubicación geográfica.
• Zona (cadena de caracteres).
• Calle (cadena de caracteres).
• Colonia (cadena de caracteres).
• Precio (real).
• Disponibilidad (caracter).
Dato:
PROPIE[N]
(donde
es un arreglo unidimensional de tipo estructura
100).
PROPIE
PROPIEDADES, 1 ≤ N ≤
Escribe un programa en C que realice lo siguiente:
a) Un listado de las propiedades disponibles para venta en la zona de
Miraflores cuyo valor oscile entre 450,000 y 650,000 nuevos soles.
b) Al recibir una zona geográfica y un cierto rango respecto al monto, obtenga
un listado de todas las propiedades disponibles para renta.
Nota: El listado debe mostrar lo siguiente: clave de la propiedad, superficie cubierta, superficie total, características, calle, colonia y precio.
En la siguiente figura se muestra la representación gráfica de la estructura de datos necesaria para resolver este problema.
318
Capítulo 8. Estructuras y uniones
PROPIE: Arreglo unidimensional
de tipo estructura propiedades
Estructura propiedades
PROPIE
ubi
clave scu ste car
precio dispo clave scu ste car
ubi
precio dispo . . . clave scu ste car
Propiedad 2
Propiedad 1
...
ubi
precio dispo
Propiedad 100
ubi
zona calle colo
Estructura domicilio
FIGURA 8.7
Representación gráfica de la estructura de datos necesaria para resolver el problema PR8.4
Programa 8.9
#include <stdio.h>
#include <string.h>
/* Bienes raíces.
El programa maneja información sobre las propiedades que tiene una empresa
➥de bienes raíces de la ciudad de Lima, Perú, tanto para venta como para
➥renta. */
typedef struct
{
char zona[20];
char calle[20];
char colo[20];
} ubicacion;
typedef struct
{
char clave[5];
float scu;
float ste;
char car[50];
ubicacion ubi;
float precio;
char dispo;
} propiedades;
/* Declaración de la estructura ubicación.*/
/* Colonia. */
/* Declaración de la estructura propiedades.*/
/* Superficie cubierta. */
/* Superficie terreno. */
/* Características. */
/* Observa que este campo es de tipo estructura
ubicación. */
/* Disponibilidad. */
void Lectura(propiedades , int);
void F1(propiedades *, int);
void F2(propiedades *, int);
/* Prototipos de funciones. */
Problemas resueltos
void main(void)
{
propiedades PROPIE[100];
/* Se declara un arreglo unidimensional de tipo estructura propiedades. */
int TAM;
do
{
printf(”Ingrese el número de propiedades: ”);
scanf(”%d”, &TAM);
}
while (TAM > 100 | | TAM < 1);
/* Se verifica que el tamaño del arreglo sea correcto. */
Lectura(PROPIE, TAM);
F1(PROPIE, TAM);
F2(PROPIE, TAM);
}
void Lectura(propiedades A[], int T)
/* Esta función se utiliza para leer un arreglo unidimensional de tipo estructura
➥propiedades de T elementos. */
{
int I;
for (I=0; I<T; I++)
{
printf(”\n\tIngrese datos de la propiedad %d”, I + 1);
printf(”\nClave: ”);
fflush(stdin);
gets(A[I].clave);
printf(”Superficie cubierta: ”);
scanf(”%f”, &A[I].scu);
printf(”Superficie terreno: ”);
scanf(”%f”, &A[I].ste);
printf(”Características: ”);
fflush(stdin);
gets(A[I].car);
printf(”\tZona: ”);
fflush(stdin);
gets(A[I].ubi.zona);
printf(”\tCalle: ”);
fflush(stdin);
gets(A[I].ubi.calle);
printf(”\tColonia: ”);
fflush(stdin);
gets(A[I].ubi.colo);
printf(”Precio: ”);
scanf(”%f”, &A[I].precio);
printf(”Disponibilidad (Venta-V Renta-R): ”);
scanf(”%c”, &A[I].dispo);
}
}
319
8
320
Capítulo 8. Estructuras y uniones
void F1(propiedades A[], int T)
/* Esta función se utiliza para generar un listado de las propiedades
➥disponibles para venta en la zona de Miraflores, cuyo valor oscila entre
➥450,000 y 650,000 nuevos soles. */
{
int I;
printf(”\n\t\tListado de Propiedades para Venta en Miraflores”);
for (I=0; I<T; I++)
if ((A[I].dispo == ‘V’) && (strcmp (A[I].ubi.zona, ”Miraflores”) == 0))
if ((A[I].precio >= 450000) && (A[I].precio <= 650000))
{
printf(”\nClave de la propiedad: ”);
puts(A[I].clave);
printf(”\nSuperficie cubierta: %f”, A[I].scu);
printf(”\nSuperficie terreno: %f”, A[I].ste);
printf(”\nCaracterísticas: ”);
puts(A[I].car);
printf(”Calle: ”);
puts(A[I].ubi.calle);
printf(”Colonia: ”);
puts(A[I].ubi.colo);
printf(”Precio: %.2f\n”, A[I].precio);
}
}
void F2(propiedades A[], int T)
/* Al recibir como datos una zona geográfica de Lima, Perú, y un cierto rango
➥respecto al monto, esta función genera un listado de todas las propiedades
➥disponibles para renta. */
{
int I;
float li, ls;
char zon[20];
printf(”\n\t\tListado de Propiedades para Renta”);
printf(”\nIngrese zona geográfica: ”);
fflush(stdin);
gets(zon);
printf(”Ingrese el límite inferior del precio:”);
scanf(”%f”, &li);
printf(”Ingrese el límite superior del precio:”);
scanf(”%f”, &ls);
for (I=0; I<T; I++)
if ((A[I].dispo == ‘R’) && (strcmp (A[I].ubi.zona, zon) == 0))
if ((A[I].precio >= li) && (A[I].precio <= ls))
{
printf(”\nClave de la propiedad: ”);
puts(A[I].clave);
printf(”\nSuperficie cubierta: %d”, A[I].scu);
printf(”\nSuperficie terreno: %d”, A[I].ste);
Problemas resueltos
printf(”\nCaracterísticas: ”);
puts(A[I].car);
printf(”Calle: ”);
puts(A[I].ubi.calle);
printf(”Colonia: ”);
puts(A[I].ubi.colo);
printf(”Precio: %.2f”, A[I].precio);
}
}
Problema PR8.5
En una empresa de artículos domésticos almacenan la siguiente información de
cada uno de sus vendedores:
•
•
•
•
•
•
•
•
•
Número vendedor (entero).
Nombre y apellido (cadena de caracteres).
Ventas del año (arreglo unidimensional de reales).
Domicilio (estructura).
• Calle y número (cadena de caracteres).
• Colonia (cadena de caracteres).
• Código Postal (cadena de caracteres).
• Ciudad (cadena de caracteres).
Salario mensual (real).
Clave forma de pago (entero).
Forma de pago (unión).
• Banco (estructura).
Nombre del banco (cadena de caracteres).
Número de cuenta (cadena de caracteres).
• Ventanilla (caracter).
Dato:
(donde VENDEDORES es un arreglo unidimensional de tipo
estructura VENDEDOR, 1 ≤ N ≤ 100).
VENDEDORES[N]
Notas: Ventas del año es un arreglo unidimensional de 12 elementos de reales,
en el que se almacenan las ventas de los empleados en cada uno de los meses.
Forma de pago es una unión en la que se almacena la forma de pago al empleado: cuenta de cheques, nómina o ventanilla. En los dos primeros casos se utiliza
una estructura para almacenar el nombre del banco y el número de cuenta del
empleado.
321
8
322
Capítulo 8. Estructuras y uniones
Escribe un programa en C que realice lo siguiente:
a) Obtenga las ventas totales anuales de cada uno de los empleados.
b) Incremente 5% el salario a todos aquellos empleados cuyas ventas anuales
superaron $1,500,000.
c) Liste el número de empleado, el nombre y total de ventas, de todos aquellos
vendedores que en el año vendieron menos de $300,000.
d) Liste el número de empleado, el nombre del banco y el número de cuenta
de todos aquellos empleados a quienes se les deposita en cuenta de cheques.
En la siguiente figura se muestra la representación gráfica de la estructura de datos necesaria para resolver este problema.
VENDEDORES: Arreglo unidimensional de tipo estructura vendedor
Estructura vendedor
VENDEDORES
num nom ven domi sal cla pago num nom ven domi sal cla pago
Vendedor 1
...
Vendedor 2
num nom ven domi sal cla pago
...
Vendedor 100
pago
venta
che
noba nucu
Unión fpago
nomi
noba nucu
Estructura banco Estructura banco
domi
cnu col cp ciu
Estructura domicilio
FIGURA 8.8
Representación gráfica de la estructura de datos necesaria para resolver el problema PR8.5
Programa 8.10
#include <stdio.h>
#include <string.h>
/* Vendedores.
El programa maneja información sobre las ventas que realizan los vendedores de
➥artículos domésticos de una importante empresa de la Ciudad de México. */
typedef struct
/* Declaración de la estructura banco. */
{
Problemas resueltos
char noba[10];
char nucu[10];
} banco;
typedef union
{
banco che;
banco nomi;
char venta;
} fpago;
typedef struct
{
char cnu[20];
char col[20];
char cp[5];
char ciu[15];
} domicilio;
typedef struct
{
int num;
char nom[20];
float ven[12];
domicilio domi;
float sal;
fpago pago;
int cla;
} vendedor;
void
void
void
void
void
/* Nombre del banco. */
/* Número de cuenta. */
/* Declaración de la union fpago. */
/* Cheque. Campo de tipo estructura banco. */
/* Cómina. Campo de tipo estructura banco. */
/* Ventanilla. */
/* Declaración de la estructura domicilio. */
/*
/*
/*
/*
Calle y número. */
Colonia. */
Código Postal. */
Ciudad. */
/* Declaración de la estructura vendedor. */
/*
/*
/*
/*
Número de vendedor. */
Nombre del vendedor. */
Ventas del año. Arreglo unidimensional de tipo real. */
domi es de tipo estructura domicilio. */
/* Salario mensual. */
/* pago es de tipo unión fpago. */
/* Clave forma de pago. */
Lectura(vendedor *, int);
F1(vendedor *, int);
F2(vendedor *, int);
F3(vendedor *, int);
F4(vendedor *, int);
/* Prototipos de funciones. */
void main(void)
{
vendedor VENDEDORES[100];
/* Declaración del arreglo unidimensional de tipo estructura vendedor. */
int TAM;
do
{
printf(”Ingrese el número de vendedores: ”);
scanf(”%d”, &TAM);
}
while (TAM > 100 | | TAM < 1);
/* Se verifica que el número de elementos del arreglo sea correcto. */
Lectura (VENDEDORES, TAM);
F1 (VENDEDORES, TAM);
F2 (VENDEDORES, TAM);
323
8
324
Capítulo 8. Estructuras y uniones
F3 (VENDEDORES, TAM);
F4 (VENDEDORES, TAM);
printf(”\n\tFIN DEL PROGRAMA”);
}
void Lectura(vendedor A[], int T)
/* Esta función se utiliza para leer un arreglo unidimensional de tipo
➥estructura vendedor de T elementos. */
{
int I, J;
for (I=0; I<T; I++)
{
printf(”\n\tIngrese datos del vendedor %d”, I+1);
printf(”\nNúmero de vendedor: ”);
scanf(”%d”, &A[I].num);
printf(”Nombre del vendedor: ”);
fflush(stdin);
gets(A[I].nom);
printf(”Ventas del año: \n”);
for (J=0; J<12; J++)
{
printf(”\tMes %d: ”, J+1);
scanf(”%f”, &A[I].ven[J]);
}
printf(”Domicilio del vendedor: \n”);
printf(”\tCalle y número: ”);
fflush(stdin);
gets(A[I].domi.cnu);
printf(”\tColonia: ”);
fflush(stdin);
gets(A[I].domi.col);
printf(”\tCódigo Postal: ”);
fflush(stdin);
gets(A[I].domi.cp);
printf(”\tCiudad: ”);
fflush(stdin);
gets(A[I].domi.ciu);
printf(”Salario del vendedor: ”);
scanf(”%f”, &A[I].sal);
printf(”Forma de Pago (Banco-1 Nómina-2 Ventanilla-3): ”);
scanf(”%d”, &A[I].cla);
switch (A[I].cla)
{
case 1:{
printf(”\tNombre del banco: ”);
fflush(stdin);
gets(A[I].pago.che.noba);
printf(”\tNúmero de cuenta: ”);
fflush(stdin);
gets(A[I].pago.che.nucu);
}
Problemas resueltos
break;
case 2:{
printf(”\tNombre del banco: ”);
fflush(stdin);
gets(A[I].pago.nomi.noba);
printf(”\tNúmero de cuenta: ”);
fflush(stdin);
gets(A[I].pago.nomi.nucu);
}
break;
case 3: A[I].pago.venta = ‘S’;
break;
}
}
}
void F1(vendedor A[], int T)
/* Esta función se utiliza para generar las ventas totales anuales de cada uno
➥de los vendedores de la empresa. */
{
int I, J;
float SUM;
printf(”\n\t\tVentas Totales de los Vendedores”);
for (I=0; I<T; I++)
{
printf(”\nVendedor: %d”, A[I].num);
SUM = 0.0;
for (J=0; J<12; J++)
SUM += A[I].ven[J];
printf(”\nVentas: %.2f\n”, SUM);
}
}
void F2(vendedor A[], int T)
/* Esta función se utiliza para incrementar 5% el salario de todos aquellos
➥vendedores cuyas ventas anuales superaron $1,500,000. */
{
int I, J;
float SUM;
printf (”\n\t\tIncremento a los Vendedores con Ventas > 1,500,000$”);
for (I=0; I<T; I++)
{
SUM = 0.0;
for (J=0; J<12; J++)
SUM += A[I].ven[J];
if (SUM > 1500000.00)
{
A[I].sal = A[I].sal * 1.05;
printf(”\nNúmero de empleado: %d\nVentas: %.2f\nNuevo salario: %.2f”,
A[I].num, SUM, A[I].sal);
}
325
8
326
Capítulo 8. Estructuras y uniones
}
}
void F3(vendedor A[], int T)
/* Esta función se utiliza para generar un listado de todos aquellos
➥vendedores que en el año vendieron menos de $300,000. */
{
int I, J;
float SUM;
printf(”\n\t\tVendedores con Ventas < 300,000”);
for (I=0; I<T; I++)
{
SUM = 0.0;
for (J=0; J<12; J++)
SUM += A[I].ven[J];
if (SUM < 300000.00)
printf(”\nNúmero de empleado: %d\nNombre: %s\nVentas: %.2f”, A[I].num,
A[I].nom, SUM);
}
}
void F4(vendedor A[], int T)
/* Esta función se usa para imprimir el número de empleado, el nombre del
➥banco y el número de cuenta de todos aquellos empleados a quienes se les
➥deposita su sueldo en cuenta de cheques. */
{
int I;
float SUM;
printf(”\n\t\tVendedores con Cuenta en el Banco”);
for (I=0; I<T; I++)
if (A[I].cla == 1)
printf(”\nNúmero de vendedor: %d\n Banco: %s\nCuenta: %s”,
➥A[I].num,
A[I].pago.che.noba, A[I].pago.che.nucu);
}
Problemas suplementarios
Problema PS8.1
Un importante banco, cuya casa central se encuentra ubicada en Quito, Ecuador,
lleva la información de sus clientes en un arreglo unidimensional. Éste se encuentra
ordenado en función del número de cuenta. El banco almacena la siguiente información de cada cliente:
Problemas suplementos
• Número de cuenta (entero extendido).
• Nombre del cliente (cadena de caracteres).
• Domicilio (estructura).
• Calle y número (cadena de caracteres).
• Código Postal (cadena de caracteres).
• Colonia (cadena de caracteres).
• Ciudad (cadena de caracteres).
• Teléfono (cadena de caracteres).
• Saldo (real).
Dato:
(donde CLI es un arreglo unidimensional de tipo estructura CLIENTE de
elementos, 1 ≤ N ≤ 100).
CLI[N]
N
Escribe un programa en C que realice las siguientes operaciones:
a) Depósitos. Al recibir el número de cuenta de un cliente y un monto
determinado, debe actualizar el saldo.
b) Retiros. Al recibir el número de cuenta de un cliente y un monto determinado
por medio de un cheque o un retiro de un cajero, el programa debe actualizar el saldo. El cajero no puede pagar el cheque o autorizar el retiro si el
saldo es insuficiente.
Nota: El programa debe realizar y validar diferentes transacciones. El fin de datos
se expresa al ingresar el número 0.
Problema PS8.2
La Federación Mexicana de Fútbol (FEMEXFUT) almacena en un arreglo unidimensional la información de la tabla de posiciones de sus torneos apertura y
clausura. Las estadísticas se ordenan lógicamente en función de los puntos. Se
almacena la siguiente información de cada equipo:
•
•
•
•
•
•
•
•
•
Nombre del equipo (cadena de caracteres).
Partidos jugados (entero).
Partidos ganados (entero).
Partidos empatados (entero).
Partidos perdidos (entero).
Goles a favor (entero).
Goles en contra (entero).
Diferencia de goles (entero).
Puntos (entero).
327
8
328
Capítulo 8. Estructuras y uniones
Dato:
FUTBOL[20]
(donde
FUTBOL
es un arreglo unidimensional de tipo estructura
EQUIPO).
Escribe un programa en C que actualice la información después de cada fecha. El
programa recibe la información de la siguiente manera:
América 0 – Puebla 2
Cruz Azul 3 – Veracruz 2
Necaxa 2 – Monterrey 3
. . .
Después de actualizar la información, el programa debe escribir la nueva tabla de
posiciones, ordenada en función de los puntos de cada equipo.
Problema PS8.3
En una universidad de Barranquilla, en Colombia, almacenan la información de
sus profesores utilizando arreglos unidimensionales. La siguiente información
de cada profesor se almacena en una estructura:
•
•
•
•
•
•
•
Número de empleado (entero).
Nombre y apellido (cadena de caracteres).
Departamento al que pertenece (cadena de caracteres).
Puesto que ocupa (cadena de caracteres).
Grado académico (cadena de caracteres).
Nacionalidad (cadena de caracteres).
Salario (arreglo unidimensional de reales).
Dato:
(donde EMPLE es un arreglo unidimensional de tipo estructura
PROFESOR, 1 ≤ N ≤ 200).
EMPLE[N]
Nota: Salario es un arreglo unidimensional de tipo real de 12 posiciones que
almacena los ingresos mensuales de los profesores. Considera además que en la
universidad existen cuatro departamentos: Economía, Derecho, Computación y
Administración.
Escribe un programa en C que obtenga lo siguiente:
a) El nombre, departamento al que pertenece y nacionalidad del profesor
que más ganó el año anterior. También debe escribir el ingreso total del
profesor.
Problemas suplementos
b) El monto total pagado a los profesores extranjeros (nacionalidad diferente a
Colombia) y el porcentaje respecto al monto total erogado por la universidad.
c) El departamento que más egresos —pago de salarios— tuvo el año anterior.
Problema PS8.4
En una empresa ubicada en Santiago de Chile almacenan la siguiente información
de cada uno de sus empleados:
•
•
•
•
Número de empleado (entero).
Nombre y apellido (cadena de caracteres).
Departamento (cadena de caracteres).
Domicilio (estructura).
• Calle y número (cadena de caracteres).
• Colonia (cadena de caracteres).
• Código Postal (cadena de caracteres).
• Ciudad (cadena de caracteres).
• Teléfono (cadena de caracteres).
• Salario mensual (real).
Dato:
(donde EMPLE es un arreglo unidimensional, ordenado en función
del número de empleado, de tipo estructura EMPLEADO, 1 ≤ N ≤ 100).
EMPLE[N]
Escribe un programa en C que contemple los siguientes módulos:
a) Altas. Al recibir el número de un empleado, debe darlo de alta incorporando
lógicamente todos los datos del empleado.
b) Bajas. Al recibir el número de un empleado, debe darlo de baja.
c) Listado. Al recibir el nombre de un departamento, debe escribir el número
de cada uno de sus empleados, sus nombres y salarios correspondientes.
Problema PS8.5
Una tienda especializada en artículos electrónicos vende como máximo 100 productos diferentes. La información de cada producto se almacena en una estructura:
• Clave del producto (entero).
• Nombre del producto (cadena de caracteres).
• Existencia (entero).
329
8
330
Capítulo 8. Estructuras y uniones
Dato: TIENDA[N] (donde TIENDA es un arreglo unidimensional de tipo estructura
Producto de N elementos, 1 ≤ N ≤ 100).
Escribe un programa que actualice la información de acuerdo con las siguientes
transacciones:
OPE1 CLA1 CAN1
OPE2 CLA2 CAN2
. . .
’0’
0
0
Donde:
es una variable de tipo caracter que representa el tipo de operación que
se realiza: ‘C’ compras, ‘V’ ventas.
CLAi es una variable de tipo entero que representa la clave del producto.
CANi es una variable de tipo entero que significa la cantidad del producto.
OPEi
Problema PS8.6
En una escuela privada de la Ciudad de México almacenan la información de cada
uno de sus alumnos en un arreglo unidimensional de tipo estructura. Se almacena la siguiente información de cada alumno:
• Matrícula del alumno (entero).
• Nombre y apellido (cadena de caracteres).
• Domicilio (estructura).
• Calle y número (cadena de caracteres).
• Código Postal (entero).
• Colonia (cadena de caracteres).
• Ciudad (cadena de caracteres).
• Teléfono (cadena de caracteres).
• Nivel de Estudios (estructura).
• Nivel (cadena de caracteres).
• Grado (entero).
• Salón (cadena de caracteres).
• Calificaciones (arreglo unidimensional de estructuras).
• Materia (cadena de caracteres).
• Promedio (real).
Dato:
ESCUELA[N]
Alumno, 1
(donde ESCUELA es un arreglo unidimensional de tipo estructura
≤ N ≤ 1000).
Problemas suplementos
331
Nota: Cada alumno tiene siete materias en sus cursos.
Escribe un programa en C que realice lo siguiente:
a) Al recibir como dato la matrícula de un alumno, calcule e imprima el promedio general del mismo.
b) Al recibir como datos el nivel de estudios (primaria, secundaria o preparatoria), el grado y el salón, liste la matrícula de todos los alumnos, el nombre
y su promedio.
c) Al recibir como datos el nivel de estudios (primaria, secundaria o preparatoria), el grado y el salón, obtenga el alumno que tiene el mayor promedio.
Debe escribir la matrícula, su nombre y el promedio correspondiente.
8
9. Punteros y gestión dinámica de memoria
9.1. ¿Por qué usar estructuras dinámicas?
Hasta ahora teníamos una serie de variables que declaramos al principio del programa o de
cada función. Estas variables, que reciben el nombre de ESTÁTICAS, tienen un tamaño
asignado desde el momento en que se crea el programa.
Este tipo de variables son sencillas de usar y rápidas... si sólo vamos a manejar estructuras de
datos que no cambien, pero resultan poco eficientes si tenemos estructuras cuyo tamaño no
sea siempre el mismo.
Es el caso de una agenda: tenemos una serie de fichas, e iremos añadiendo más. Si reservamos
espacio para 10, no podremos llegar a añadir la número 11, estamos limitando el máximo. Una
solución sería la de trabajar siempre en el disco: no tenemos límite en cuanto a número de
fichas, pero es muchísimo más lento.
Lo ideal sería aprovechar mejor la memoria que tenemos en el ordenador, para guardar en ella
todas las fichas o al menos todas aquellas que quepan en memoria.
Una solución “típica” (pero mala) es sobredimensionar: preparar una agenda contando con
1000 fichas, aunque supongamos que no vamos a pasar de 200. Esto tiene varios
inconvenientes: se desperdicia memoria, obliga a conocer bien los datos con los que vamos a
trabajar, sigue pudiendo verse sobrepasado, etc.
La solución suele ser crear estructuras DINÁMICAS, que puedan ir creciendo o disminuyendo
según nos interesen. Ejemplos de este tipo de estructuras son:
Las pilas. Como una pila de libros: vamos apilando cosas en la cima, o cogiendo de la
cima.
Las colas. Como las del cine (en teoría): la gente llega por un sitio (la cola) y sale por
el opuesto (la cabeza).
Las listas, en las que se puede añadir elementos, consultarlos o borrarlos en cualquier
posición.
Y la cosa se va complicando: en los árboles cada elemento puede tener varios sucesores, etc.
Todas estas estructuras tienen en común que, si se programan bien, pueden ir creciendo o
decreciendo según haga falta, al contrario que un array, que tiene su tamaño prefijado.
En todas ellas, lo que vamos haciendo es reservar un poco de memoria para cada nuevo
elemento que nos haga falta, y enlazarlo a los que ya teníamos. Cuando queramos borrar un
elemento, enlazamos el anterior a él con el posterior a él (para que no “se rompa” nuestra
estructura) y liberamos la memoria que estaba ocupando.
Revisión 0.90– Página 127
9.2. ¿Qué son los punteros?
Un puntero no es más que una dirección de memoria. Lo que tiene de especial es que
normalmente un puntero tendrá un tipo de datos asociado: por ejemplo, un “puntero a entero”
será una dirección de memoria en la que habrá almacenado (o podremos almacenar) un
número entero.
Vamos a ver qué símbolo usamos en C para designar los punteros:
int num;
int *pos;
/* "num" es un número entero */
/* "pos" es un "puntero a entero" (dirección de
memoria en la que podremos guardar un entero) */
Es decir, pondremos un asterisco entre el tipo de datos y el nombre de la variable. Ese asterisco
puede ir junto a cualquiera de ambos, también es correcto escribir
int* pos;
Esta nomenclatura ya la habíamos utilizado aun sin saber que era eso de los punteros. Por
ejemplo, cuando queremos acceder a un fichero, hacemos
FILE* fichero;
Antes de entrar en más detalles, y para ver la diferencia entre trabajar con “arrays=” o con
punteros, vamos a hacer dos programas que pidan varios números enteros al usuario y
muestren su suma. El primero empleará un “array” (una tabla, de tamaño predefinido) y el
segundo empleará memoria que reservaremos durante el funcionamiento del programa.
El primero podría ser así:
/*---------------------------*/
/* Ejemplo en C nº 71:
*/
/* c071.c
*/
/*
*/
/* Sumar varios datos
*/
/* Version 1: con arrays
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
main() {
int datos[100];
int cuantos;
int i;
long suma=0;
/*
/*
/*
/*
Preparamos espacio para 100 numeros */
Preguntaremos cuantos desea introducir */
Para bucles */
La suma, claro */
do {
printf("Cuantos numeros desea sumar? ");
scanf("%d", &cuantos);
if (cuantos>100) /* Solo puede ser 100 o menos */
printf("Demasiados. Solo se puede hasta 100.");
} while (cuantos>100); /* Si pide demasiado, no le dejamos */
Revisión 0.90– Página 128
/* Pedimos y almacenamos los datos */
for (i=0; i<cuantos; i++) {
printf("Introduzca el dato número %d: ", i+1);
scanf("%d", &datos[i]);
}
/* Calculamos la suma */
for (i=0; i<cuantos; i++)
suma += datos[i];
printf("Su suma es: %ld\n", suma);
}
Los más avispados se pueden dar cuenta de que si sólo quiero calcular la suma, lo podría hacer
a medida que leo cada dato, no necesitaría almacenar todos. Vamos a suponer que sí
necesitamos guardarlos (en muchos casos será verdad, si los cálculos son más complicados).
Entonces nos damos cuenta de que lo que hemos estado haciendo hasta ahora no es
eficiente:
•
•
•
Si quiero sumar 1000 datos, o 500, o 101, no puedo. Nuestro límite previsto era de
100, así que no podemos trabajar con más datos.
Si sólo quiero sumar 3 números, desperdicio el espacio de 97 datos que no uso.
Y el problema sigue: si en vez de 100 números, reservamos espacio para 5000, es más
difícil que nos quedemos cortos pero desperdiciamos muchísima más memoria.
La solución es reservar espacio estrictamente para lo que necesitemos, y eso es algo que
podríamos hacer así:
/*---------------------------*/
/* Ejemplo en C nº 72:
*/
/* c072.c
*/
/*
*/
/* Sumar varios datos
*/
/* Version 2: con punteros */
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
#include <stdlib.h>
main() {
int* datos;
/* Necesitaremos espacio para varios numeros */
int cuantos;
/* Preguntaremos cuantos desea introducir */
int i;
/* Para bucles */
long suma=0;
/* La suma, claro */
do {
printf("Cuantos numeros desea sumar? ");
scanf("%d", &cuantos);
datos = (int *) malloc (cuantos * sizeof(int));
if (datos == NULL) /* Si no hay espacio, avisamos */
printf("No caben tantos datos en memoria.");
} while (datos == NULL); /* Si pide demasiado, no le dejamos */
/* Pedimos y almacenamos los datos */
for (i=0; i<cuantos; i++) {
printf("Introduzca el dato número %d: ", i+1);
Revisión 0.90– Página 129
scanf("%d", datos+i);
}
/* Calculamos la suma */
for (i=0; i<cuantos; i++)
suma += *(datos+i);
printf("Su suma es: %ld\n", suma);
free(datos);
}
Este fuente es más difícil de leer, pero a cambio es mucho más eficiente: funciona
perfectamente si sólo queremos sumar 5 números, pero también si necesitamos sumar 120.000
(y si caben tantos números en la memoria disponible de nuestro equipo, claro).
Vamos a ver las diferencias:
En primer lugar, lo que antes era
int datos[100] que quiere decir “a partir de la posición de
memoria que llamaré datos, querré espacio para a guardar 100 números enteros=”,se ha
convertido en int* datos que quiere decir “a partir de la posición de memoria que llamaré
datos voy a guardar varios números enteros (pero aún no sé cuantos)”.
Luego reservamos el espacio exacto que necesitamos, haciendo datos = (int *) malloc (cuantos
* sizeof(int));
•
•
•
Esta orden suena complicada, así que vamos a verla por partes:
“malloc” es la orden que usaremos para reservar memoria cuando la necesitemos (es la
abreviatura de las palabra “memory” y “allocate”).
Como parámetro, le indicamos cuanto espacio queremos reservar. Para 100 números
enteros, sería “100*sizeof(int)”, es decir, 100 veces el tamaño de un entero. En nuestro
caso, no son 100 números, sino el valor de la variable “cuantos”. Por eso hacemos
“malloc (cuantos*sizeof(int))”.
Para terminar, ese es el espacio que queremos reservar para nuestra variable “datos=”.
Y esa variable es de tipo “int *” (un puntero a datos que serán números enteros). Para
que todo vaya bien, debemos “convertir” el resultado de “malloc” al tipo de datos
correcto, y lo hacemos forzando una conversión como vimos en el apartado 2.4
(operador “molde”), con lo que nuestra orden está completa:
datos = (int *) malloc (cuantos * sizeof(int));
•
•
Si “malloc” nos devuelve NULL como resultado (un “puntero nulo”), quiere decir que no
ha encontrado ninguna posición de memoria en la que nos pudiera reservar todo el
espacio que le habíamos solicitado.
Para usar “malloc” deberemos incluir “stdlib.h” al principio de nuestro fuente.
La forma de guardar los datos que teclea el usuario también es distinta. Cuando trabajábamos
con un “array”, hacíamos scanf("%d", &datos[i]) (“el dato número i”), pero con punteros
usaremos scanf("%d", datos+i) (en la posición datos + i). Ahora ya no necesitamos el símbolo
“ampersand” (&). Este símbolo se usa para indicarle a C en qué posición de memoria debe
almacenar un dato. Por ejemplo, float x; es una variable que podremos usar para guardar un
número real. Si lo hacemos con la orden “scanf”, esta orden no espera que le digamos en qué
variable deber guardar el dato, sino en qué posición de memoria. Por eso hacemos scanf("%f",
Revisión 0.90– Página 130
&x);
En el caso que nos encontramos ahora, int* datos
ya se refiere a una posición de
memoria (un puntero), por lo que no necesitamos & para usar “scanf”.
Finalmente, la forma de acceder a los datos también cambia. Antes leíamos el primer dato
como datos[0], el segundo como datos[1], el tercero como datos[2] y así sucesivamente. Ahora
usaremos el asterisco (*) para indicar que queremos saber el valor que hay almacenado en una
cierta posición: el primer dato será *datos, el segundo *(datos+1), el tercero será *(datos+2) y
así en adelante. Por eso, donde antes hacíamos suma += datos[i]; ahora usamos suma +=
*(datos+i);
También aparece otra orden nueva: free. Hasta ahora, teníamos la memoria reservada
estáticamente, lo que supone que la usábamos (o la desperdiciábamos) durante todo el tiempo
que nuestro programa estuviera funcionando. Pero ahora, igual que reservamos memoria justo
en el momento en que la necesitamos, y justo en la cantidad que necesitamos, también
podemos volver a dejar disponible esa memoria cuando hayamos terminado de usarla. De eso
se encarga la orden “free”, a la que le debemos indicar qué puntero es el que queremos liberar.
9.3. Repasemos con un ejemplo sencillo
El ejemplo anterior era “un caso real”. Generalmente, los casos reales son más aplicables que
los ejemplos puramente académicos, pero también más difíciles de seguir. Por eso, antes de
seguir vamos a ver un ejemplo más sencillo que nos ayude a asentar los conceptos:
Reservaremos espacio para un número real de forma estática, y para dos números reales de
forma dinámica, daremos valor a dos de ellos, guardaremos su suma en el tercer número y
mostraremos en pantalla los resultados.
/*---------------------------*/
/* Ejemplo en C nº 73:
*/
/* c073.c
*/
/*
*/
/* Manejo básico de
*/
/* punteros
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
#include <stdlib.h>
main() {
float n1;
float *n2, *suma;
/* Primer número, estático */
/* Los otros dos números */
n1 = 5.0;
/* Damos un valor prefijado a n1 (real) */
n2 = (float *) malloc (sizeof(float)); /* Reservamos espacio para n2 */
*n2 = 6.7;
/* Valor prefijado para n2 (puntero a real) */
suma = (float *) malloc (sizeof(float)); /* Reservamos espacio para suma */
*suma = n1 + *n2;
/* Calculamos la suma */
printf("El valor prefijado para la suma era %4.2f\n",
*suma);
Revisión 0.90– Página 131
printf("Ahora es tu turno: Introduce el primer número ");
scanf("%f",&n1);
/* Leemos valor para n1 (real) */
printf("Introduce el segundo número ");
scanf("%f",n2);
/* Valor para n2 (puntero a real) */
*suma = n1 + *n2; /* Calculamos nuevamente la suma */
printf("Ahora la suma es %4.2f\n", *suma);
free(n2);
free(suma);
/* Liberamos la memoria reservada */
}
Las diferencias son:
•
•
n1 es un “float”, así que le damos valor normalmente: n1 = 0; Y pedimos su valor con
scanf usando & para indicar en qué dirección de memoria se encuentra: scanf("%f",
&n1);
n2 (y también “suma”) es un “puntero a float”, así que debemos reservarle espacio con
“malloc” antes de empezar a usarlo, y liberar con “free” el epacio que ocupaba cuando
terminemos de utilizarlo. Para guardar un valor en la dirección de memoria “a la que
apunta”, usamos un asterisco: *n2 = 0; Y pedimos su valor con scanf, pero sin
necesidad de usar &, porque el puntero ES una dirección de memoria: scanf("%f", n2);
(En este ejemplo, no hemos comprobado si el resultado de “malloc” era NULL, porque sólo
pedíamos espacio para dos variables, y hemos dado por sentado que sí habría memoria
disponible suficiente para almacenarlas; en un caso general, deberemos asegurarnos siempre
de que se nos ha concedido ese espacio que hemos pedido).
9.4. Aritmética de punteros
Si declaramos una variable como int n=5 y posteriormente hacemos n++, debería resultar claro
que lo que ocurre es que aumenta en una unidad el valor de la variable n, pasando a ser 6.
Pero ¿qué sucede si hacemos esa misma operación sobre un puntero?
int *n;
n = (int *) malloc (sizeof(int));
*n = 3;
n++;
Después de estas líneas de programa, lo que ha ocurrido no es que el contenido de la posición
n sea 4. Eso lo conseguiríamos modificando “*n”, de la misma forma que le hemos dado su
valor inicial. Es decir, deberíamos usar
(*n) ++;
En cambio, nosotros hemos aumentado el valor de “n”. Como “n” es un puntero, estamos
modificando una dirección de memoria. Por ejemplo, si “n” se refería a la posición de memoria
Revisión 0.90– Página 132
número 10.000 de nuestro ordenador, ahora ya no es así, ahora es otra posición de memoria
distinta, por ejemplo la 10.001.
¿Y por qué “por ejemplo”? Porque, como ya sabemos, el espacio que ocupa una variable en C
depende del sistema operativo. Así, en un sistema operativo de 32 bits, un “int” ocuparía 4
bytes, de modo que la operación
n++;
haría que pasáramos de mirar la posición 10.000 a la 10.004. Generalmente no es esto lo que
querremos, sino modificar el valor que había almacenado en esa posición de memoria. Olvidar
ese * que indica que queremos cambiar el dato y no la posición de memoria puede dar lugar a
fallos muy difíciles de descubrir (o incluso a que el programa se interrumpa con un aviso de
“Violación de segmento” porque estemos accediendo a zonas de memoria que no hemos
reservado).
9.5. Punteros y funciones: parámetros por referencia
Hasta ahora no sabíamos cómo modificar los parámetros que pasábamos a una función.
Recordemos el ejemplo 64:
/*---------------------------*/
/* Ejemplo en C nº 64:
*/
/* c064.c
*/
/*
*/
/* Dos variables locales
*/
/* con el mismo nombre
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
void duplica(int n) {
n = n * 2;
}
main() {
int n = 5;
printf("n vale %d\n", n);
duplica(n);
printf("Ahora n vale %d\n", n);
}
Cuando poníamos este programa en marcha, el valor de n que se mostraba era un 5, porque
los cambios que hiciéramos dentro de la función se perdían al salir de ella. Esta forma de
trabajar (la única que conocíamos hasta ahora) es lo que se llama “pasar parámetros por
valor”.
Pero existe una alternativa. Es lo que llamaremos “pasar parámetros por referencia”.
Consiste en que el parámetro que nosotros pasamos a la función no es realmente la variable,
sino la dirección de memoria en que se encuentra dicha variable (usando &). Dentro de la
Revisión 0.90– Página 133
función, modificaremos la información que se encuentra dentro de esa dirección de memoria
(usando *), así:
/*---------------------------*/
/* Ejemplo en C nº 74:
*/
/* c074.c
*/
/*
*/
/* Modificar el valor de
*/
/* un parámetro
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
void duplica(int *x) {
*x = *x * 2;
}
main() {
int n = 5;
printf("n vale %d\n", n);
duplica(&n);
printf("Ahora n vale %d\n", n);
}
Esto permite que podamos obtener más de un valor a partir de una función. Por ejemplo,
podemos crear una función que intercambie los valores de dos variables enteras así:
/*---------------------------*/
/* Ejemplo en C nº 75:
*/
/* c075.c
*/
/*
*/
/* Intercambiar el valor de */
/* dos parámetros
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
void intercambia(int *x, int *y) {
int auxiliar;
auxiliar = *x;
*x = *y;
*y = auxiliar ;
}
main() {
int a = 5;
int b = 12;
intercambia(&a, &b);
printf("Ahora a es %d y b es %d\n", a, b);
}
Este programa escribirá en pantalla que a vale 12 y que b vale 5. Dentro de la función
“intercambia”, nos ayudamos de una variable auxiliar para memorizar el valor de x antes de
cambiarlo por el valor de y.
Revisión 0.90– Página 134
Ejercicio propuesto: Crear una función que calcule las dos soluciones de una ecuación de
segundo grado (Ax2 + Bx + C = 0) y devuelva las dos soluciones como parámetros.
9.6. Punteros y arrays
En C hay muy poca diferencia “interna” entre un puntero y un array. En muchas ocasiones,
podremos declarar un dato como array (una tabla con varios elementos iguales, de tamaño
predefinido) y recorrerlo usando punteros. Vamos a ver un ejemplo:
/*---------------------------*/
/* Ejemplo en C nº 76:
*/
/* c076.c
*/
/*
*/
/* Arrays y punteros (1)
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
main() {
int datos[10];
int i;
/* Damos valores normalmente */
for (i=0; i<10; i++)
datos[i] = i*2;
/* Pero los recorremos usando punteros */
for (i=0; i<10; i++)
printf ("%d ", *(datos+i));
}
Pero también podremos hacer lo contrario: declarar de forma dinámica una variable usando
“malloc” y recorrerla como si fuera un array:
/*---------------------------*/
/* Ejemplo en C nº 77:
*/
/* c077.c
*/
/*
*/
/* Arrays y punteros (2)
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
#include <stdlib.h>
main() {
int *datos;
int i;
/* Reservamos espacio */
datos = (int *) malloc (20*sizeof(int));
/* Damos valores como puntero */
Revisión 0.90– Página 135
printf("Uso como puntero... ");
for (i=0; i<20; i++)
*(datos+i) = i*2;
/* Y los mostramos */
for (i=0; i<10; i++)
printf ("%d ", *(datos+i));
/* Ahora damos valores como array */
printf("\nUso como array... ");
for (i=0; i<20; i++)
datos[i] = i*3;
/* Y los mostramos */
for (i=0; i<10; i++)
printf ("%d ", datos[i]);
/* Liberamos el espacio */
free(datos);
}
9.7. Arrays de punteros
Igual que creamos “arrays” para guardar varios datos que sean números enteros o reales,
podemos hacerlo con punteros: podemos reservar espacio para “20 punteros a enteros=”
haciendo
int *datos[20];
Tampoco es algo especialmente frecuente en un caso general, porque si fijamos la cantidad de
datos, estamos perdiendo parte de la versatilidad que podríamos tener al usar memoria
dinámica. Pero sí es habitual cuando se declaran varias cadenas:
char *mensajesError[3]={"Fichero no encontrado", "No se puede escribir",
"Fichero sin datos"};
Un ejemplo de su uso sería este:
/*---------------------------*/
/* Ejemplo en C nº 78:
*/
/* c078.c
*/
/*
*/
/* Arrays de punteros
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
main() {
char *mensajesError[3]={"Fichero no encontrado",
"No se puede escribir",
"Fichero sin datos"};
printf("El primer mensaje de error es: %s\n",
mensajesError[0]);
printf("El segundo mensaje de error es: %s\n",
mensajesError[1]);
printf("El tercer mensaje de error es: %s\n",
mensajesError[2]);
}
Revisión 0.90– Página 136
9.8. Punteros y estructuras
Igual que creamos punteros a cualquier tipo de datos básico, le reservamos memoria con
“malloc” cuando necesitamos usarlo y lo liberamos con “free” cuando terminamos de utilizarlo,
lo mismo podemos hacer si se trata de un tipo de datos no tan sencillo, como un “struct”.
Eso sí, la forma de acceder a los datos en un struct cambiará ligeramente. Para un dato que sea
un número entero, ya sabemos que lo declararíamos con int *n y cambiaríamos su valor
haciendo algo como *n=2, de modo que para un struct podríamos esperar que se hiciera algo
como *persona.edad = 20. Pero esa no es la sintaxis correcta: deberemos utilizar el nombre de la
variable y el del campo, con una flecha (->) entre medias, así: persona->edad = 20. Vamos a
verlo con un ejemplo:
/*---------------------------*/
/* Ejemplo en C nº 79:
*/
/* c079.c
*/
/*
*/
/* Punteros y structs
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
main() {
/* Primero definimos nuestro tipo de datos */
struct datosPersona {
char nombre[30];
char email[25];
int edad;
};
/* La primera persona será estática */
struct datosPersona persona1;
/* La segunda será dinámica */
struct datosPersona *persona2;
/* Damos valores a la persona estática */
strcpy(persona1.nombre, "Juan");
strcpy(persona1.email, "j@j.j");
persona1.edad = 20;
/* Ahora a la dinámica */
persona2 = (struct datosPersona*)
malloc (sizeof(struct datosPersona));
strcpy(persona2->nombre, "Pedro");
strcpy(persona2->email, "p@p.p");
persona2->edad = 21;
/* Mostramos los datos y liberamos la memoria */
printf("Primera persona: %s, %s, con edad %d\n",
persona1.nombre, persona1.email, persona1.edad);
printf("Segunda persona: %s, %s, con edad %d\n",
persona2->nombre, persona2->email, persona2->edad);
free(persona2);
}
Revisión 0.90– Página 137
Ejercicio propuesto: Mejorar la versión de la agenda que leía todos los datos al principio de
la ejecución y guardaba todos los datos cuando terminábamos su uso (apartado 6.4). Esta
nueva versión deberá estar preparada para manejar hasta 1000 fichas, pero sólo reservará
espacio para las que realmente sean necesarias.
9.9. Opciones de la línea de comandos: parámetros de
“main”
Es muy frecuente que un programa que usamos desde la “línea de comandos” tenga ciertas
opciones que le indicamos como argumentos. Por ejemplo, bajo Linux o cualquier otro sistema
operativo de la familia Unix, podemos ver la lista detallada de ficheros que terminan en .c
haciendo
ls –l *.c
En este caso, la orden sería “ls=”, y las dos opciones (a
rgumentos o parámetros) que le
indicamos son “-l” y “*.c”.
Pues bien, estas opciones que se le pasan al programa se pueden leer desde C. La forma de
hacerlo es con dos parámetros. El primero (que por convenio se suele llamar “argc”) será un
número entero que indica cuantos argumentos se han tecleado. El segundo (que se suele
llamar “argv”) es una tabla de cadenas de texto, que contiene cada uno de esos argumentos.
Por ejemplo, si bajo Windows o MsDos tecleamos la orden “DIR *.EXE P”, tendríamos que:
•
•
•
•
argc es la cantidad de parámetros, incluyendo el nombre del propio programa (3, en
este ejemplo).
argv[0] es el nombre del programa (DIR, en este caso).
argv[1] es el primer argumento (*.EXE).
argv[2] es el segundo argumento (/P).
Un fuente en C de ejemplo, que mostrara todos los parámetros que se han tecleado sería:
/*---------------------------*/
/* Ejemplo en C nº 80:
*/
/* c080.c
*/
/*
*/
/* Argumentos de "main"
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
int main (int argc, char *argv[])
{
int i;
printf ("Nombre del programa:\"%s\".\n",argv[0]);
if (argc > 0)
for (i = 1; i<argc; i++)
Revisión 0.90– Página 138
printf("Parámetro %d = %s\n", i, argv[i]);
else
printf("No se han indicado parámetros.\n");
return 0;
}
Ejercicios propuestos:
• Crear un programa llamado “suma”, que calcule (y muestre) la suma de dos números
que se le indiquen como parámetro. Por ejemplo, si se teclea “suma 2 3” deberá
responder “5”, y si se teclea “suma 2” deberá responder “no hay suficientes datos.
• Crear una calculadora básica, llamada “calcula”, que deberá sumar, restar, multiplicar o
dividir los dos números que se le indiquen como parámetros. Ejemplos de su uso sería
“calcula 2 + 3” o “calcula 5 * 60”.
9.10. Estructuras dinámicas habituales 1: las listas
enlazadas
Ahora vamos a ver dos tipos de estructuras totalmente dinámicas (que puedan aumentar o
disminuir realmente de tamaño durante la ejecución del programa). Primero veremos las listas,
y más adelante los árboles binarios. Hay otras muchas estructuras, pero no son difíciles de
desarrollar si se entienden bien estas dos.
Ahora “el truco” consistirá en que dentro de cada dato almacenaremos todo lo que nos
interesa, pero también una referencia que nos dirá dónde tenemos que ir a buscar el siguiente.
Sería algo como:
(Posición: 1023).
Nombre
: 'Nacho Cabanes'
Web
: 'www.nachocabanes.com'
SiguienteDato : 1430
Este dato está almacenado en la posición de memoria número 1023. En esa posición
guardamos el nombre y la dirección (o lo que nos interese) de esta persona, pero también una
información extra: la siguiente ficha se encuentra en la posición 1430.
Así, es muy cómodo recorrer la lista de forma secuencial, porque en todo momento sabemos
dónde está almacenado el siguiente dato. Cuando lleguemos a uno para el que no esté
definido cual es el siguiente dato, quiere decir que se ha acabado la lista.
Por tanto, en cada dato tenemos un enlace con el dato siguiente. Por eso este tipo de
estructuras recibe el nombre de “listas simplemente enlazadas=” olistas simples. Si
tuvieramos enlaces hacia el dato siguiente y el posterior, se trataría de una “lista doblemente
enlazada” o lista doble, que pretende hacer más sencillo el recorrido hacia delante o hacia
atrás.
Con este tipo de estructuras de información, hemos perdido la ventaja del acceso directo: ya no
podemos saltar directamente a la ficha número 500. Pero, por contra, podemos tener tantas
Revisión 0.90– Página 139
fichas como la memoria nos permita, y eliminar una (o varias) de ellas cuando queramos,
recuperando inmediatamente el espacio que ocupaba.
Para añadir una ficha, no tendríamos más que reservar la memoria para ella, y el compilador de
C nos diría “le he encontrado sitio en la posición 4079”. Entonces nosotros iríamos a la última
ficha y le diríamos “tu siguiente dato va a estar en la posición 4079”.
Esa es la “idea intuitiva”. Ahora vamos a concretar cosas en forma de programa en C.
Primero veamos cómo sería ahora cada una de nuestras fichas:
struct f {
/* Estos son los datos que guardamos: */
char nombre[30];
/*
Nombre, hasta 30 letras */
char direccion[50];
/*
Direccion, hasta 50 */
int edad;
/*
Edad, un numero < 255 */
struct f* siguiente;
/*
Y dirección de la siguiente */
};
La diferencia con un “struct” normal está en el campo “siguiente” de nuestro registro, que es el
que indica donde se encuentra la ficha que va después de la actual, y por tanto será otro
puntero a un registro del mismo tipo, un “struct f *”.
Un puntero que “no apunta a ningún sitio” tiene el valor NULL (realmente este identificador es
una constante de valor 0), que nos servirá después para comprobar si se trata del final de la
lista: todas las fichas “apuntarán” a la siguiente, menos la última, que “no tiene siguiente”, y
apuntará a NULL.
Entonces la primera ficha definiríamos con
struct f *dato1;
/* Va a ser un puntero a ficha */
y la comenzaríamos a usar con
dato1 = (struct f*) malloc (sizeof(struct f)); /* Reservamos memoria */
strcpy(dato1->nombre, "Pepe");
/* Guardamos el nombre, */
strcpy(dato1->direccion, "Su casa");
/*
la dirección */
dato1->edad = 40;
/*
la edad */
dato1->siguiente = NULL;
/* y no hay ninguna más */
(No debería haber anada nuevo: ya sabemos cómo reservar memoria usando “malloc” y como
acceder a los campos de una estructura dinámica usando ->).
Ahora que ya tenemos una ficha, podríamos añadir otra ficha detrás de ella.
guardamos espacio para la nueva ficha, como antes:
Primero
struct f *dato2;
dato2 = (struct f*) malloc (sizeof(struct f)); /* Reservamos memoria */
strcpy(dato2->nombre, "Juan");
/* Guardamos el nombre, */
strcpy(dato2->direccion, "No lo sé");
/*
la dirección */
dato2->edad = 35;
/*
la edad */
dato2->siguiente = NULL;
/* y no hay ninguna más */
Revisión 0.90– Página 140
y ahora enlazamos la anterior con ella:
dato1->siguiente = dato2;
Si quisieramos introducir los datos ordenados alfabéticamente, basta con ir comparando
cada nuevo dato con los de la lista, e insertarlo donde corresponda. Por ejemplo, para insertar
un nuevo dato entre los dos anteriores, haríamos:
struct f *dato3;
dato3 = (struct f*) malloc (sizeof(struct f)); /* La tercera */
strcpy(dato3->nombre, "Carlos");
strcpy(dato3->direccion, "Por ahí");
dato3->edad = 14;
dato3->siguiente = dato2;
/* enlazamos con la siguiente */
dato1->siguiente = dato3;
/* y la anterior con ella */
printf("La lista inicialmente es:\n");
La estructura que hemos obtenido es la siguiente
Dato1 - Dato3 - Dato2 - NULL
Gráficamente:
Es decir: cada ficha está enlazada con la siguiente, salvo la última, que no está enlazada con
ninguna (apunta a NULL).
Si ahora quisiéramos borrar Dato3, tendríamos que seguir dos pasos:
1.- Enlazar Dato1 con Dato2, para no perder información.
2.- Liberar la memoria ocupada por Dato3.
Esto, escrito en "C" sería:
dato1->siguiente = dato2;
free(dato3);
/* Borrar dato3: Enlaza Dato1 y Dato2 */
/* Libera lo que ocupó Dato3 */
Hemos empleado tres variables para guardar tres datos. Si tenemos 20 datos, ¿necesitaremos
20 variables? ¿Y 3000 variables para 3000 datos?
Sería tremendamente ineficiente, y no tendría mucho sentido. Es de suponer que no sea así.
En la práctica, basta con dos variables, que nos indicarán el principio de la lista y la posición
actual, o incluso sólo una para el principio de la lista.
Por ejemplo, una rutina que muestre en pantalla toda la lista se podría hacer de forma
recursiva así:
void MuestraLista ( struct f *inicial ) {
Revisión 0.90– Página 141
if (inicial!=NULL) {
/* Si realmente hay lista */
printf("Nombre: %s\n", inicial->nombre);
printf("Dirección: %s\n", inicial->direccion);
printf("Edad: %d\n\n", inicial->edad);
MuestraLista ( inicial->siguiente );
/* Y mira el siguiente */
}
}
Lo llamaríamos con "MuestraLista(Dato1)", y a partir de ahí el propio procedimiento se encarga
de ir mirando y mostrando los siguientes elementos hasta llegar a NULL, que indica el final.
Antes de seguir, vamos a juntar todo esto en un programa, para comprobar que realmente
funciona: añadimos los 3 datos y decimos que los muestre desde el primero; luego borramos el
del medio y los volvemos a mostrar:
/*---------------------------*/
/* Ejemplo en C nº 81:
*/
/* c081.c
*/
/*
*/
/* Primer ejemplo de lista */
/* enlazada simple
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
#include <stdlib.h>
struct f {
/* Estos son los datos que guardamos: */
char nombre[30];
/*
Nombre, hasta 30 letras */
char direccion[50];
/*
Direccion, hasta 50 */
int edad;
/*
Edad, un numero < 255 */
struct f* siguiente;
/*
Y dirección de la siguiente */
};
struct f *dato1;
struct f *dato2;
struct f *dato3;
/* Va a ser un puntero a ficha */
/* Otro puntero a ficha */
/* Y otro más */
void MuestraLista ( struct f *inicial ) {
if (inicial!=NULL) {
/* Si realmente hay lista */
printf("Nombre: %s\n", inicial->nombre);
printf("Dirección: %s\n", inicial->direccion);
printf("Edad: %d\n\n", inicial->edad);
MuestraLista ( inicial->siguiente );
/* Y mira el siguiente */
}
}
int main() {
dato1 = (struct f*) malloc (sizeof(struct f)); /* Reservamos memoria */
strcpy(dato1->nombre, "Pepe");
/* Guardamos el nombre, */
strcpy(dato1->direccion, "Su casa");
/*
la dirección */
dato1->edad = 40;
/*
la edad */
dato1->siguiente = NULL;
/* y no hay ninguna más */
dato2 = (struct f*) malloc (sizeof(struct f)); /* Reservamos memoria */
strcpy(dato2->nombre, "Juan");
/* Guardamos el nombre, */
strcpy(dato2->direccion, "No lo sé");
/*
la dirección */
dato2->edad = 35;
/*
la edad */
dato2->siguiente = NULL;
/* y no hay ninguna más */
dato1->siguiente = dato2;
/* Enlazamos anterior con ella */
Revisión 0.90– Página 142
dato3 = (struct f*) malloc (sizeof(struct f)); /* La tercera */
strcpy(dato3->nombre, "Carlos");
strcpy(dato3->direccion, "Por ahí");
dato3->edad = 14;
dato3->siguiente = dato2;
/* enlazamos con la siguiente */
dato1->siguiente = dato3;
/* y la anterior con ella */
printf("La lista inicialmente es:\n");
MuestraLista (dato1);
dato1->siguiente = dato2;
/* Borrar dato3: Enlaza Dato1 y Dato2 */
free(dato3);
/* Libera lo que ocupó Dato3 */
printf("Y tras borrar dato3:\n\n");
MuestraLista (dato1);
return 0;
}
Vamos a ver otro ejemplo, que cree una lista de números, y vaya insertando en ella varios
valores ordenados. Ahora tendremos una función para mostrar datos, otra para crear la lista
insertando el primer dato, y otra que inserte un dato ordenado
/*---------------------------*/
/* Ejemplo en C nº 82:
*/
/* c082.c
*/
/*
*/
/* Segundo ejemplo de lista */
/* enlazada (ordenada)
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
#include <stdlib.h>
struct lista {
int numero;
struct lista* sig;
};
/* Nuestra lista */
/* Solo guarda un numero */
/* Y el puntero al siguiente dato */
struct lista* CrearLista(int valor) { /* Crea la lista, claro */
struct lista* r;
/* Variable auxiliar */
r = (struct lista*)
malloc (sizeof(struct lista)); /* Reserva memoria */
r->numero = valor;
/* Guarda el valor */
r->sig = NULL;
/* No hay siguiente */
return r;
/* Crea el struct lista* */
};
void MuestraLista ( struct lista *lista
if (lista) {
printf("%d\n", lista->numero);
MuestraLista (lista->sig );
};
};
) {
/* Si realmente hay lista */
/* Escribe el valor */
/* Y mira el siguiente */
void InsertaLista( struct lista **lista, int valor) {
struct lista* r;
/* Variable auxiliar, para reservar */
struct lista* actual;
/* Otra auxiliar, para recorrer */
actual = *lista;
if (actual)
if (actual->numero < valor)
/* Si hay lista */
/*
y todavía no es su sitio */
Revisión 0.90– Página 143
InsertaLista(&actual->sig,valor); /*
mira la siguiente posición */
else {
/* Si hay lista pero ya es su sitio */
r = CrearLista(valor);
/* guarda el dato */
r->sig = actual;
/* pone la lista a continuac. */
*lista = r;
/* Y hace que comience en el nuevo dato */
}
else {
/* Si no hay lista, hay que crearla */
r = CrearLista(valor);
*lista = r;
/* y hay que indicar donde debe comenzar */
}
}
int main() {
struct lista* l;
l = CrearLista(5);
InsertaLista(&l, 3);
InsertaLista(&l, 2);
InsertaLista(&l, 6);
MuestraLista(l);
return 0;
}
/*
/*
/*
/*
/*
/*
/*
La lista que crearemos */
Crea una lista e introduce un 5 */
Inserta un 3 */
Inserta un 2 */
Inserta un 6 */
Muestra la lista resultante */
Se acabó */
No es un fuente fácil de leer (en general, no lo serán los que manejen punteros), pero aun así
los cambios no son grandes:
•
Hemos automatizado la inserción de un nuevo dato con la función CrearLista.
•
Cuando después de ese dato debemos enlazar datos existentes, lo hacemos en dos
pasos: r = CrearLista(valor); r->sig = actual;
•
Si además el dato que modificamos es el primero de toda la lista, deberemos modificar
la dirección de comienzo para que lo refleje: r = CrearLista(valor); *lista = r;
•
Como ya vimos, cuando queremos modificar un dato de tipo “int”, pasamos un
parámetro que es de tipo “int *”. En este caso, la función InsertaLista puede tener que
modificar un dato que es de tipo “struct lista *”, por lo que el parámetro a modificar
deberá ser de tipo “struct lista **”. Al final del tema comentaremos algo más sobre este
tipo de expresiones.
•
Por otra parte, también hemos abreviado un poco alguna expresión: if (lista) es lo
mismo que if (lista!=NULL) (recordemos que NULL es una constante que vale 0).
Finalmente, hay varios casos particulares que resultan más sencillos que una lista “normal”.
Vamos a comentar los más habituales:
•
Una pila es un caso particular de lista, en la que los elementos siempre se introducen y
se sacan por el mismo extremo (se apilan o se desapilan). Es como una pila de libros,
en la que para coger el tercero deberemos apartar los dos primeros (excluyendo
malabaristas, que los hay). Este tipo de estructura se llama LIFO (Last In, First Out: el
último en entrar es el primero en salir).
Revisión 0.90– Página 144
•
Una cola es otro caso particular, en el que los elementos se introducen por un extremo
y se sacan por el otro. Es como se supone que debería ser la cola del cine: los que
llegan, se ponen al final, y se atiende primero a los que están al principio. Esta es una
estructura FIFO (First In, First Out).
Ejercicio propuesto: Las listas simples, tal y como las hemos tratado, tienen la ventaja de
que no hay limitaciones tan rígidas en cuanto a tamaño como en las variables estáticas, ni hay
por qué saber el número de elementos desde el principio. Pero siempre hay que recorrerlas
desde DELANTE hacia ATRAS, lo que puede resultar lento. Una mejora relativamente evidente
es lo que se llama una lista doble o lista doblemente enlazada: si guardamos punteros al dato
anterior y al siguiente, en vez de sólo al siguiente, podremos avanzar y retroceder con
comodidad. Implementa una lista doble enlazada que almacene números enteros.
9.11. Estructuras dinámicas habituales 2: los árboles
binarios
En las listas, después de cada elemento había otro, el “siguiente” (o ninguno, si habíamos
llegado al final).
Pero también nos puede interesar tener varias posibilidades después de cada elemento, varios
“hijos=”, por ejemplo 3. De cada uno de estos 3 “hijos=”
saldrían otros 3, y así sucesivamente.
Obtendríamos algo que recuerda a un árbol: un tronco del que nacen 3 ramas, que a su veces
se subdividen en otras 3 de menor tamaño, y así sucesivamente hasta llegar a las hojas.
Pues eso mismo será un árbol: una estructura dinámica en la que cada nodo (elemento) puede
tener más de un “siguiente”. Nos centraremos en los árboles binarios, en los que cada nodo
puede tener un hijo izquierdo, un hijo derecho, ambos o
ninguno (dos hijos como máximo).
Para puntualizar aun más, trataremos los árboles binarios de
búsqueda, en los que tenemos prefijado un cierto orden,
que nos ayudará a encontrar un cierto dato dentro de un
árbol con mucha rapidez.
Este "orden prefijado" será el siguiente: para cada nodo
tendremos que
• la rama de la izquierda contendrá elementos menores que él.
• la rama de la derecha contendrá elementos mayores que él.
Para que se entienda mejor, vamos a introducir en un árbol binario de búsqueda los datos
5,3,7,2,4,8,9
Primer número: 5 (directo)
5
Segundo número: 3 (menor que 5)
Revisión 0.90– Página 145
5
/
3
Tercer número: 7 (mayor que 5)
5
/ \
3
7
Cuarto: 2 (menor que 5, menor que 3)
5
/ \
3
7
/
2
Quinto: 4 (menor que 5, mayor que 3)
5
/ \
3
7
/ \
2
4
Sexto: 8 (mayor que 5, mayor que 7)
5
/ \
3
7
/ \
\
2
4
8
Séptimo: 9 (mayor que 5, mayor que 7, mayor que 8)
5
/ \
3
7
/ \
\
2
4
8
\
9
¿Qué ventajas tiene esto? La rapidez: tenemos 7 elementos, lo que en una lista supone que si
buscamos un dato que casualmente está al final, haremos 7 comparaciones; en este árbol,
tenemos 4 alturas => 4 comparaciones como máximo.
Y si además hubiéramos “equilibrado” el árbol (irlo recolocando, de modo que siempre tenga
la menor altura posible), serían 3 alturas. Esto es lo que se hace en la práctica cuando en el
árbol se va a hacer muchas más lecturas que escrituras: se reordena internamente después de
añadir cada nuevo dato, de modo que la altura sea mínima en cada caso. De este modo, el
número máximo de comparaciones que tendríamos que hacer sería log2(n), lo que supone que
si tenemos 1000 datos, en una lista podríamos llegar a tener que hacer 1000 comparaciones, y
en un árbol binario, log2(1000) => 10 comparaciones como máximo. La ganancia en velocidad
de búsqueda es clara.
No vamos a ver cómo se hace eso de los “equilibrados=”, ue
q sería propio de un curso de
programación más avanzado, pero sí vamos a empezar a ver rutinas para manejar estos árboles
binarios de búsqueda.
Revisión 0.90– Página 146
Recordemos que la idea importante es que todo dato menor estará a la izquierda del nodo que
miramos, y los datos mayores estarán a su derecha.
Ahora la estructura de cada nodo (dato) será:
struct arbol {
/* El tipo base en sí: */
int dato;
/*
- un dato (entero) */
struct arbol* hijoIzq; /*
- puntero a su hijo izquierdo */
struct arbol* hijoDer; /*
- puntero a su hijo derecho */
};
Y las rutinas de inserción, búsqueda, escritura, borrado, etc., podrán ser recursivas. Como
primer ejemplo, la de escritura de todo el árbol en orden sería:
void Escribir(struct arbol *punt)
{
if (punt)
/*
{
Escribir(punt->hijoIzq);
/*
printf("%d ",punt->dato); /*
Escribir(punt->hijoDer);
/*
};
};
Si no hemos llegado a una hoja */
Mira la izqda recursivamente */
Escribe el dato del nodo */
Y luego mira por la derecha */
Quien no se crea que funciona, debería coger lápiz y papel comprobarlo con el árbol que hemos
visto antes como ejemplo. Es muy importante que esta función quede clara antes de seguir
leyendo, porque los demás serán muy parecidos.
La rutina de inserción sería parecida, aunque algo más "pesada" porque tenemos que pasar el
puntero por referencia, para que se pueda modificar el puntero:
void Insertar(struct arbol **punt, int valor)
{
struct arbol * actual= *punt;
if (actual == NULL)
/* Si hemos llegado a una hoja */
{
*punt = (struct arbol *)
malloc (sizeof(struct arbol)); /* Reservamos memoria */
actual= *punt;
actual->dato = valor;
/* Guardamos el dato */
actual->hijoIzq = NULL;
/* No tiene hijo izquierdo */
actual->hijoDer = NULL;
/* Ni derecho */
}
else
/*
Si no es hoja
*/
if (actual->dato > valor)
/* Y encuentra un dato mayor */
Insertar(&actual->hijoIzq, valor); /* Mira por la izquierda */
else
/* En caso contrario (menor) */
Insertar(&actual->hijoDer, valor); /* Mira por la derecha */
};
Y finalmente, la de borrado de todo el árbol, casi igual que la de escritura, sólo que en vez de
borrar la izquierda, luego el nodo y luego la derecha, borraremos primero las dos ramas y en
último lugar el nodo, para evitar incongruencias (intentar borrar el hijo de algo que ya no
existe):
Revisión 0.90– Página 147
void Borrar(struct arbol *punt)
{
if (punt)
/*
{
Borrar(punt->hijoIzq);
/*
Borrar(punt->hijoDer);
/*
free (punt);
/*
};
};
Si no hemos llegado a una hoja */
Va a la izqda recursivamente */
Y luego a la derecha */
Finalmente, libera lo que ocupa el nodo */
Vamos a juntar todo esto en un ejemplo "que funcione":
/*---------------------------*/
/* Ejemplo en C nº 83:
*/
/* c083.c
*/
/*
*/
/* Arbol binario de
*/
/* búsqueda
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
#include <stdlib.h>
struct arbol {
/* El tipo base en sí: */
int dato;
/*
- un dato (entero) */
struct arbol* hijoIzq; /*
- puntero a su hijo izquierdo */
struct arbol* hijoDer; /*
- puntero a su hijo derecho */
};
void Escribir(struct arbol *punt)
{
if (punt)
/*
{
Escribir(punt->hijoIzq);
/*
printf("%d ",punt->dato); /*
Escribir(punt->hijoDer);
/*
};
};
Si no hemos llegado a una hoja */
Mira la izqda recursivamente */
Escribe el dato del nodo */
Y luego mira por la derecha */
void Insertar(struct arbol **punt, int valor)
{
struct arbol * actual= *punt;
if (actual == NULL)
/* Si hemos llegado a una hoja */
{
*punt = (struct arbol *)
malloc (sizeof(struct arbol)); /* Reservamos memoria */
actual= *punt;
actual->dato = valor;
/* Guardamos el dato */
actual->hijoIzq = NULL;
/* No tiene hijo izquierdo */
actual->hijoDer = NULL;
/* Ni derecho */
}
else
/*
Si no es hoja
*/
if (actual->dato > valor)
/* Y encuentra un dato mayor */
Insertar(&actual->hijoIzq, valor); /* Mira por la izquierda */
else
/* En caso contrario (menor) */
Insertar(&actual->hijoDer, valor); /* Mira por la derecha */
};
/*
Cuerpo del programa */
Revisión 0.90– Página 148
int main()
{
struct arbol *arbol = NULL;
Insertar(&arbol, 5);
Insertar(&arbol, 3);
Insertar(&arbol, 7);
Insertar(&arbol, 2);
Insertar(&arbol, 4);
Insertar(&arbol, 8);
Insertar(&arbol, 9);
Escribir(arbol);
return 0;
}
9.12. Indirección múltiple
Lo que estamos haciendo mediante los punteros es algo que técnicamente se conoce como
“direccionamiento indirecto”: cuando hacemos int *n, generalmente no nos va a interesar el
valor de n, sino que n es una dirección de memoria a la que debemos ir a mirar el valor que
buscamos.
Pues bien, podemos hacer que ese direccionamiento sea todavía menos directo que en el caso
normal: algo como int **n se referiría a que n es una dirección de memoria, en la que a su vez
se encuentra como dato otra dirección de memoria, y dentro de esta segunda dirección de
memoria es donde se encuentra el dato. Es decir, n sería un “puntero a puntero a entero”.
Esta no es una situación habitual, así que no profundizaremos más en ella.
Aun así, lo que sí se debe recordar es que
char **datos
es algo muy parecido a
char
datos[][], por lo que alguna vez lo veremos indicado como parámetros de una función de una
forma o de la otra.
9.13. Un ejemplo: copiador de ficheros en una pasada
Como ejemplo de un fuente en el que se apliquen algunas de las ideas más importantes que
hemos visto, vamos a crear un copidor de ficheros, que intente copiar todo el fichero de origen
en una única pasada: calculará su tamaño, intentará reservar la memoria suficiente para
almacenar todo el fichero a la vez, y si esa memoria está disponible, leerá el fichero completo y
lo guardará con un nuevo nombre.
/*---------------------------*/
/* Ejemplo en C nº 84:
*/
/* c084.c
*/
/*
*/
/* Copiador de ficheros en */
/* una pasada
*/
/*
*/
/* Curso de C,
*/
/*
Nacho Cabanes
*/
/*---------------------------*/
#include <stdio.h>
Revisión 0.90– Página 149
FILE *fichOrg, *fichDest;
char *buffer;
char nombreOrg[80],
nombreDest[80];
long longitud;
long cantidad;
/* Los dos ficheros */
/* El buffer para guardar lo que leo */
/* Los nombres de los ficheros */
/* Tamaño del fichero */
/* El número de bytes leídos */
main()
{
/* Accedo al fichero de origen */
printf("Introduzca el nombre del fichero Origen: ");
scanf("%s",nombreOrg);
if ((fichOrg = fopen(nombreOrg, "rb")) == NULL)
{
printf("No existe el fichero origen!\n");
exit(1);
}
/* Y ahora al de destino */
printf("Introduzca el nombre del fichero Destino: ");
scanf("%s",nombreDest);
if ((fichDest = fopen(nombreDest, "wb")) == NULL)
{
printf("No se ha podido crear el fichero destino!\n");
exit(2);
}
/* Miro la longitud del fichero de origen */
fseek(fichOrg, 0, SEEK_END);
longitud = ftell(fichOrg);
fseek(fichOrg, 0, SEEK_SET);
/* Reservo espacio para leer todo */
buffer = (char *) malloc (longitud);
if (buffer == NULL)
{
printf("No se ha podido reservar tanto espacio!\n");
exit(3);
}
/* Leo todos los datos a la vez */
cantidad = fread( buffer, 1, longitud, fichOrg);
/* Escribo tantos como haya leído */
fwrite(buffer, 1, cantidad, fichDest);
if (cantidad != longitud)
printf("Cuidado: no se han leido (ni copiado) todos los datos\n");
/* Cierro los ficheros */
fclose(fichOrg);
fclose(fichDest);
}
Revisión 0.90– Página 150
Descargar