ESTRUCTURAS Y UNIONES
ESTRUCTURAS Y UNIONES
ESTRUCTURAS Y UNIONES
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"
// Empleando C++
#include "TPersona.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "TPersona.h"
int main(void) {
TPersona empleado, trabajador;
char aux[100];
return (EXIT_SUCCESS);
}
La salida de este programa será similar a la 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(&qut;%lf", &empleado.sueldo); while(getchar()!='\n');
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( );
return 0;
}
La salida de este programa será similar a la siguiente:
#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:
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:
// **********************************************************
// 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);
}
#ifndef NODO_H
#define NODO_H
//Nodo de una lista simplemente ligada (LSL)
struct Nodo {
int codigo;
char *nombre;
Nodo *siguiente;
};
#endif /* NODO_H */
#ifndef LISTASL_H
#define LISTASL_H
#include "Nodo.h"
#include <stdio.h>
#include <string.h>
#include "Nodo.h"
#include "ListaSL.h"
Nodo * leeRegistro(void) {
int cod;
char nomb[60];
Nodo *dato;
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 */
#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 */
#include <stdio.h>
#include <string.h>
#include "Nodo.h"
#include "arbol.h"
Nodo *leeRegistro(void) {
int cod;
char nomb[60];
Nodo *dato;
#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.
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.
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);
}
}
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;
}
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;
//****************************************************
//
// 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;
//****************************************************
//
// 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;
return dato;
}
//****************************************************
//
// 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];
printf("Codigo: ");
scanf("%d", &dato->codigo);
while(getchar() != '\n');
return dato;
}
d1 = (TDato *)v1;
d2 = (TDato *)v2;
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:
#include <stdlib.h>
#include <stdio.h>
#include &quet;union.h"
int main(void) {
TUnion dato;
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
Por ejemplo:
Prototipos:
Luego:
#include <iostream.h>
#include <stdarg.h>
void main(void)
{ cout << "Total = " << sum(4, 1,2,3,4) << endl;
cout << "Otro total = " << sum(3, 27,34,55) << endl;
}
va_start(PuntArg, &NumArg);
va_end(PuntArg);
return total;
}
#include <iostream.h>
#include <stdio.h>
#include <stdarg.h>
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);
}
}
void main(void)
{ cout << "Total = " << sum(4, 1,2,3,4) << endl;
cout << "Otro total = " << sum(3, 27,34,55) << endl;
}
PuntArg = &NumArg;
return total;
}
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
clrscr();
imprime("a= %d b= %d", a,b);
imprime("Mensaje %s", mens);
}
PArg = &formato;
((char **)PArg)++;