Una estructura anónima es la que carece de identificador de tipo de estructura y de declaración de variables 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?
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.
Cuando se aplica el operador sizeof a una estructura, el tamaño obtenido no siempre coincide con el tamaño de la suma de sus campos. 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; cin.get(); 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 4, y el compilador intenta alinear las variables con esas posiciones.
En el caso de variables "int" es fácil, ya que ocupan 4 bytes, pero con las variables "char" no, ya que sólo ocupan 1.
Cuando se accede a datos de menos de 4 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 4, dejando 3 posiciones libres después del campo "a". Lo mismo pasa con el campo "b".
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
x |
a |
vací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".
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
x |
y |
a |
b |
vacío |
Existe otro tipo de estructuras que consiste en empaquetar los campos de la estructura en el interior de enteros, usando bloques o conjuntos de bits para cada campo.
Por ejemplo, una variable 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 una variable int de 16 bits podremos almacenar 16 bits, etc.
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_variables>];
Hay algunas limitaciones, por ejemplo, un campo de bits no puede ocupar dos variables distintas, 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:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
bit7 |
bit6 |
bit5 |
bit4 |
bit3 |
bit2 |
bit1 |
bit0 |
En el segundo caso dividimos un valor entero sin signo de dieciséis bits en cinco campos de distintas longitudes:
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
campo5 |
campo4 |
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 | ||||||||||||||
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
campo2 |
campo1 |
En este ejemplo vemos que como no es posible empaquetar el campo2 dentro del mismo char que el campo1, se añade un segundo valor char, y se dejan sin usar 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 en general igual que cualquier otro de los campos de una estructura. Se les puede asignar valores (dentro del rango que admitan), pueden usarse en condicionales, imprimirse, etc.
#include <iostream> #include <cstdlib> using namesoace 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; cin.get(); return 0; }
No es normal 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 los bits de datos, 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.
struct.
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 ==.
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.
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.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.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.int Diferencia(fecha,
fecha);
, y debe devolver la diferencia en días entre las dos fechas
suministradas. © Septiembre de 2000 Salvador Pozo, salvador@conclase.net