En informática , una unión es un valor que puede tener cualquiera de múltiples representaciones o formatos dentro de la misma área de memoria ; que consiste en una variable que puede contener dicha estructura de datos . Algunos lenguajes de programación admiten un tipo de unión para dicho tipo de datos . En otras palabras, un tipo de unión especifica los tipos permitidos que se pueden almacenar en sus instancias, por ejemplo, float
y integer
. A diferencia de un registro , que podría definirse para contener tanto un flotante como un entero; una unión contendría solo uno a la vez.
Una unión puede ser descrita como un bloque de memoria que se utiliza para almacenar variables de diferentes tipos de datos. Una vez que se asigna un nuevo valor a un campo, los datos existentes se sobrescriben con los nuevos datos. El área de memoria que almacena el valor no tiene un tipo intrínseco (excepto bytes o palabras de memoria), pero el valor puede ser tratado como uno de varios tipos de datos abstractos , que tienen el tipo del valor que se escribió por última vez en el área de memoria.
En la teoría de tipos , una unión tiene un tipo suma ; esto corresponde a una unión disjunta en matemáticas.
Según el lenguaje y el tipo, se puede utilizar un valor de unión en algunas operaciones, como la asignación y la comparación de igualdad, sin conocer su tipo específico. Otras operaciones pueden requerir ese conocimiento, ya sea mediante alguna información externa o mediante el uso de una unión etiquetada .
Debido a las limitaciones de su uso, las uniones sin etiquetas generalmente solo se proporcionan en lenguajes sin tipos o de una manera que no es segura para los tipos (como en C ). Tienen la ventaja sobre las uniones con etiquetas simples de que no requieren espacio para almacenar una etiqueta de tipo de datos.
El nombre "unión" proviene de la definición formal del tipo. Si se considera un tipo como el conjunto de todos los valores que ese tipo puede tomar, un tipo de unión es simplemente la unión matemática de los tipos que lo constituyen, ya que puede tomar cualquier valor que pueda tomar cualquiera de sus campos. Además, debido a que una unión matemática descarta los duplicados, si más de un campo de la unión puede tomar un único valor común, es imposible saber a partir del valor únicamente qué campo se escribió por última vez.
Sin embargo, una función de programación útil de las uniones es asignar elementos de datos más pequeños a otros más grandes para facilitar su manipulación. Una estructura de datos que consta, por ejemplo, de 4 bytes y un entero de 32 bits, puede formar una unión con un entero sin signo de 64 bits y, por lo tanto, se puede acceder a ella más fácilmente para fines de comparación, etc.
ALGOL 68 ha etiquetado las uniones y utiliza una cláusula de caso para distinguir y extraer el tipo constituyente en tiempo de ejecución. Una unión que contiene otra unión se trata como el conjunto de todas sus posibilidades constituyentes y, si el contexto lo requiere, una unión se convierte automáticamente en la unión más amplia. Una unión no puede contener explícitamente ningún valor, que se pueda distinguir en tiempo de ejecución. Un ejemplo es:
modo nodo = unión ( real , int , string , void ); nodo n := "abc"; caso n en ( r real ): print(("real:", r)), ( int i): imprimir(("int:", i)), ( cadena s): imprimir(("cadena:", s)), ( void ): imprimir(("void:", "VACÍO")), fuera imprimir(("?:", n)) esac
La sintaxis del tipo de unión C/C++ y la noción de conversiones se derivaron de ALGOL 68, aunque en una forma no etiquetada. [1]
En C y C++ , las uniones sin etiquetar se expresan casi exactamente como las estructuras ( structs ), excepto que cada miembro de datos se encuentra en la misma dirección de memoria. Los miembros de datos, como en las estructuras, no necesitan ser valores primitivos y, de hecho, pueden ser estructuras o incluso otras uniones. C++ (desde C++11 ) también permite que un miembro de datos sea cualquier tipo que tenga un constructor/destructor completo y/o un constructor de copia, o un operador de asignación de copia no trivial. Por ejemplo, es posible tener la cadena estándar de C++ como miembro de una unión.
El uso principal de una unión es permitir el acceso a una ubicación común por parte de diferentes tipos de datos, por ejemplo, acceso a entrada/salida de hardware, uso compartido de campos de bits y palabras, o juegos de palabras de tipos . Las uniones también pueden proporcionar polimorfismo de bajo nivel . Sin embargo, no hay verificación de tipos, por lo que es responsabilidad del programador asegurarse de que se acceda a los campos adecuados en diferentes contextos. El campo relevante de una variable de unión generalmente está determinado por el estado de otras variables, posiblemente en una estructura envolvente.
Un modismo común de programación en C utiliza uniones para realizar lo que C++ llama una reinterpret_cast
, asignando a un campo de una unión y leyendo de otro, como se hace en el código que depende de la representación en bruto de los valores. Un ejemplo práctico es el método de calcular raíces cuadradas utilizando la representación IEEE . Sin embargo, este no es un uso seguro de las uniones en general.
Los especificadores de estructura y unión tienen la misma forma. [ . . . ] El tamaño de una unión es suficiente para contener el mayor de sus miembros. El valor de como máximo uno de los miembros se puede almacenar en un objeto de unión en cualquier momento. Un puntero a un objeto de unión, convertido adecuadamente, apunta a cada uno de sus miembros (o si un miembro es un campo de bits, a la unidad en la que reside), y viceversa.
— ANSI/ISO 9899:1990 (la norma ANSI C) Sección 6.5.2.1
En C++, C11 y como una extensión no estándar en muchos compiladores, las uniones también pueden ser anónimas. No es necesario hacer referencia a sus miembros de datos, sino que se accede a ellos directamente. Tienen algunas restricciones en comparación con las uniones tradicionales: en C11, deben ser miembros de otra estructura o unión [2] y, en C++, no pueden tener métodos ni especificadores de acceso.
La simple omisión de la parte del nombre de la clase en la sintaxis no convierte a una unión en una unión anónima. Para que una unión se considere anónima, la declaración no debe declarar un objeto. Ejemplo:
#include <iostream> #include <cstdint> int main () { union { float f ; uint32_t d ; // Supone que float tiene 32 bits de ancho }; f = 3.14f ; std :: cout << "Representación hexadecimal de 3.14f:" << std :: hex << d << '\n' ; return 0 ; }
Las uniones anónimas también son útiles en struct
las definiciones de C para proporcionar una sensación de espacio de nombres. [3]
En compiladores como GCC, Clang e IBM XL C para AIX, transparent_union
hay un atributo disponible para los tipos de unión. Los tipos contenidos en la unión se pueden convertir de forma transparente al tipo de unión en sí en una llamada de función, siempre que todos los tipos tengan el mismo tamaño. Está pensado principalmente para funciones con interfaces de parámetros múltiples, un uso que se hizo necesario debido a las primeras extensiones de Unix y la posterior reestandarización. [4]
En COBOL , los elementos de datos de unión se definen de dos maneras. La primera utiliza la palabra clave RENAMES (nivel 66), que asigna efectivamente un segundo elemento de datos alfanuméricos sobre la misma ubicación de memoria que un elemento de datos anterior. En el código de ejemplo a continuación, el elemento de datos PERSON-REC se define como un grupo que contiene otro grupo y un elemento de datos numéricos. PERSON-DATA se define como un elemento de datos alfanuméricos que cambia el nombre de PERSON-REC y trata los bytes de datos que continúan dentro de él como datos de caracteres.
01 PERSONA-REC . 05 PERSONA-NOMBRE . 10 PERSONA-NOMBRE-APELLIDO PIC X(12) . 10 PERSONA-NOMBRE-PRIMER PIC X(16) . 10 PERSONA-NOMBRE-MEDIO PIC X . 05 PERSONA-ID PIC 9(9) PACKED-DECIMAL . 01 PERSONA-DATOS RENOMBRAN PERSONA-REC .
La segunda forma de definir un tipo de unión es mediante la palabra clave REDEFINES . En el código de ejemplo que aparece a continuación, el elemento de datos VERS-NUM se define como un entero binario de 2 bytes que contiene un número de versión. Un segundo elemento de datos VERS-BYTES se define como una variable alfanumérica de dos caracteres. Dado que el segundo elemento se redefine sobre el primero, los dos elementos comparten la misma dirección en la memoria y, por lo tanto, comparten los mismos bytes de datos subyacentes. El primer elemento interpreta los dos bytes de datos como un valor binario, mientras que el segundo elemento interpreta los bytes como valores de caracteres.
01 VERS-INFO . 05 VERS-NUM PIC S9(4) COMP . 05 VERS-BYTES PIC X(2) REDEFINE VERS-NUM
En Pascal , hay dos formas de crear uniones. Una es la forma estándar a través de un registro de variantes. La segunda es una forma no estándar de declarar una variable como absoluta, lo que significa que se coloca en la misma ubicación de memoria que otra variable o en una dirección absoluta. Si bien todos los compiladores de Pascal admiten registros de variantes, solo algunos admiten variables absolutas.
Para los fines de este ejemplo, los siguientes son todos tipos de enteros: un byte consta de 8 bits, una palabra tiene 16 bits y un entero tiene 32 bits.
El siguiente ejemplo muestra la forma absoluta no estándar:
var A : Entero ; B : matriz [ 1 .. 4 ] de Byte absoluto A ; C : Entero absoluto 0 ;
En el primer ejemplo, cada uno de los elementos de la matriz B se asigna a uno de los bytes específicos de la variable A. En el segundo ejemplo, la variable C se asigna a la dirección de máquina exacta 0.
En el siguiente ejemplo, un registro tiene variantes, algunas de las cuales comparten la misma ubicación que otras:
tipo Forma = ( Círculo , Cuadrado , Triángulo ) ; Dimensiones = registro caso Figura : Forma del Círculo : ( Diámetro : real ) ; Cuadrado : ( Ancho : real ) ; Triángulo : ( Lado : real ; Ángulo1 , Ángulo2 : 0 .. 360 ) fin ;
En PL/I, el término original para una unión era cell , [5] que aún se acepta como sinónimo de unión por varios compiladores. La declaración de unión es similar a la definición de estructura, donde los elementos del mismo nivel dentro de la declaración de unión ocupan el mismo almacenamiento. Los elementos de la unión pueden ser de cualquier tipo de datos, incluidas las estructuras y las matrices. [6] : pp192–193 Aquí vers_num y vers_bytes ocupan las mismas ubicaciones de almacenamiento.
1 vers_info unión , 5 vers_num binario fijo , 5 vers_bytes pic '(2)A';
Una alternativa a una declaración de unión es el atributo DEFINED, que permite declaraciones alternativas de almacenamiento, sin embargo los tipos de datos de las variables base y definidas deben coincidir. [6] : pp.289–293
Rust implementa tanto uniones etiquetadas como no etiquetadas. En Rust, las uniones etiquetadas se implementan utilizando la enum
palabra clave. A diferencia de los tipos enumerados en la mayoría de los otros lenguajes, las variantes de enumeración en Rust pueden contener datos adicionales en forma de tupla o estructura, lo que las convierte en uniones etiquetadas en lugar de tipos enumerados simples. [7]
Rust también admite uniones sin etiquetar mediante la union
palabra clave. La disposición de la memoria de las uniones en Rust no está definida de forma predeterminada, [8] pero una unión con el #[repr(C)]
atributo se dispondrá en la memoria exactamente como la unión equivalente en C. [9] La lectura de los campos de una unión solo se puede realizar dentro de una unsafe
función o bloque, ya que el compilador no puede garantizar que los datos en la unión sean válidos para el tipo del campo; si este no es el caso, el resultado será un comportamiento indefinido . [10]
En C y C++, la sintaxis es:
unión < nombre > { < tipo de datos > < nombre de variable 1 > ; < tipo de datos > < nombre de variable 2 > ; . . . < tipo de datos > < nombre de variable n > ; } < nombre de variable de unión > ;
Una estructura también puede ser miembro de una unión, como muestra el siguiente ejemplo:
unión nombre1 { estructura nombre2 { int a ; float b ; char c ; } svar ; int d ; } uvar ;
Este ejemplo define una variable uvar
como una unión (etiquetada como name1
), que contiene dos miembros, una estructura (etiquetada como name2
) denominada svar
(que a su vez contiene tres miembros) y una variable entera denominada d
.
Las uniones pueden ocurrir dentro de estructuras y matrices, y viceversa:
struct { int banderas ; char * nombre ; int utype ; unión { int ival ; float fval ; char * sval ; } u ; } symtab [ NSYM ];
El número ival se conoce como symtab[i].u.ival
y el primer carácter de la cadena sval por cualquiera de *symtab[i].u.sval
o symtab[i].u.sval[0]
.
Los tipos de unión se introdujeron en PHP 8.0. [11] Los valores están "etiquetados" implícitamente con un tipo por el lenguaje y pueden recuperarse mediante "gettype()".
clase Ejemplo { int privado | float $foo ; función pública squareAndAdd ( float | int $bar ) : int | float { return $bar ** 2 + $this -> foo ; } }
El soporte para tipificación se introdujo en Python 3.5. [12] La nueva sintaxis para tipos de unión se introdujo en Python 3.10. [13]
Clase Ejemplo : foo = 0 def cuadrado_y_suma ( self , bar : int | float ) - > int | float : returnbar ** 2 + self.foo
Los tipos de unión son compatibles con TypeScript. [14] Los valores están "etiquetados" implícitamente con un tipo por el lenguaje, y pueden recuperarse mediante una typeof
llamada a valores primitivos y una instanceof
comparación para tipos de datos complejos. Los tipos con usos superpuestos (por ejemplo, existe un método de corte tanto en cadenas como en matrices, el operador más funciona tanto en cadenas como en números) no necesitan una restricción adicional para usar estas características.
función sucesora ( n : número | bigint ) : número | bigint { // los tipos que admiten las mismas operaciones no necesitan restricción return ++ n ; } función depende de parámetro ( v : cadena | matriz < cadena > | número ) { // los tipos distintos necesitan restringirse if ( v instanceof Array ) { // hacer algo } else if ( typeof ( v ) === "cadena" ) { // hacer algo más } else { // tiene que ser un número } }
Las uniones etiquetadas en Rust usan la enum
palabra clave y pueden contener variantes de tuplas y estructuras:
enumeración Foo { Bar ( i32 ), Baz { x : Cadena , y : i32 }, }
Las uniones sin etiquetar en Rust utilizan la union
palabra clave:
unión Foo { bar : i32 , baz : bool , }
La lectura de los campos de una unión sin etiqueta da como resultado un comportamiento indefinido si los datos de la unión no son válidos como el tipo del campo y, por lo tanto, requiere un unsafe
bloque:
let x = Foo { bar : 10 }; let y = unsafe { x . bar }; // Esto establecerá y en 10 y no dará como resultado un comportamiento indefinido. let z = unsafe { x . baz }; // Esto da como resultado un comportamiento indefinido, ya que el valor almacenado en x no es un bool válido.
El esquema de composición de tipos adoptado por C debe una deuda considerable a Algol 68, aunque tal vez no surgió en una forma que los partidarios de Algol aprobarían. La noción central que capté de Algol fue una estructura de tipos basada en tipos atómicos (incluidas las estructuras), compuestas en matrices, punteros (referencias) y funciones (procedimientos). El concepto de uniones y conversiones de Algol 68 también tuvo una influencia que apareció más tarde.