Estructuras, uniones y tipos de datos definidos por el usuario.


Estructuras.


Las estructuras ( struct ) son agrupaciones de una o más variables de tipos posiblemente diferentes, agrupadas bajo un mismo nombre. Esto permite un manejo más cómodo de la información cuando ésta está relacionada. Las “struct” son estructuras de datos similares a los registros ( record ) de Pascal. La forma general de definición es:


struct tipo_estructura {

tipo miembro_1;

tipo miembro_2;

.

tipo miembro_n;

} Lista_variables_estructura;



donde tipo_estructura ( que será el nombre de la estructura ) ó Lista_variables_estructura ( que es una lista de las variables que serán de este tipo de estructura ) pueden omitirse pero no ambos. Las estructuras ayudan a agrupar información relacionada como los datos de una cédula de identidad, las coordenadas de un punto, etc.


Ejemplos de declaración de estructuras


struct datos {

char nombre[20];

char direccion[20];

long int NumCedula;

char sexo;

};


/* Veamos ahora como declaramos una variable de tipo “struct datos”. Aquí tenemos a “a” como una estructura de este tipo y “b” como un array de 5 estructuras de este tipo*/


struct datos a,b[5];



Las estructuras también pueden estar anidadas. Esto quiere decir que un elemento de una estructura puede ser a su vez otra estructura.


Veamos un ejemplo:


struct fecha {

int dia;

int mes;

int anio;

};


struct persona {

char nombre[20];

struct fecha nacimiento;

};


struct persona p;


Aquí vemos que la estructura persona contiene un elemento que es del tipo de estructura fecha.


OJO: Como vimos en la definición, también pueden declararse las variables que serán de un tipo de estructura desde el momento de la declaración haciendo algo como:


struct persona {

char nombre[20];

struct fecha nacimiento;

} p, q ; // Esto declara 'p' y 'q' de tipo “struct persona"


Cómo se referencian estas variables?. Muy bien, los elementos individuales de una estructura se referencian utilizando el operador punto ( . ) entre el nombre de la variable de tipo estructura y el nombre del miembro de la estructura. A los elementos de una estructura se les denomina miembros.


Continuando con las estructuras de los ejemplos anteriores, se pueden tener las siguientes referencias a miembros:


a.nombre // Referencia la variable nombre de la estructura datos llamada “a”

a.dirección // Referencia la variable dirección de la estructura datos llamada “a”

b[2].NumCedula // Referencia la variable NumCedula del segundo elemento del arreglo

// de tipo struct datos llamado “b”

p.nombre

p.nacimiento.dia // Estos casos referencian un elemento de una “struct fecha” dentro

p.nacimiento.mes // de la “struct persona”. Analícenlo.



Funciones y estructuras.


1 .- Paso por valor de miembros de una estructura a una función.


Se realiza como si fueran variables simples. Por ejemplo, para pasar por valor el miembro “a.NumCedula”:


void funcion f1(int x); /*declaración de la función prototipo*/


f1(a.NumCedula); /* llamada a la función */


void f1(int x) { /* definición de la función */

...

}


2.- Paso por dirección ( lo mismo que por referencia ) de miembros de una estructura a una función.

Se realiza como si fueran variables simples. Por ejemplo, para pasar por referencia el miembro “a.NumCedula”:


void funcion f1(int *) /*declaración de la función prototipo*/


f1(&a.codigo); /* llamada a la función */


void f1(int *x) { /* definición de la función */

}


Hay que tener en cuenta que si lo que se pasa a una función es un miembro de una estructura que sea un arreglo, éste siempre se pasa por dirección ( ya que el nombre del arreglo es la dirección del primer elemento del mismo ).


3.- Paso por valor de estructuras completas a funciones.


En el siguiente ejemplo, suma es una función que recibe dos estructuras pasadas por valor y a su vez devuelve una estructura.


struct vector {

int x,y,z;

};


struct vector (struct vector v1, struct vector v2);


void main() {

struct vector v1,v2,v3;

...

v3=suma(v1,v2);

...

}


struct vector suma(struct vector v1, struct vector v2) {

v1.x+=v2.x;

v1.y+=v2.y;

v1.z+=v2.z;

return (v1);

}



4.- Paso por referencia de estructuras completas a funciones.


Cuando las estructuras son muy grandes es más eficiente pasarlas por dirección. En ese caso se utilizan punteros a estructuras para realizar la comunicación. Para acceder a los miembros de la estructura debe utilizarse la combinación de los operadores * y punto. Sin embargo el operador punto tiene más precedencia que * siendo necesario el uso de paréntesis para asegurar que se aplique primero * y después punto. También puede utilizarse el operador -> para acceder a los miembros de una estructura referenciada por un puntero y de hecho este es la forma más usada en la actualidad.


#include <stdio.h>


struct pareja {

int a,b;

};


void f1(struct pareja *q);


void main() {

struct pareja p = { 13, 17 } /* inicialización de los miembros*/

f1(&p);

printf("a:%d y b:%d\n", p.a, p.b); /* a: 14 y b:18 */

}


void f1(struct pareja *q) {

q->a++; /* equivalente a (*q).a++ pero más usado */

q->b++;

return; // Este return no retorna nada ya que la función es void.

// En este caso no era necesario su uso pero recuerden que return también

// se usa para finalizar una función inmediatamente.

}


En este ejemplo también se observó como se inicializan los elementos de una estructura: con {ele1, ele2,...,elen}; “igual que en los arreglos”. El primer elemento inicializa la primera variable de la estructura, el segundo elemento a la segunda variable y así sucesivamente. Así, si usamos la estructura datos que mencionamos al principio, una forma de inicializarla sería algo como:


struct datos a = {“Tony”, “Maracay”, 8765432, 'M'};


COMENTARIO ADICIONAL: Existe una forma en C de acceder a los bits de una variable de forma independiente sin tener que hacer operaciones a nivel de éstos. Esta forma de acceso está ligada al uso de estructuras. Si lo necesita, puede buscar más información acerca de este tema o preguntarme.



Uniones


Las uniones son otro tipo de datos que pueden ser usados en C y aunque tienen una sintaxis similar a las estructuras se diferencian de éstas en que sus miembros comparten almacenamiento. Esto quiere decir que una variable unión define a un conjunto de valores alternos que se almacenan en una porción compartida de memoria. Es una versión C de los registros variantes de otros lenguajes como Pascal.


El compilador asigna una porción de almacenamiento que pueda acomodar al más grande de los miembros especificados. La notación para acceder a un miembro de la unión es idéntica a la que se emplea para acceder a un miembro de una estructura. Un ejemplo de unión es el siguiente:


union simple {

char ch;

int i;

};

union simple a;


// podemos acceder a las variables de la unión haciendo:

a.ch = ´a´;

...

a.i = 3;


Como ya comentamos, las uniones comparten el almacenamiento, o sea, que sólo se reserva espacio en memoria como para almacenar la más grande de las variables que en este caso es un int por lo que son dos bytes. Dentro de esos dos bytes se almacenarán las variables pero, como usted ya habrá analizado, sólo puede mantenerse el valor de una de ellas. O sea que en el ejemplo anterior, al usar a.i, a.ch se pierde. Veamos otro ejemplo:


union ejemplo {

long int cedula;

char nombre[10];

char Nacimiento[12];

};


Esta variable sólo ocupará 12 bytes que es el mayor dato que puede contener y mantendrá en cada momento sólo a una de ellas.


OJO: Es muy importante que sepan que una unión sólo puede ser inicializada con un valor del tipo de su primer miembro.


union ejemplo a={8765432};



Uso de sizeof para asegurar la portabilidad.


Al principio del curso, y también en la primera parte de punteros, comentamos el operador sizeof(). El uso de este operador es de gran importancia a la hora de calcular el tamaño real de las variables, el cual puede variar de maquina en maquina. Por ejemplo, en MSDOS:


char c;

sizeof(int); //Retorna 2, ya que int ocupa dos bytes

sizeof(c); // retorna 1 ya que c es de tipo char y ocupa un byte


mientras que en Linux, aunque los char son del mismo tamaño, sizeof(int) retorna 4.


En todo caso, hice nuevamente referencia al operador sizeof debido a que uno de sus mayores usos es en el cálculo de tamaños de variables definidas por el usuario tales como estructuras y uniones. Supongamos que se necesita guardar datos en un arreglo que será creado dinámicamente con malloc y que este arreglo contendrá, por ejemplo, 27 elementos de un tipo de estructura que ha sido creada por nosotros. Supongamos también que la estructura es la siguiente:


struct tamaño {

char ch;

int i;

long int li;

float f;

double d;

long double ld;

char cad[10];

int mat[10][15];

} me;


Para poder reservar memoria con malloc de forma directa deberíamos primero calcular cuantos bytes ocupa esta estructura que en MSDOS sería: ch = 1, i = 2, li = 4, f = 4, d = 8, ld = 10, cad = 10 y mat = 2*10*15 = 300. Sumado todo da 339 bytes por estructura que luego multiplicamos por 27 para obtener el espacio total requerido = 9153 bytes. Así, haríamos:


struct tamaño *pe;

pe = (struct tamaño *)malloc(9153);


y tendríamos que hacer los distintos cálculos para las diferentes plataformas. Usando sizeof sólo haríamos:


pe = (struct tamaño *)malloc(27*sizeof(me));



Tipos de datos definidos por el usuario.


C soporta la creación de nuevos nombres de tipos de datos. Esto se realiza utilizando la palabra reservada “typedef:”


typedef tipo nombre


Ejemplos:


Con tipos simples:


typedef int ENTERO

typedef float REAL


ENTERO a,b; // Define a y b como de tipo ENTERO, o sea, int.

REAL c;


Con tipos estructurados:


typedef struct{

int dia;

int mes;

int anio;

} FECHA;


FECHA a;


ó incluso si la estructura ya está creada:


struct test {

int i;

char ch;

char cad[10];

}


podemos usar:


typedef struct test MiNuevoTipo;

...

MiNuevoTipo a;



Enumeraciones ( enum )


Una enumeración, es un conjunto de constantes enteras con nombre, que especifica todos los valores válidos que una variable de este tipo puede tener. Estos valores son listados explícitamente por el programador. Las constantes representan los valores que pueden ser asignados a las variables declaradas del tipo del enum. Su forma de declaración es:


enum nombre { val1,val2,...,valn }; donde valn son identificadores de constantes.


ó


enum etiqueta { lista_de_enumeraciones } lista_de_variables;


En el primer caso, val1 es un identificador que tendrá un valor de cero, val2 un valor de 1 y así sucesivamente pero se pueden asignar otros valores indicándolo en la enumeración:


enum nombre {val1=0, val2=10, val3=13,... };


Puede suceder incluso que más de una constante de enumeración tenga el mismo valor entero.


enum color{rojo=-1, azul, amarillo, verde, negro=0};


Las variables de enumeración pueden utilizarse como enteros; asignarles valores, compararlas, etc.


Vemos un ejemplo:


enum moneda { medio, real, bolivar };

...

enum moneda dinero;


Entonces podríamos decir:


dinero = medio;

if (dinero == medio ) printf(“Es un medio\n”);

La clave para entender las enumeraciones es que cada uno de los símbolos corresponde a un valor entero. De esta forma, puede usarse en cualquier expresión entera. Por ejemplo:


printf(“”El valor de un medio es %i”, medio);


Perfectamente válido.


Como ya comentamos, a menos que se inicialice de otro modo, el valor del primer símbolo será cero, el del segundo 1 y así sucesivamente. Por lo tanto en el ejemplo anterior:


printf(“%i %i”, medio, bolivar);


imprimirá 0 2 en la pantalla.


También dijimos que se pueden especificar valores en la inicialización:


enum moneda { medio=25, real=50, bolivar=100 };


ó, por ejemplo, en:


enum color { amarillo, azul=10, verde, rojo, negro=100 };


tendremos que, según la inicialización: amarillo=0, azul=10, verde=11, rojo=12 y negro =100.


No se puede escribir más que el valor entero de la variable enum lo que quiere decir que no se puede escribir su nombre. Recuerde, NO son cadenas. Son sólo nombres de enteros.


dinero = real;

printf(“%s”, dinero); // Esto está mal.


Lo que si se puede hacer es pedir un entero y asignárselo a una enumeración.


Los tipos enumerados NO aportan capacidades nuevas al lenguaje, pero aumentan la claridad de algunos programas. Veamos un caso típico de su uso ( en conjunto con arreglos de cadenas ).


enum colores {negro, azul, verde, cyan, rojo, magenta, marron, amarillo=14, blanco };


char *nombreColores[]={

negro",

"azul",

"verde",

"cyan",

"rojo",

"magenta",

"marron",

amarillo”,

blanco”

};


/* Aunque por supuesto en este caso el índice del amarillo y blanco no corresponden con los valores en enum lo cual presentará errores en el siguiente código. Serán comentados */


printf("Introduzca un numero de color: ");

scanf("%i",&i);

color = i;

printf("El numero del color es %i\n",color);

switch(color) {

case negro: printf("negro = %s\n",nombreColores[negro]);

printf("%s\n",nombreColores[color]); // Análogo a nombreColores[negro]

break;

case azul: printf("azul = %s\n",nombreColores[azul]);

printf("%s\n",nombreColores[color]);

break;

case verde: printf("verde = %s\n",nombreColores[verde]);

printf("%s\n",nombreColores[color]);

break;

case cyan: printf("cyan = %s\n",nombreColores[cyan]);

printf("%s\n",nombreColores[color]);

break;

case rojo: printf("rojo = %s\n",nombreColores[rojo]);

printf("%s\n",nombreColores[color]);

break;

case magenta: printf("magenta = %s\n",nombreColores[magenta]);

printf("%s\n",nombreColores[color]);

break;

case marron: printf("marron = %s\n",nombreColores[marron]);

printf("%s\n",nombreColores[color]);

break;


/* Para los siguientes casos, los valores de “color” no están definidos dentro de los permitidos por el arreglo de cadenas ( aunque si por el enum ). Por tal hecho, al tratar de imprimir la cadena del arreglo correspondiente, imprimirá lo que encuentre en ese punto de memoria hasta conseguir el caracter nulo */


case amarillo: printf("amarillo = %s\n",nombreColores[amarillo]);

printf("%s\n",nombreColores[color]);

break;

case blanco: printf("blanco = %s\n",nombreColores[blanco]);

printf("%s\n",nombreColores[color]);

break;

default: printf("Ese color no lo conozco\n");

}