Estructuras anónimas
Antes dijimos, al hablar sobre la sintaxis de las declaraciones de estructuras, que debe aparecer o bien el identificador de estructura, o bien declararse algún objeto de ese tipo en la declaración. Bien, eso no es del todo cierto. Hay situaciones donde se pueden omitir ambos identificadores.
Una estructura anónima es la que carece de identificador de tipo de estructura y de declaración de objetos del tipo de estructura.
Por ejemplo, veamos esta declaración:
struct stAnonima {
struct {
int x;
int y;
};
int z;
}; |
Para acceder a los campos x o y se usa la misma forma que para el campo z:
stAnonima Anonima;
Anonima.x = 0;
Anonima.y = 0;
Anonima.z = 0; |
Pero, ¿cual es la utilidad de esto?
Pues, la verdad, no mucha, al menos cuando se usa con estructuras. En el capítulo dedicado a las uniones veremos que sí puede resultar muy útil.
El método usado para declarar la estructura dentro de la estructura es la forma anónima, como verás no tiene identificador de tipo de estructura ni de campo. El único lugar donde es legal el uso de estructuras anónimas es en el interior de estructuras y uniones.
Operador sizeof con estructuras
Podemos usar el operador sizeof para calcular el espacio de memoria necesario para almacenar una estructura.
Sería lógico suponer que sumando el tamaño de cada elemento de una estructura, se podría calcular el tamaño de la estructura completa, pero no siempre es así. Por ejemplo:
#include <iostream>
using namespace std;
struct A {
int x;
char a;
int y;
char b;
};
struct B {
int x;
int y;
char a;
char b;
};
int main()
{
cout << "Tamaño de int: "
<< sizeof(int) << endl;
cout << "Tamaño de char: "
<< sizeof(char) << endl;
cout << "Tamaño de estructura A: "
<< sizeof(A) << endl;
cout << "Tamaño de estructura B: "
<< sizeof(B) << endl;
return 0;
} |
El resultado, usando Dev-C++, es el siguiente:
Tamaño de int: 4
Tamaño de char: 1
Tamaño de estructura A: 16
Tamaño de estructura B: 12
|
Si hacemos las cuentas, en ambos casos el tamaño de la estructura debería ser el mismo, es decir, 4+4+1+1=10 bytes. Sin embargo en el caso de la estructura A el tamaño es 16 y en el de la estructura B es 12, ¿por qué?
La explicación es algo denominado alineación de bytes (byte-aling). Para mejorar el rendimiento del procesador no se accede a todas las posiciones de memoria. En el caso de microprocesadores de 32 bits (4 bytes), es mejor si sólo se accede a posiciones de memoria múltiplos de cuatro, de modo que el compilador intenta alinear los objetos con esas posiciones.
En el caso de objetos int es fácil, ya que ocupan cuatro bytes, pero con los objetos char no, ya que sólo ocupan uno.
Cuando se accede a datos de menos de cuatro bytes la alineación no es tan importante. El rendimiento se ve afectado sobre todo cuando hay que leer datos de cuatro bytes que no estén alineados.
En el caso de la estructura A hemos intercalado campos int con char, de modo que el campo int y, se alinea a la siguiente posición múltiplo de cuatro, dejando tres posiciones libres después del campo a. Lo mismo pasa con el campo b.
0123456789101112131415
x avacío y b vacío
En el caso de la estructura B hemos agrupado los campos de tipo char al final de la estructura, de modo que se aprovecha mejor el espacio, y sólo se desperdician los dos bytes sobrantes después de b.
01234567891011
x y a b vacío
Campos de bits
Existe otro tipo de estructuras que consiste en empaquetar cada uno de los campos en el interior de valores enteros, usando bloques o subconjuntos de bits para cada campo.
Por ejemplo, un objeto char contiene ocho bits, de modo que dentro de ella podremos almacenar ocho campos de un bit, o cuatro de dos bits, o dos de tres y uno de dos, etc. En un objeto int de 16 bits podremos almacenar 16 campos de un bit, etc.
Para definir campos de bits debemos usar siempre valores de enteros sin signo, ya que el signo se almacena en un bit del entero, el de mayor peso, y puede falsear los datos almacenados en la estructura.
La sintaxis es:
struct [<nombre de la estructura>] {
unsigned <tipo_entero> <identificador>:<núm_de_bits>;
.
} [<lista_objetos>]; |
Existen algunas limitaciones, por ejemplo, un campo de bits no puede crearse a orcajadas entre dos objetos distintos, todos sus bits tienen que estar en el mismo valor entero.
Veamos algunos ejemplos:
struct mapaBits {
unsigned char bit0:1;
unsigned char bit1:1;
unsigned char bit2:1;
unsigned char bit3:1;
unsigned char bit4:1;
unsigned char bit5:1;
unsigned char bit6:1;
unsigned char bit7:1;
};
struct mapaBits2 {
unsigned short int campo1:3;
unsigned short int campo2:4;
unsigned short int campo3:2;
unsigned short int campo4:1;
unsigned short int campo5:6;
};
struct mapaBits3 {
unsigned char campo1:5;
unsigned char campo2:5;
}; |
En el primer caso se divide un valor char sin signo en ocho campos de un bit cada uno:
76543210
bit7bit6bit5bit4bit3bit2bit1bit0
En el segundo caso dividimos un valor entero sin signo de dieciséis bits en cinco campos de distintas longitudes:
1514131211109876543210
campo5 c4 campo3 campo2 campo1
Los valores del campo5 estarán limitados entre 0 y 63, que son los números que se pueden codificar con seis bits. Del mismo modo, el campo4 sólo puede valer 0 ó 1, etc.
unsigned char unsigned char
7654321076543210
campo2 campo1
En este ejemplo vemos que como no es posible empaquetar el campo2 dentro del mismo char que el campo1, de modo que se añade un segundo valor char, y se dejan sin usar todos los bits sobrantes.
También es posible combinar campos de bits con campos normales, por ejemplo:
struct mapaBits2 {
int numero;
unsigned short int campo1:3;
unsigned short int campo2:4;
unsigned short int campo3:2;
unsigned short int campo4:1;
unsigned short int campo5:6;
float n;
}; |
Los campos de bits se tratan, por norma general, igual que cualquier otro de los campos de una estructura. Se les puede asignar valores (dentro del rango que itan por su tamaño), pueden usarse expresiones, imprimirse, etc.
#include <iostream>
#include <cstdlib>
using namespace std;
struct mapaBits2 {
unsigned short int campo1:3;
unsigned short int campo2:4;
unsigned short int campo3:2;
unsigned short int campo4:1;
unsigned short int campo5:6;
};
int main()
{
mapaBits2 x;
x.campo2 = 12;
x.campo4 = 1;
cout << x.campo2 << endl;
cout << x.campo4 << endl;
return 0;
} |
Lo que no es posible es leer valores de un campo de bits mediante cin. Para poder leer valores desde el teclado se debe usar una variable entera auxiliar, y posteriormente asignarla al campo de bits.
No es frecuente usar estas estructuras en programas, salvo cuando se relacionan con ciertos dispositivos físicos. Por ejemplo, para configurar un puerto serie en MS-DOS se usa una estructura empaquetada en un unsigned char, que indica el número de bits de datos, de bits de parada, la paridad, etc, es decir, todos los parámetros del puerto. En general, para programas que no requieran estas estructuras, es mejor usar estructuras normales, ya que son mucho más rápidas.
Otro motivo que puede decidirnos por estas estructuras es el ahorro de espacio, ya sea en disco o en memoria. Si conocemos los límites de los campos que queremos almacenar, y podemos empaquetarlos en estructuras de mapas de bits podemos ahorrar mucho espacio.
Palabras reservadas usadas en este capítulo
struct.
Problemas
1-Escribir un programa que almacene en un array los nombres y números de teléfono de 10 personas. El programa debe leer los datos introducidos por el y guardarlos en memoria (en el array). Después debe ser capaz de buscar el nombre correspondiente a un número de teléfono y el teléfono correspondiente a una persona. Ambas opciones deben se accesibles a través de un menú, así como la opción de salir del programa. El menú debe tener esta forma, más o menos:
a) Buscar por nombre
b) Buscar por número de teléfono
c) Salir
Pulsa una opción:
Nota: No olvides que para comparar cadenas se debe usar una función, no el operador ==. |
2-Para almacenar fechas podemos crear una estructura con tres campos: ano, mes y dia. Los días pueden tomar valores entre 1 y 31, los meses entre 1 y 12 y los años, dependiendo de la aplicación, pueden requerir distintos rangos de valores. Para este ejemplo consideraremos suficientes 128 años, entre 1960 y 2087. En ese caso el año se obtiene sumando 1960 al valor de ano. El año 2003 se almacena como 43.
Usando estructuras, y ajustando los tipos de los campos, necesitamos un char para dia, un char para mes y otro para ano.
Diseñar una estructura análoga, llamada fecha, pero usando campos de bits. Usar sólo un entero corto sin signo (unsigned short), es decir, un entero de 16 bits. Los nombres de los campos serán: dia, mes y año.
3-Basándose en la estructura de bits del ejercicio anterior, escribir una función para mostrar fechas: void Mostrar(fecha);. El formato debe ser: "dd de mmmmmm de aaaa", donde dd es el día, mmmmmm el mes con letras, y aaaa el año. Usar un array para almacenar los nombres de los meses.
4-Basándose en la estructura de bits del ejercicio anterior, escribir una función bool ValidarFecha(fecha);, que verifique si la fecha entregada como parámetro es válida. El mes tiene que estar en el rango de 1 a 12, dependiendo del mes y del año, el día debe estar entre 1 y 28, 29, 30 ó 31. El año siempre será válido, ya que debe estar en el rango de 0 a 127.
Para validar los días usaremos un array int DiasMes[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};. Para el caso de que el mes sea febrero, crearemos otra función para calcular si un año es o no bisiesto: bool Bisiesto(int); Los años bisiestos son los divisibles entre 4, al menos en el rango de 1960 a 2087 se cumple.
Nota: los años bisiestos son cada cuatro años, pero no cada 100, aunque sí cada 400. Por ejemplo, el año 2000, es múltiplo de 4, por lo tanto debería haber sido bisiesto, pero también es múltiplo de 100, por lo tanto no debería serlo; aunque, como también es múltiplo de 400, finalmente lo fue. |
5-Seguimos con el tema de las fechas. Ahora escribir dos funciones más. La primera debe responder a este prototipo: int CompararFechas(fecha, fecha);. Debe comparar las dos fechas suministradas y devolver 1 si la primera es mayor, -1 si la segunda es mayor y 0 si son iguales.
La otra función responderá a este prototipo: int Diferencia(fecha, fecha);, y debe devolver la diferencia en días entre las dos fechas suministradas.
Comentarios