ESTRUCTURAS Y UNIONES

Descargar como docx, pdf o txt
Descargar como docx, pdf o txt
Está en la página 1de 35

0

ESTR
UCTURAS 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.

Librería stdarg.h

Permite la definición de funciones con una lista de


argumentos variables, es decir una función en donde no se
conocen la cantidad ni el tipo de argumento que recibirán.

Por ejemplo:

Se desea sumar varios datos diferentes, el número de ellos


varía cada vez que se invoca. Así:

sumar (3, 27, 30, 50);


O se define una función que imprima los agumentos que se le
envían.
int a, float b;
...
a=3;
b=8.75;
imprime ("a=%d b=%f\n", a, b);
int mensaje[] = "Hola";
imprime ("Mensaje=%s\n", mensaje);

Prototipos:

int sumar (int NumDat, ...);


void imprime (char *formato, ...);

... indica que el número y tipo de Argumentos puede variar.

Para definir este tipo de funciones, se debe utilizar la


librería stdarg.h. Esta librería define un tipo de dato
llamado va_list y tres macros: va_start, va_arg y va_end.

va_list: Se emplea para definir una variable que apunta a la


lista de argumentos.

va_start: Tiene la siguiente forma:


va_start (PuntArg, UltArg);

Esta macro inicializa la variable PuntArg (de tipo va_list) de


modo que apunte a la lista de argumentos variables. UltArg es
el último argumentos con nombre de la función antes de los
puntos (...).

Es obligatorio que exista por lo menos un argumento con


nombre (int NumArg en Suma, char *formato en Imprime).
Esta variable es usada como punto de referencia para ubicar
la lista de argumentos.

va_arg: Tiene la siguiente forma:


va_arg (PuntArg, Type);

Esta macro se expande en una expresión que devuelve un


valor del tipo indicado por el argumento type, este resultado
es el valor del argumento apuntado por PuntArg.
Finalmente PuntArg apuntará al siguiente Argumento.

va_end: Cuya forma es:


va_end (PuntArg);

Ayuda a la función creada a tener un retorno normal,


realizando una labor de "limpieza" (descargando los valores
de la pila por ejemplo). Debe ser utilizada luego de haber
utilizado todos los argumentos.

Luego:
#include <iostream.h>
#include <stdarg.h>

int sum(int NumArg, ...);

void main(void)
{ cout << "Total = " << sum(4, 1,2,3,4) << endl;
cout << "Otro total = " << sum(3, 27,34,55) << endl;
}

int sum(int NumArg, ...)


{ int total = 0,i;
va_list PuntArg;
int arg;

va_start(PuntArg, &NumArg);

for (i=0;i<NumArg; i++)


{ arg = va_arg(PuntArg,int);
total += arg;
}

va_end(PuntArg);
return total;
}

#include <iostream.h>
#include <stdio.h>
#include <stdarg.h>

void imprime(char *formato, ...);


void main (void)
{ int a=3;
int b=8;
char mens[]="Hola";

imprime("a= %d b= %d", a,b);


cout << endl;
imprime("Mensaje %s", mens);
cout << endl;
}
void imprime(char *formato, ...)
{ va_list PArg;
char *c, *Texto;
int Entero;

va_start(PArg,formato);
for (c=formato; *c!='\0'; c++)
{ if (*c!='%')
{ putchar(*c);
continue;
}
switch(*++c)
{ case 'd' : Entero = va_arg(PArg, int);
cout << Entero;
break;
case 's' : Texto = va_arg(PArg, char*);
cout << Texto;
break;
default : putchar(*c);
}
va_end(PArg);
}
}

Sin usar la librería stdarg:


#include <iostream.h>

int sum(int NumArg, ...);

void main(void)
{ cout << "Total = " << sum(4, 1,2,3,4) << endl;
cout << "Otro total = " << sum(3, 27,34,55) << endl;
}

int sum(int NumArg, ...)


{ int total = 0,i;
int * PuntArg;

PuntArg = &NumArg;

for (i=0;i<NumArg; i++)


{ PuntArg++;
total += *PuntArg;
}

return total;
}

#include <iostream.h>
#include <stdio.h>
#include <conio.h>

void imprime(char *formato, ...);

void main (void)


{ int a=3;
int b=8;
char mens[]="Hola";

clrscr();
imprime("a= %d b= %d", a,b);
imprime("Mensaje %s", mens);
}

void imprime(char *formato, ...)


{ void *PArg;
char *c, *Texto;
int Entero;
double Real;

PArg = &formato;
((char **)PArg)++;

for (c=formato; *c!='\0'; c++)


{ if (*c!='%')
{ putchar(*c);
continue;
}
switch(*++c)
{ case 'd' : Entero = *((int *)PArg)++;
cout << Entero;
break;
case 's' : Texto = *((char **)PArg)++;
cout << Texto;
break;
default : putchar(*c);
}
}
}

También podría gustarte