Curso de C++ v2.0
Consultas, lista de correo 'C++ Con Clase' 'C++ Con Clase' página de entrada Librerías estándar C Tabla de contenido Contactar con Webmaster
*Introducción
*1 Toma de contacto
*2 Variables I
*3 Funciones I: Declaración y definición
*4 Operadores I
*5 Sentencias
*6 Declaración de variables
*7 Normas para la notación
*8 Cadenas de caracteres
*9 Conversión de tipos
*10 Variables II: Arrays
*11 Variables III: Estructuras
 .%20src=" Funciones miembro
 . Inicialización
 . Asignación
 . Arrays de estructuras
 . Estructuras anidadas
 . Estructuras anónimas
 . sizeof con estructuras
 . Campos de bits
 . Problemas
 . Ejercicios capítulo 11
*12 Variables IV: Punteros 1
*13 Operadores II: Más operadores
*14 Operadores III: Precedencia
*15 Funciones II: Parámetros por valor y referencia
*16 Variables V: Uniones
*17 Variables VI: Punteros 2
*18 Operadores IV: De bits y condicional
*19 Definición de tipos
*20 Funciones III
*21 Funciones IV: Sobrecarga
*22 Operadores V: Sobrecarga
*23 El preprocesador
*24 Funciones V: Recursividad
*25 Variables VII: Modificadores
*26 Espacios con nombre
*27 Clases I: Definiciones
*28 Declaración de clases
*29 Constructores
*30 Destructores
*31 El puntero this
*32 Sistema de protección
*33 Modificadores para miembros
*34 Más sobre funciones
*35 Operadores sobrecargados
*36 Herencia
*37 Funciones virtuales
*38 Derivación múltiple
*39 Trabajar con ficheros
*40 Plantillas
*41 Punteros a miembros
*42 Castings
*43 Excepciones
*Ejemplos capítulos 1 a 6
*Ejemplos capítulos 8 y 9
*A Palabras reservadas C/C++
*B Trigrafos y símbolos alternativos
*C Librerías estándar
*D Streams
  > >>

Estructuras anónimas:  

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.

Operador "sizeof" con estructuras:  

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

Campos de bits:  

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.

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 usuario y guardarlos en memoria. 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 día. Los días pueden tomar valores entre 1 y 31, los meses de 1 a 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 año. El año 2003 se almacena como 43.
    Usando estructuras, y ajustando los tipos de los campos, necesitamos un char para día, un char para mes y otro para año.
    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 anno.
  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.
  > >>