Ejemplos capítulo 16

Ejemplo 16.1

Una aplicación clásica de las uniones es ofrecer la posibilidad de manipular los mismos datos de formas diferentes. Por ejemplo, podemos crear una unión para manipular un byte a tres niveles: completo, bit a bit o nibble a nibble. (Un nibble es un conjunto de cuatro bits).

#include <iostream>
#include <cstdio>

using namespace std;

union byte {
    unsigned char b;
    struct {
        unsigned char bit8:1;
        unsigned char bit7:1;
        unsigned char bit6:1;
        unsigned char bit5:1;
        unsigned char bit4:1;
        unsigned char bit3:1;
        unsigned char bit2:1;
        unsigned char bit1:1;
    };
    struct {
        unsigned char nibble2:4;
        unsigned char nibble1:4;
    };
};

int main() {
    byte x;

    x.b = 0x2a;

    printf("%d\n", x.bit1);
    printf("%d\n", x.bit2);
    printf("%d\n", x.bit3);
    printf("%d\n", x.bit4);
    printf("%d\n", x.bit5);
    printf("%d\n", x.bit6);
    printf("%d\n", x.bit7);
    printf("%d\n", x.bit8);

    printf("%x\n", x.nibble1);
    printf("%x\n", x.nibble2);
	
    x.bit2 = 1;
    x.bit3 = 0;
    printf("%02x\n", x.b);

    return 0;
}

Ejemplo 16.2


Sistesis aditiva

En los ordenadores, por norma general, se usan números para codificar colores. Los dispositivos gráficos usan la síntesis aditiva de color. Partiendo de los colores básicos: rojo, azul y verde, se puede obtener cualquier color combinandolos en diferentes proporciones.

También por norma general, se suelen usar ocho bits para cada color, como hay tres componentes, eso da un total de 24 bits. En una palabra de 32 bits sobrarían ocho. A veces, esos ocho bits se usan como componente alfa, que indica la transparencia, o cómo se combina el color con el que existía previamente en ese punto.

En un valor entero de 32 bits, se usan los ocho de menor peso para codificar el valor del color rojo. Se suele usar la letra 'R' (Red=Rojo). En los ocho bits siguientes se codifica el color verde. Se usa la letra 'G' (Green=Verde). En los ocho siguientes se codifica el azul. Se usa la letra 'B' (Blue=Azul). Estos tres valores codifican un color en formato RGB.

Si se usan los 32 bits, los ocho restantes codifican la componente Alfa (Alpha), de transparencia.

Algunas aplicaciones y funciones de los APIs trabajan con enteros de 32 bits para manejar colores, pero a menudo es interesante acceder los componentes básicos directamente.

Podemos crear una unión para codificar colores según estas reglas, y que además de que nos permitan manipular el color como un valor único, podamos acceder a los componentes por separado:

#include <iostream>

using namespace std;

union color {
    unsigned int c;
    struct {
        unsigned char red;
        unsigned char green;
        unsigned char blue;
        unsigned char alpha;
    };
};

int main() {
    color c1 = { 0x80fedc12 };

    cout << "Color: " << dec << c1.c << " - " << hex << c1.c << endl;
    cout << "Rojo:  " << dec << (int)c1.red << " - " << hex << (int)c1.red << endl;
    cout << "Verde: " << dec << (int)c1.green << " - " << hex << (int)c1.green << endl;
    cout << "Azul:  " << dec << (int)c1.blue << " - " << hex << (int)c1.blue << endl;
    cout << "Alfa:  " << dec << (int)c1.alpha << " - " << hex << (int)c1.alpha << endl;

    c1.red = 0x42;
    c1.green = 0xde;
    cout << "Color: " << dec << c1.c << " - " << hex << c1.c << endl;
    cout << "Rojo:  " << dec << (int)c1.red << " - " << hex << (int)c1.red << endl;
    cout << "Verde: " << dec << (int)c1.green << " - " << hex << (int)c1.green << endl;
    cout << "Azul:  " << dec << (int)c1.blue << " - " << hex << (int)c1.blue << endl;
    cout << "Alfa:  " << dec << (int)c1.alpha << " - " << hex << (int)c1.alpha << endl;

    return 0;
}

Ejemplo 16.3

Veamos ahora cómo usar funciones dentro de uniones.

Para este ejemplo crearemos un constructor para cada tipo de dato que contenga la unión. Esto nos permitirá evitar la limitación de la inicialización de objetos de este tipo, ya que el compilador eligirá el constructor adecuado en función del valor suministrado.

Podemos reinterpretar, entonces, la regla que dice que sólo podemos inicializar uniones usando el primer elemento.

En realidad, lo que pasa es que el compilador sólo crea un constructor por defecto para las uniones y que el parámetro elegido para ese constructor es el primero. Nada nos impide, pues, crear nuestros propios constructores para modificar el comportamiento predefinido.

#include <iostream>
#include <cstring>

using namespace std;

union ejemplo {
    int x;
    double d;
    char cad[8];
    ejemplo(int i) : x(i) {}
    ejemplo(double n) : d(n) {}
    ejemplo(const char *c) {
        strncpy(cad, c, 7);
        cad[7] = 0;
    } 
};

int main() {
    ejemplo A(23);
    ejemplo B(123.323);
    ejemplo C("hola a todos");

    cout << "A: " << A.x << endl;
    cout << "B: " << B.d << endl;
    cout << "C: " << C.cad << endl;
    
    return 0;
}

Ejecutar este código en codepad.

Vemos en el ejemplo que se puede invocar a los constructores de la forma normal, o implícitamente, como en el caso del objeto D, para el que se suministra una cadena, que evidentemente, no es el tipo del primer elemento de la unión.

Ejemplo 16.4

Vamos a completar el ejemplo de los discriminadores, añadiendo código para iniciar y visualizar elementos del array.

// Ejemplo de unión con discriminador
// 2009 Con Clase, Salvador Pozo
#include <iostream>

using namespace std;

struct tipoLibro {
    int codigo;
    char autor[80];
    char titulo[80];
    char editorial[32];
    int anno;
};

struct tipoRevista {
    int codigo;
    char nombre[32];
    int mes;
    int anno;
};

struct tipoPelicula {
    int codigo;
    char titulo[80];
    char director[80];
    char productora[32];
    int anno;
};

enum eEjemplar { libro, revista, pelicula };

struct tipoEjemplar {
    eEjemplar tipo;
    union {
        tipoLibro l;
        tipoRevista r;
        tipoPelicula p;
    };
};

tipoEjemplar tabla[100];

int main() {
    tabla[0].tipo = libro;
    tabla[0].l.codigo = 3;
    strcpy(tabla[0].l.titulo, "El señor de los anillos");
    strcpy(tabla[0].l.autor, "J.R.R. Tolkien");
    tabla[0].l.anno = 1954;

    tabla[1].tipo = revista;
    tabla[1].r.codigo = 12;
    strcpy(tabla[1].r.nombre, "National Geographic");
    tabla[1].r.mes = 11;
    tabla[1].r.anno = 2009;

    tabla[2].tipo = pelicula;
    tabla[2].p.codigo = 43;
    strcpy(tabla[2].p.titulo, "Blade Runner");
    strcpy(tabla[2].p.director, "Ridley Scott");
    strcpy(tabla[2].p.productora, "Warner Bros. Pictures");
    tabla[2].l.anno = 1982;

    for(int i=0; i < 3; i++) {
        switch(tabla[i].tipo) {
            case libro:
                cout << "[" << tabla[i].l.codigo << "] Libro titulo: " << tabla[i].l.titulo << endl;
                break;
            case revista:
                cout << "[" << tabla[i].r.codigo << "] Revista nombre: " << tabla[i].r.nombre << endl;
                break;
            case pelicula:
                cout << "[" << tabla[i].p.codigo << "] Pelicula titulo: " << tabla[i].p.titulo << endl;
                break;
        }
    }
    return 0;
}

Ejecutar este código en codepad.

Comentarios de los usuarios (2)

Alejandro
2012-05-09 14:37:08

Según leo en la wikipedia los ordenadores pueden almacenar datos de dos maneras: big-endian ó little-endian, según se almacene primero el byte más significativo ó el byte menos significativo. Y pone un ejemplo precisamente con uniones:

#include <stdio.h>
int main()
{
    union {
        short s;
        char c[sizeof(short)];
    } un;
    un.s = 0x0102;
    if(sizeof(short) == 2)
    {
        if(un.c[0] == 1 && un.c[1] == 2)
            printf("big-endian\n");
        else if(un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    }
    else
    {
        printf("sizeof(short) = %d\n", sizeof(short));
    }
    return(0);
}
Alejandro
2012-05-09 14:40:13

Lo digo porque los códigos anteriores funcionarán o no bien según la máquina, creo