ESTRUCTURAS Y UNIONES
ESTRUCTURAS:
Cuando uno declara una variable en un programa, está definiendo un espacio de memoria en la que se podrá almacenar un solo valor. Por ejemplo cuando colocamos en un programa la sentencia int a; estamos definiendo la variable entera a, la cual sólo podrá almacenar un valor en un instante dado. Este tipo de variables se denominan escalares.
Por otro lado cuando definimos un arreglo (estático o dinámico) estamos definiendo un conjunto de variables, todas identificadad por un mismo nombre, diferenciándose cada una por uno o más indices. Por ejemplo cuando escribimos la sentencia int a[5]; estamos definiendo las variables enteras a[0], a[1], a[2], a[3] y a[4]. Hemos visto en los capítulos anteriores la cantidad de aplicaciones que podemos desarrollar con este tipo de dato, sin embargo a pesar de ello, los arreglos tienen un inconveniente, que todos elementos de un arreglo tienen el mismo tipo de dato Esto quiere decir que, en el ejemplo, en el arrglo a sus elementos solo pueden albergar valores enteros. Si quisiéramos almacenar por medio de un programa una lista de personas en donde por cada una tenemos un código, un nombre y un sueldo, para poder hacerlo tendríamos que definir un arreglo por cada uno de los datos, esto es un arrglo de enteros para el código, un arreglo de cadenas de caracteres para el nombre y uno de punto flotante para el sueldo. El inconveniente sería que los datos de una persona no se pueden manejar, así, como una unidad. Los arreglos reciben el nombre de variables estructuradas.
Los registro o "estructuras" como se denominan en el lenguanje C, permiten definir variables que pertenencen también a la categoría de variables estructuradas, en este sentido al declarar una estructura estaremos definiendo, como en el caso de los arreglos, un conjunto de datos, todos identificados por el mismo nombre, pero con la diferencia que cada elemento del conjunto puede ser de diferente tipo. La forma en que se diferenciará un elemento de otro del conjunto ya no se hará por medio de índices sino que se le asignará a cada elemento un nombre. Cada elemento de una estructura se denominará campo. Las estructuras son un tipo primitivo de datos, su evolución ha dado lugar a las "Clases" definidas en la programación orientada a objetos (POO) e implementadas en C++.
Declaración de una estructura
A diferencia de los arreglos, al declarar una estructura no estamos definiendo una variable directamente, si no un "Tipo de dato". Esto quiere decir que el nombre dado a la estructura servirá para declarar variables de ese tipo.
Cuando se elabora un programa, es muy probable que éste se desarrolle empleando varios módulos, como vimos en capítulos anteriores. Entonces, si definimos un tipo de dato como una estructura, también será probable que necesitemos declarar variables de ese tipo en más de un módulo. Aquí se presenta un problema, como cada módulo se compila independientemente, el compilador detectará un error de sintaxis en la declaración de la variable, a menos que el tipo de dato sea definido en ese módulo. Por lo tanto habría que definir el tipo de dato en cada módulo en el que querramos declarar variables de ese tipo. Esto no es práctico ni lógico, primero por el trabajo que esto implica, pero lo más grave es que en cada definición que hagamos podemos introducir pequeñas diferencias que harían incompatibles las variables definidad en diferntes módulos.
De acuerdo a esto, se debe buscar una solución más práctica a este problema, en este sentido ésta se dará por el camino de la inclusión de archivos. Esto quiere decir que declararemos las estrucuras en archivos de cabecera (con extensión .h), una declaración por archivo. Luego, para evitar que se produzcan errores de compilación por duplicidad de identificadores, debemos agregar a la declaración del tipo, cláusulas de inclusión condicional, como se vió en el capítulo de funciones.
Según esto, la sintaxis para la declaración de un regisro será la siguiente:
#ifndef NOMBREARCH_H
#define NOMBREARCH_H
struct NombreDelTipo {
TipoDeDato nombreDeCampo1 ;
TipoDeDato nombreDeCampo2 ;
TipoDeDato nombreDeCampo3 ;
. . .
TipoDeDato nombreDeCampoN ;
};
#endif /* NOMBREARCH_H */
en donde podemos definir:
NOMBREARCH_H es el nombre que tendrá el archivo de cabecera en donde declararemos la estructura. Por ejemplo si el nombre del archivo fuera Registro.h, en las cláusulas del preprocesador colocaremos REGISTRO_H.
struct es una palabra reservada del C/C++, le indica al compilador que se está definiendo una estructura.
NombreDelTipo es el nombre del tipo de dato que estamos definiendo, este nombre se empleará para declarar variables del tipo. Por convención debe empezar con una mayúscula.
TipoDeDato Indica el tipo de datos que tendrá el campo o elemento del conjunto. Como se comentó anteriormente cada elemento del conjunto podrá tener un tipo de dato diferente. Se puede emplear cualquier tipo de dato, no existe restricción alguna.
nombreDeCampoi es el nombre que tendrá el campo y que servirá para diferenciarlo de los otros campos de la estructura. El nombre del campo debe empezar con una minúscula.
A continuación se muesta un ejemplo de como se define una estructura:
#ifndef TPERSONA_H
#define TPERSONA_H
struct TPersona {
char codigo[15];
char *nombre;
int dni;
char direccion[50];
double sueldo;
};
#endif /* TPERSONA_H */
De acuerdo a esta definición los módulos que requieran declarar variables de este tipo deberán incluir el archivo de cabecera y emplear un protocolo de definición que dependerá si estamos trabajando con C o con C++.
En el caso del lenguaje C, la palabra reservada struct es parte del nombre del tipo de dato, por lo que debe aparecer siempre e las declaraciones de variables y funciones. En el caso de C++ no será necesario. Ver el siguiente ejemplo:
// Empleando C
#include "TPersona.h"
int main (void){
struct TPersona empleado;
. . .
}
// Empleando C++
#include "TPersona.h"
int main (void){
TPersona empleado; // No requiere la palabra struct
. . .
}
Representación interna de una estructura
Una variable de tipo struct como empleado se almacena en la pila del proceso de una manera particular, a continuación mostramos esto:
Asignación de datos a una estructura
Una vez declarada la variable se podrá manipular ésta, asignando valores a sus campos o utilizando los valores de éstos en otras expresiones. La forma de manipular el campo se hará por medio del nombre de la variable, seguido por el nombre del campo, ambas palabras se separarán por un punto. Los límites para el manejo de un campo estarán dados por el tipo de dato en que fue definido. Esto es:
#include "TPersona.h"
int main (void) {
TPersona empleado;
strcpy (empleado.codigo, "995-535262");
empleado.nombre = new char [30];
strcpy (empleado.nombre, "Juan Lopez");
empleado.dni = 82716612;
...
printf("Nombre = %s\n", empleado.nombre);
}
Otra forma de asignar valores a una variable de tipo struct se puede hacer al momento de declararla, esta operación se hace de la siguiente manera:
int main(void) {
TPersona empleado = {"995-535262", "Juan Lopez", 82716612,
"Av. ABC 123", 12345.67};
printf("Codigo : %s\n", empleado.codigo);
printf("Nombre : %s\n", empleado.nombre);
printf("DNI : %d\n", empleado.dni);
printf("Direccion: %s\n", empleado.direccion);
printf("Sueldo : %f\n", empleado.sueldo);
}
La manera en que se almacenan las variables de tipo struct permite manejar las variables como una unidad, y a diferencia de los arreglos, se podrán asignar los valores de todos los campos de una variable a otra en una sola operación. Esto se puede apreciar en el ejemplo siguiente:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "TPersona.h"
int main(void) {
TPersona empleado, trabajador;
char aux[100];
printf("Ingrese los datos del empleado:\n");
printf("Codigo : "); gets(empleado.codigo);
printf("Nombre : "); gets(aux);
empleado.nombre = new char[strlen(aux)+1];
strcpy(empleado.nombre, aux);
printf("DNI : ");
scanf("%d", &empleado.dni); while(getchar()!='\n');
printf("Direccion : "); gets(empleado.direccion);
printf("Sueldo : ");
scanf("%lf", &empleado.sueldo); while(getchar()!='\n');
printf("Datos leidos a empleado:\n");
printf("Codigo : %s\n", empleado.codigo);
printf("Nombre : %s\n", empleado.nombre);
printf("DNI : %d\n", empleado.dni);
printf("Direccion : %s\n", empleado.direccion);
printf("Sueldo : %10.2f\n", empleado.sueldo);
// Pasamos el contenido de la variable empleado
// a la variable trabajados
trabajador = empleado;
printf("\n\nDatos asignados a trabajador:\n");
printf("Codigo : %s\n", trabajador.codigo);
printf("Nombre : %s\n", trabajador.nombre);
printf("DNI : %d\n", trabajador.dni);
printf("Direccion : %s\n", trabajador.direccion);
printf("Sueldo : %10.2f\n", trabajador.sueldo);
return (EXIT_SUCCESS);
}
La salida de este programa será similar a la siguiente:
A pesar que la operación de asignación se puede dar entre variables de tipo struct, se debe tener muy presente los conceptos teóricos sobre la asignación de datos. En el ejemplo anterior la asignación se ha dado correctamente, pero sería bueno analizar el siguiente programa, observe en él, que el código siendo similar al del ejemplo anterior, se le ha agregado asignaciones de datos a dos de los campos de la variable trabajador. El resultado lo puede sorprender.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "TPersona.h"
int main(void) {
TPersona empleado, trabajador;
char aux[100];
printf("Ingrese los datos del empleado:\n");
printf("Codigo : "); gets(empleado.codigo);
printf("Nombre : "); gets(aux);
empleado.nombre = new char[strlen(aux)+1];
strcpy(empleado.nombre, aux);
printf("DNI : ");
scanf("%d", &empleado.dni); while(getchar()!='\n');
printf("Direccion : "); gets(empleado.direccion);
printf("Sueldo : ");
scanf(&qut;%lf", &empleado.sueldo); while(getchar()!='\n');
printf("\n\nDatos leidos en empleado:\n");
printf("Codigo : %s\n", empleado.codigo);
printf("Nombre : %s\n", empleado.nombre);
printf("DNI : %d\n", empleado.dni);
printf("Direccion : %s\n", empleado.direccion);
printf("Sueldo : %10.2f\n", empleado.sueldo);
// pasamos el contenido de la variable empleado
// a la variable trabajados
trabajador = empleado;
// Modificamos el campo dni en la variable trabajador
trabajador.dni = 55667788;
// Modificamos el campo nombre
strcpy(trabajador.nombre, "Maria Li");
printf("\n\nDatos asignados a trabajador:\n");
printf("Codigo : %s\n", trabajador.codigo);
printf("Nombre : %s\n", trabajador.nombre);
printf("DNI : %d\n", trabajador.dni);
printf("Direccion : %s\n", trabajador.direccion);
printf("Sueldo : %10.2f\n", trabajador.sueldo);
printf("\n\nDatos contenidos en empleado:\n");
printf("Codigo : %s\n", empleado.codigo);
printf("Nombre : %s\n", empleado.nombre);
printf("DNI : %d\n", empleado.dni);
printf("Direccion : %s\n", empleado.direccion);
printf("Sueldo : %10.2f\n", empleado.sueldo);
return (EXIT_SUCCESS);
}
La respuesta a este programa la presentamos a continuación:
Observe que la asignación del DNI a la variable trabajador no afecta el valor del campo en la variable empleado, sin embargo la asignación del nombre si. Esto se debe a que el campo nombre, a diferencia del campo dni, es un puntero, por esa razón cuando se hace la operación trabajador = empleado; se asigna la dirección donde apunta el campo nombre de empleado al campo nombre de trabajador, terminando ambos apuntando al mismo lugar, como se aprecia en la figura siguiente:
Por esa razón se debe tener mucho cuidade al realizar una asignación deirecta entre variables de tipo struct.
Otra forma de asignar datos a una variable de tipo struct es a través de una función, esto es, un función en C/C++ puede devolver un dato de tipo struct, como se muestra a continuación:
#ifndef LEESTRUCT_H
#define LEESTRUCT_H
#include "TPersona.h"
TPersona leeStruct(void);
#endif /* LEESTRUCT_H */
#include <stdio.h>
#include <string.h>
#include "TPersona.h"
TPersona leeStruct(void) {
TPersona dato;
char aux[100];
printf("Ingrese los datos del empleado:\n");
printf("Codigo : "); gets(dato.codigo);
printf("Nombre : "); gets(aux);
dato.nombre = new char[strlen(aux)+1];
strcpy(dato.nombre, aux);
printf("DNI : ");
scanf("%d", &dato.dni); while(getchar() != '\n');
printf("Direccion: "); gets(dato.direccion);
printf("Sueldo : ");
scanf("%lf", &dato.sueldo); while(getchar() !=' \n');
return dato;
}
#include <stdlib.h>
#include <stdio.h>
#include "TPersona.h"
#include "leeStruct.h"
int main(void) {
TPersona empleado;
empleado = leeStruct( );
printf("\n\nDatos leidos en empleado:\n");
printf("Codigo : %s\n", empleado.codigo);
printf("Nombre : %s\n", empleado.nombre);
printf("DNI : %d\n", empleado.dni);
printf("Direccion : %s\n", empleado.direccion);
printf("Sueldo : %10.2f\n", empleado.sueldo);
return 0;
}
La salida de este programa será similar a la siguiente:
Comparación de datos entre estructuras
A pesar que las estructuras de datos se pueden asignar directamente, esto no se puede hacer así cuando se trata de compararlas. Debido a la forma cómo se representa una estructura en memoria, la manera en que se podrían comparar dos estructuras directamente tendría que ser hecho byte a byte, si se encuentra que un byte no coincide, la igualdad en la comparación facasaría. Por eso, si un campo es definido como un arreglo, nada nos garantiza que todos los bytes coincidan, incluso los que no se usan. Lo misma pasaría con los punteros.
Por esta razón al comparar estructuras, se debe hacer esta operación campo a campo y no como una unidad.
Anidación de una estructura
Este concepto se refiere a que un tipo de dato struct se puede emplear como tipo de dato para definir un o más campos en otra estructura. El ejemplo siguiente mustra esta propiedad:
#ifndef TFECHA_H
#define TFECHA_H
struct TFecha {
int dd; // día
int mm; // mes
int aa; // año
};
#endif /* TFECHA_H */
#ifndef TEMPLEADO_H
#define TEMPLEADO_H
#include "TFecha.h"
struct TEmpleado {
int dni;
char *nombre;
char *direccion;
TFecha fechaDeNacimiento;
char *cargo;
TFecha fechaDeIngreso;
};
#endif /* TEMPLEADO_H */
Luego, para manejar los campos en la anidación de una estructura, se debe hacer siguiendo la misma lógica que se sigue en el caso de dun estructura simple, esto es:
#include "TEmpleado.h"
int main(void) {
TEmpleado empleado;
empleado.fechaDeNacimiento.dd = 26;
empleado.fechaDeNacimiento.mm = 7;
empleado.fechaDeNacimiento.aa = 1975;
...
Punteros a estructuras
La definición de un puntero a una estructura es una tarea simple, sin embargo es en el manejo de la variable refernciada donde debemos centrar nuestra atención. A continuación definiremos una estructura sencilla, y a partir de ella veremos en detalle el manejo de la variable referenciada.
#ifndef TPERSONA_H
#define TPERSONA_H
struct TPersona {
int dni;
char *nombre;
double sueldo;
};
#endif /* TPERSONA_H */
#include "TPersona.h"
int main(void) {
// declaración:
TPersona *empleado;
// asignación de memoria:
empleado = new TPersona;
...
Cuando definimos aquí la variable empleado debemos tener bien claro que lo que hemos creado es un puntero y no una variable de tipo struct. Es recién cuando asignamos una dirección válida al puntero cuando se crea la variable refenciada de tipo struct. La figura siguiente muestra este detalle:
Para acceder a los campos de la variable referenciada, debemos tener en cuenta que cuando se hace mención a un campo de una variable de tipo struct el compilador reconoce al nombre de la variable, al punto y al nombre del campo como el identificador, por lo tanto el colocar el asterisco (*) al lado para llegar a la variable referenciada podría causar un error.
Por ejemplo, si con el puntero empleado queremos llegar al campo dni, el escribir *empleado.dni es un error porque el campo dni no es un puntero, es una variable de tipo int, quien es un puntero es empleado.
Por lo tanto, la manera correcta de llegar al campo dni es escribir (*empleado).dni, de ese modo el operador * se aplica sobre el puntero y no sobre el campo.
Esa forma de acceder al campo de la variable referenciada, aunque es correcta, es un poco forzada. El lenguaje C/C++ da una alternativa para esta situación, basándose en la figura que a continuación se mustra:
Como se ve, la flecha lleva el puntero a la variable referenciada, de allí que el lenguaje define el operador ->, con el guión (-) y el signo mayor que (>), que semeja a una flecha, para llegar al campo de la variable referenciada. Así para tarbajar con el campo dni a través del puntero empleado usaremos la siguiente sintaxis: empleado->dni.
De debe tener en cuenta que el operador -> se emplea porque la variable empleado es un puntero, si no lo fuera estaríamos obligados a usar el punto (.).
El siguiente ejemplo muesrtra cómo emplearemos esta nueva nomenclatura y cuando emplearemos el punto (.) y cuando el ->.
#include <string.h>
#include "TPersona.h"
int main(void) {
// declaración:
TPersona *empleado, *trabajador;
// asignación de memoria:
empleado = new TPersona;
// manejo de la variable referenciada
empleado->dni = 17566570;
char nomb[50];
strcpy(nomb, "Ana Roncal");
empleado->nombre = new char [strlen(nomb) + 1];
strcpy(empleado->nombre, nomb);
empleado->sueldo = 3456.53;
// manejo de un arreglo dinámico de estructuras
trabajador = new TPersona[10];
//manipulación de un elemento del arreglo
trabajador[2].dni = 17566570;
trabajador[2].nombre = new char [strlen(nomb) + 1];
strcpy(trabajador[2].nombre, nomb);
trabajador[2].sueldo = 3456.53;
//obserbe que no se debe usar el operador -> porque
// la referencia se hace a través del índice ([2])
...
Estructuras autoreferenciadas
Una estructura autoreferenciada es una variable de tipo struct en la que uno o más de sus campos son punteros del mismo tipo que la estructura que lo define. Bajo este concepto, una variable definida así puede enlazarse (apuntar), a través de estos campos, a otra u otras variables del mismo tipo, gerenado las denominadas "Listas ligadas", "Árboles", etc.
Los ejemplos siguientes muestran la definición de diversas estructuras de datos que se definen bajo este concepto:
#ifndef LISTASIMPLE_H
#define LISTASIMPLE_H
//Nodo de una lista simplemente ligada (LSL)
struct NodoLSL {
int codigo;
char *nombre;
NodoLSL *siguiente;
};
#endif /* LISTASIMPLE_H */
La estructura anterior generará la siguiente estructura de datos:
#ifndef ARBOL_H
#define ARBOL_H
//Nodo de un árbol binario
struct NodoArbol {
int codigo;
char *nombre;
NodoArbol *izquierda;
NodoArbol *derecha;
};
#endif /* ARBOL_H */
La estructura que se forma con la definición anterior es la siguiente:
A continuación se presentan dos programas en el que se implementan una lista simplemente ligada ordenada y un árbol binario:
Lista simplemente ligada ordenada
// **********************************************************
// Programa que crea una lista simplemente ligada ordenada
// **********************************************************
#include <stdlib.h>
#include "Nodo.h"
#include "ListaSL.h"
int main(void) {
Nodo *lista = NULL;
crear(lista);
mostrar(lista);
eliminar(lista);
return (EXIT_SUCCESS);
}
// Archivo de cabecera: Nodo.h
#ifndef NODO_H
#define NODO_H
//Nodo de una lista simplemente ligada (LSL)
struct Nodo {
int codigo;
char *nombre;
Nodo *siguiente;
};
#endif /* NODO_H */
// Archivo de cabecera: Lista.h
#ifndef LISTASL_H
#define LISTASL_H
#include "Nodo.h"
void crear(Nodo *&);
void mostrar(Nodo *);
void eliminar(Nodo *);
Nodo *leeRegistro(void);
void insertar(Nodo *&, Nodo *);
#endif /* LISTASL_H */
// Módulo para manejar una lista simplemente ligada ordenada
#include <stdio.h>
#include <string.h>
#include "Nodo.h"
#include "ListaSL.h"
void crear(Nodo *&lista) {
Nodo *nuevo;
while (1) {
nuevo = leeRegistro( );
if (nuevo == NULL) break;
insertar(lista, nuevo);
}
}
void mostrar(Nodo *lista) {
while(lista) {
printf("%10d %-30s\n", lista->codigo, lista->nombre);
lista = lista->siguiente;
}
}
void eliminar(Nodo *lista) {
Nodo *sale;
while(lista) {
sale = lista;
lista = lista->siguiente;
delete sale;
}
}
Nodo * leeRegistro(void) {
int cod;
char nomb[60];
Nodo *dato;
if (scanf("%d", &cod) == EOF) return NULL;
while(getchar( ) != '\n');
dato = new Nodo;
dato->codigo = cod;
gets(nomb);
dato->nombre = new char[strlen(nomb) + 1];
strcpy(dato->nombre, nomb);
return dato;
}
void insertar(Nodo *&lista, Nodo *nuevo) {
Nodo *p = lista, *ant = NULL;
while (p) {
if (p->codigo > nuevo->codigo) break;
ant = p;
p = p->siguiente;
}
nuevo->siguiente = p;
if (ant == NULL) lista = nuevo;
else ant->siguiente = nuevo;
}
Al ejecutar el programa, para datos como los que se muestra a continuación:
Obtendremos el siguiente resultado:
Árbol binario
// ***************************************************
// Programa que crea un árbol binario
// ***************************************************
#include <stdlib.h>
#include "Nodo.h"
#include "arbol.h"
int main(void) {
Nodo *arbol = NULL;
crear(arbol);
mostrarEnOrden(arbol);
eliminar(arbol);
return (EXIT_SUCCESS);
}
// Archivo de cabecera: Nodo.h
#ifndef NODO_H
#define NODO_H
// Nodo de un árbol binario
struct Nodo {
int codigo;
char *nombre;
Nodo *izquierda;
Nodo *derecha;
};
#endif /* NODO_H */
// Archivo de cabecera: Arbol.h
#ifndef ARBOL_H
#define ARBOL_H
#include "Nodo.h"
void crear(Nodo *&);
void mostrarEnOrden(Nodo *);
void eliminar(Nodo *);
Nodo *leeRegistro(void);
void insertar(Nodo *&, Nodo *);
#endif /* ARBOL_H */
// Módulo para manejar un árbol binario
#include <stdio.h>
#include <string.h>
#include "Nodo.h"
#include "arbol.h"
void crear(Nodo *∓arbol) {
Nodo *nuevo;
while (1) {
nuevo = leeRegistro( );
if (nuevo == NULL) break;
insertar(arbol, nuevo);
}
}
void mostrarEnOrden(Nodo *arbol) {
if (arbol) {
mostrarEnOrden(arbol->izquierda);
printf("%10d %-30s\n", arbol->codigo, arbol->nombre);
mostrarEnOrden(arbol->derecha);
}
}
void eliminar(Nodo *arbol) {
if (arbol) {
eliminar(arbol->izquierda);
eliminar(arbol->derecha);
delete arbol;
}
}
Nodo *leeRegistro(void) {
int cod;
char nomb[60];
Nodo *dato;
if (scanf("%d", &cod) == EOF) return NULL;
while(getchar( ) != '\n');
dato = new Nodo;
dato->codigo = cod;
gets(nomb);
dato->nombre = new char[strlen(nomb) + 1];
strcpy(dato->nombre, nomb);
dato->izquierda = NULL;
dato->derecha = NULL;
return dato;
}
void insertar(Nodo *&arbol, Nodo *nuevo) {
Nodo *p = arbol, *ant = NULL;
if (arbol == NULL) arbol = nuevo;
else {
while(p) {
ant = p;
if (p->codigo >> nuevo->codigo)
p = p->izquierda;
else
p = p->derecha;
}
if (ant->codigo > nuevo->codigo)
ant->izquierda = nuevo;
else
ant->derecha = nuevo;
}
}
Al ejecutar el programa se obtienen resultados que son similares a los que resultan del programa anterior referido a listas simplemente ligadas.
Listas ligadas genéricas empleando estructuras
En el capítulo sobre punteros a funciones se analizó la importancia de la creación de listas genéricas, estas lista tienen la particularidad de porder almacenar cualquier tipo de datos como se muestra en la figura siguiente:
La diferencia en este caso es que para definir los nodos de la lista usaremos estructuras en lugar de arreglos de punteros genéricos. La estructura que define el nodo tendrá dos campos, uno será un puntero genérico para colgar de ahí el tipo de datos que querramos procesar, el otro será un puntero que referencia a la misma estructura (puntero siguiente).
#include <stdlib.h>
#include "nodoLista.h"
#include "manipDato.h"
#include "lista.h"
int main(void) {
Nodo *lista = NULL;
// Este programa es similar al desarrollado en el capítulo de
// punteros a funciones pero en esta oportunidad se emplearán
// estructuras.
// Aquí, la función main no sabe qué tipo de datos va a almacenar
// en la lista ligada.
// Las funciones: creaLista, imprimeLista y eliminaLista están definidas
// en los módulos lista.h y lista.cpp
// Las funciones: compDato, impDato y elimdato están definidas en los
// módulos manipDato.h y manipDato.cpp
crearLista(lista, leeDato, compDato);
imprimeLista(lista, impDato);
eliminaLista(lista, elimDato);
return (EXIT_SUCCESS);
}
//****************************************************
//
// Módulo: Lista.h
//
// Funciones para crear, imprimir y eleiminar
// una lista genérica
//
//****************************************************
#ifndef _LISTA_H
#define _LISTA_H
#include "NodoLista.h"
void crearLista(Nodo *&, void * (*)(void), int (*)(void*, void*));
void imprimeLista(Nodo *, void (*)(void *));
void eliminaLista(Nodo *, void (*)(void *));
void insertLista(Nodo *&, void *, int (*)(void*, void*));
#endif /* _LISTA_H */
//****************************************************
//
// Módulo: ManipDato.h
//
// Funciones que permiten crear, imprimir,
// eliminar y comparar un dato específico según
// una plantilla dada
//
//****************************************************
#ifndef _MANIPDATO_H
#define _MANIPDATO_H
void * leeDato(void);
int compDato(void*, void*);
void impDato(void *);
void elimDato(void *);
#endif /* _MANIPDATO_H */
//****************************************************
//
// Módulo: Lista.cpp
//
// Módulo de implementación de las funciones
//
//****************************************************
#include <stdio.h>
#include "lista.h"
// Aquí, las funciones definidas son capaces de manipular una lista ligada
// sin saber qué tipo de dato va a manejar, todo el trabajo se realiza
// a través de punteros a funciones que son los que manipulan los datos
// especificos para cada situación.
void crearLista(Nodo *&lista, void * (*leeDato)(void),
int (*compDato)(void*, void*)) {
void *dato;
while (1) {
dato = leeDato(); // Lee un dato de cualquier tipo y devuelve
// un puntero genérico que apunta al dato
if (dato == NULL) break;
insertLista(lista, dato, compDato);
}
}
void insertLista(Nodo *&lista, void* dato,
int (*compDato)(void*, void*) ) {
Nodo *p, *nuevo, *ant;
nuevo = new Nodo;
nuevo->dato = dato; // Cuelga el dato sea cualfuera su naturaleza
p = lista;
ant = NULL;
while(p) {
if (compDato(p->dato, dato) > 0)break; // Compara dos datos,
// devuelve 0 si son iguales, un valor mayor que cereo si el
// primero es mayor que el segundo y un valor menor que cereo
// si el primero es menor que el segundo. El tipo de dato que
// comprar depende de la función a la que apunte el puntero
ant = p;
p = p->siguiente;
}
nuevo->siguiente = p;
if(ant == NULL) lista = nuevo;
else ant->siguiente = nuevo;
}
void imprimeLista(Nodo *lista, void (*impDato)(void *)) {
while (lista) {
impDato(lista->dato); // imprime un dato sea cualfuera su naturaleza
lista = lista->siguiente;
}
}
void eliminaLista(Nodo *lista, void (*elimDato)(void *)) {
Nodo *sale;
while (lista) {
elimDato(lista->dato); // elimina un dato sea cualfuera su naturaleza
sale = lista;
lista = lista->siguiente;
delete [ ]sale;
}
}
//****************************************************
//
// Módulo: ManipDato.cpp Versión 1
//
// Módulo de implementación de las funciones
// para manejar valores enteros
//
//****************************************************
#include <stdio.h>
void * leeDato(void) {
// lee un entero, si se intenta leer el fin del archivo
// devuelve NULL
int dato, *ptDato;
if (scanf("%d", &dato) == EOF) return NULL;
ptDato = new int;
*ptDato = dato;
return ptDato;
}
int compDato(void *v1, void *v2) {
int *n1, *n2;
// Compara los valores enteros apuntados por los punteros
n1 = (int*)v1;
n2 = (int*)v2;
return *n1 - *n2;
}
void impDato(void *v) {
int *n;
// Imprime los valores enteros apuntados por los punteros
n = (int *)v;
printf("%d\n", *n);
}
void elimDato(void *v) {
int *n;
// Elimina los valores enteros apuntados por los punteros
n = (int *)v;
delete n;
}
//****************************************************
//
// Módulo: ManipDato.cpp Versión 2
//
// Módulo de implementación de las funciones
// para manejar valores de punto flotante
//
//****************************************************
#include <stdio.h>
void * leeDato(void) {
// lee un entero, si se intenta leer el fin del archivo
// devuelve NULL
float dato, *ptDato;
if (scanf("%f", &dato) == EOF) return NULL;
ptDato = new float;
*ptDato = dato;
return ptDato;
}
int compDato(void *v1, void *v2) {
float *n1, *n2;
// Compara los valores enteros apuntados por los punteros
n1 = (float*)v1;
n2 = (float*)v2;
return (int)(*n1 - *n2);
}
void impDato(void *v) {
float *n;
// Imprime los valores enteros apuntados por los punteros
n = float *)v;
printf("%f\n", *n);
}
void elimDato(void *v) {
float *n;
// Elimina los valores enteros apuntados por los punteros
n = (float *)v;
delete n;
}
//****************************************************
//
// Módulo: ManipDato.cpp Versión 3
//
// Módulo de implementación de las funciones
// para manejar cadenas de caracteres
//
//****************************************************
#include <stdio.h>
#include <string.h>
void * leeDato(void) {
// lee una cadena, si se intenta leer una cadena
// vacía develve NULL
char *dato, aux[300];
int tam;
if (gets(aux) == NULL) return NULL;
tam = strlen(aux);
if (tam == 0) return NULL;
dato = new char[tam + 1];
strcpy(dato, aux);
return dato;
}
int compDato(void *v1, void *v2) {
char *n1, *n2;
// Compara las cadenas de caracteres apuntadas por los punteros
n1 = (char*)v1;
n2 = (char*)v2;
return strcmp(n1, n2);
}
void impDato(void *v) {
char *n;
// Imprime las cadenas apuntados por los punteros
n = (char *)v;
printf("%s\n", n);
}
void elimDato(void *v) {
char *n;
// Elimina las cadenas apuntadas por los punteros
n = (char *)v;
delete n;
}
//****************************************************
//
// Versión 4
//
// Módulos de implementación de la funciones
// para manejar registros de datos
//
//****************************************************
//****************************************************
//
// Módulo: DatoStruct.h
// Módulo que contiene el tipo de dato struct
//
//****************************************************
#ifndef DATOSTRUCT_H
#define DATOSTRUCT_H
struct TDato {
int codigo;
char *nombre;
char *fechIng; // <-- dd/mm/aaaa
float sueldo;
};
//****************************************************
//
// Módulo: ManipDato.cpp Versión 4
//
// Módulo de implementación de la funciones
// para manejar registros de datos
//
//****************************************************
#include <stdio.h>
#include <string.h>
#include "DatoStruct.h"
void * leeDato(void) {
TDato *dato;
char auxStr[300];
// lee una ficha que contiene el nombre, el código,
// la fecha de ingreso y el sueldo de empleados de una
// compañía, si se intenta leer el fin del archivo
// o un nombre vacío develve NULL
printf("Nombre: ");
if (gets(auxStr) == NULL) return NULL;
if (strlen(auxStr) == 0) return NULL;
dato = new TDato;
dato->nombre = new char[strlen(auxStr) + 1];
strcpy(dato->nombre, auxStr);
printf("Codigo: ");
scanf("%d", &dato->codigo);
while(getchar() != '\n');
printf("Fecha de ingreso: ");
dato->fechIng = new char[11]; //<-- dd/mm/aaaa
scanf("%s", dato->fechIng);
printf("Sueldo: ");
scanf("%f", &dato->sueldo);
while(getchar() != '\n');
return dato;
}
int compDato(void *v1, void *v2) {
TDato *d1, *d2;
d1 = (TDato *)v1;
d2 = (TDato *)v2;
return d1->codigo - d2->codigo;
}
void impDato(void *v) {
TDato *d;
d = (TDato *)v;
printf("Nombre: %s\n", d->nombre);
printf("Codigo: %d\n", d->codigo);
printf("Fecha de ingreso: %s\n", d->fechIng);
printf("Sueldo: %f\n", d->sueldo);
}
void elimDato(void *v) {
TDato *d;
d = (TDato *)v;
delete [ ]d->nombre;
delete [ ]d->fechIng;
delete [ ]d;
}
UNIONES:
Una unión es una estructura con características especiales. La diferencia radica en que cada uno de los campos definidos en ella comparten el mismo espacio de memoria. Esto quiere decir que si definimos una variable de este tipo, a pesar que tenga varios campos, sólo uno podrá almacenar datos a la vez.
El siguiente ejemplo mustra cómo se declara una UNION y a partir de allí su aplicación:
//****************************************************
//
// Declaración de una "union"
//
// Módulo: union.h
//
//****************************************************
#ifndef UNION_H
#define UNION_H
union TUnion {
int valor;
unsigned char parte[4];
};
#endif /* UNION_H */
La variable dato que se definirá en el siguiente programa se almacenará en la pila del proceso de la siguiente manera:
Como se puede apreciar los campos pomparten el mismo espacio de memoria. A continuación se presenta la aplicación que maneje este tipo de daro:
//****************************************************
//
// Implementación de una aplicación"
//
// Módulo: main.h
//
//****************************************************
#include <stdlib.h>
#include <stdio.h>
#include &quet;union.h"
int main(void) {
TUnion dato;
// Manejamos el campo de tipo entero:
printf("Campo definido como un valor entero:\n");
dato.valor = 1061659361;
printf("Valor decimal: %d\n", dato.valor);
printf("Valor exadecimal: %X\n", dato.valor);
// Manejamos el arrglo con el mismo valor asignado:
printf("\nCampo definido como un arreglo:\n");
printf("Valores decimales:\n");
for(int i = 0; i < 4; i++)
printf("Parte[%d]: %5d\n", i, dato.parte[i]);
printf("\nValores exadecimales:\n");
for(int i = 3; i >= 0; i--)
printf("Parte[%d]: %5X\n", i, dato.parte[i]);
return (EXIT_SUCCESS);
}
La salida de este programa muestra claramente cómo luego de asignar una cantidad dada al campo valor (int), se pueden manejar cada uno de sus bytes por medio del campo parte (unsigned char[4]) sin tener que emplear operadores de bits.
![]() |
![]() ![]() |