В информатике объединение — это значение , которое может иметь любое из нескольких представлений или форматов в одной и той же области памяти ; которое состоит из переменной , которая может содержать такую структуру данных . Некоторые языки программирования поддерживают тип объединения для такого типа данных . Другими словами, тип объединения определяет разрешенные типы, которые могут храниться в его экземплярах, например, float
и integer
. В отличие от записи , которая может быть определена как содержащая как число с плавающей точкой , так и целое число; объединение будет содержать только одно за раз.
Объединение можно представить как фрагмент памяти, который используется для хранения переменных различных типов данных. После того, как полю присваивается новое значение, существующие данные перезаписываются новыми данными. Область памяти, в которой хранится значение, не имеет внутреннего типа (кроме байтов или слов памяти), но значение можно рассматривать как один из нескольких абстрактных типов данных , имеющих тип значения, которое было последним записано в область памяти.
В теории типов объединение имеет тип суммы ; это соответствует несвязному объединению в математике.
В зависимости от языка и типа, значение union может использоваться в некоторых операциях, таких как присваивание и сравнение на равенство, без знания его конкретного типа. Другие операции могут потребовать этого знания, либо посредством некоторой внешней информации, либо посредством использования помеченного union .
Из-за ограничений их использования немаркированные объединения обычно предоставляются только в нетипизированных языках или небезопасным для типов способом (как в C ). Они имеют преимущество перед простыми маркированными объединениями, так как не требуют места для хранения тега типа данных.
Название "union" происходит от формального определения типа. Если тип рассматривается как набор всех значений, которые может принимать этот тип, тип union является просто математическим объединением его составляющих типов, поскольку он может принимать любое значение, которое может принимать любое из его полей. Кроме того, поскольку математическое объединение отбрасывает дубликаты, если более одного поля union могут принимать одно общее значение, невозможно определить по одному значению, какое поле было записано последним.
Однако одна полезная функция программирования объединений заключается в отображении меньших элементов данных в более крупные для более легкой манипуляции. Структура данных, состоящая, например, из 4 байтов и 32-битного целого числа, может образовывать объединение с беззнаковым 64-битным целым числом и, таким образом, быть более доступной для целей сравнения и т. д.
ALGOL 68 имеет помеченные объединения и использует предложение case для различения и извлечения составного типа во время выполнения. Объединение, содержащее другое объединение, рассматривается как набор всех его составных возможностей, и если контекст требует этого, объединение автоматически приводится к более широкому объединению. Объединение может явно не содержать никакого значения, что может быть различимо во время выполнения. Вот пример:
узел режима = объединение ( действительный , целое , строка , пустота ); узел n := "abc"; случай n в ( действительном r): print(("действительном:", r)), ( целое i): печать(("целое:", i)), ( строка s): print(("строка:", s)), ( void ): print(("void:", "EMPTY")), out print(("?:", n)) esac
Синтаксис типа объединения C/C++ и понятие приведения типов были получены из ALGOL 68, хотя и в немаркированной форме. [1]
В C и C++ немаркированные объединения выражаются почти так же, как структуры ( structs ), за исключением того, что каждый член данных расположен по одному и тому же адресу памяти. Члены данных, как и в структурах, не обязательно должны быть примитивными значениями и фактически могут быть структурами или даже другими объединениями. C++ (начиная с C++11 ) также позволяет члену данных быть любым типом, имеющим полноценный конструктор/деструктор и/или конструктор копирования или нетривиальный оператор присваивания копирования. Например, можно иметь стандартную строку C++ в качестве члена объединения.
Основное применение объединения — предоставление доступа к общему расположению различными типами данных, например, доступ к аппаратному вводу/выводу, совместное использование битовых полей и слов или каламбур типа . Объединения также могут обеспечивать низкоуровневый полиморфизм . Однако проверка типов не производится, поэтому программист должен убедиться, что в разных контекстах осуществляется доступ к нужным полям. Соответствующее поле переменной объединения обычно определяется состоянием других переменных, возможно, в охватывающей структуре.
Одна из распространенных идиом программирования на языке C использует объединения для выполнения того, что в C++ называется a reinterpret_cast
, путем назначения одному полю объединения и чтения из другого, как это делается в коде, который зависит от исходного представления значений. Практическим примером является метод вычисления квадратных корней с использованием представления IEEE . Однако это не является безопасным использованием объединений в целом.
Спецификаторы структуры и объединения имеют одинаковую форму. [ . . . ] Размер объединения достаточен для того, чтобы вместить наибольший из его членов. Значение не более одного из членов может быть сохранено в объекте объединения в любое время. Указатель на объект объединения, соответствующим образом преобразованный, указывает на каждого из его членов (или, если член является битовым полем, то на единицу, в которой он находится), и наоборот.
— ANSI/ISO 9899:1990 (стандарт ANSI C) Раздел 6.5.2.1
В C++, C11 и как нестандартное расширение во многих компиляторах объединения также могут быть анонимными. Их члены данных не нуждаются в ссылках, вместо этого они доступны напрямую. Они имеют некоторые ограничения по сравнению с традиционными объединениями: в C11 они должны быть членами другой структуры или объединения, [2] а в C++ они не могут иметь методов или спецификаторов доступа.
Простое опускание части синтаксиса с именем класса не делает объединение анонимным объединением. Чтобы объединение считалось анонимным объединением, в объявлении не должен быть объявлен объект. Пример:
#include <iostream> #include <cstdint> int main () { union { float f ; uint32_t d ; // Предполагается, что float имеет ширину 32 бита }; f = 3.14f ; std :: cout << "Шестнадцатеричное представление 3.14f:" << std :: hex << d << '\n' ; return 0 ; }
Анонимные объединения также полезны в struct
определениях C для обеспечения ощущения пространства имен. [3]
В таких компиляторах, как GCC, Clang и IBM XL C для AIX, transparent_union
атрибут доступен для типов union. Типы, содержащиеся в union, могут быть прозрачно преобразованы в сам тип union в вызове функции, при условии, что все типы имеют одинаковый размер. Он в основном предназначен для функций с интерфейсами нескольких параметров, использование, необходимое для ранних расширений Unix и более поздней повторной стандартизации. [4]
В COBOL элементы данных union определяются двумя способами. Первый использует ключевое слово RENAMES (уровень 66), которое эффективно отображает второй буквенно-цифровой элемент данных поверх того же расположения памяти, что и предыдущий элемент данных. В примере кода ниже элемент данных PERSON-REC определяется как группа, содержащая другую группу и числовой элемент данных. PERSON-DATA определяется как буквенно-цифровой элемент данных, который переименовывает PERSON-REC , обрабатывая байты данных, продолженные внутри него, как символьные данные.
01 PERSON-REC . 05 PERSON-NAME . 10 PERSON-NAME-LAST PIC X(12) . 10 PERSON-NAME-FIRST PIC X(16) . 10 PERSON-NAME-MID PIC X . 05 PERSON-ID PIC 9(9) УПАКОВАННЫЙ-ДЕСЯТИЧНЫЙ . 01 PERSON-DATA ПЕРЕИМЕНОВЫВАЕТ PERSON-REC .
Второй способ определения типа объединения — использование ключевого слова REDEFINES . В примере кода ниже элемент данных VERS-NUM определяется как 2-байтовое двоичное целое число, содержащее номер версии. Второй элемент данных VERS-BYTES определяется как двухсимвольная буквенно-цифровая переменная. Поскольку второй элемент переопределяется по первому элементу, два элемента совместно используют один и тот же адрес в памяти и, следовательно, совместно используют одни и те же базовые байты данных. Первый элемент интерпретирует два байта данных как двоичное значение, тогда как второй элемент интерпретирует байты как символьные значения.
01 VERS-INFO . 05 VERS-NUM PIC S9(4) COMP . 05 VERS-BYTES PIC X(2) ПЕРЕОПРЕДЕЛЯЕТ VERS-NUM
В Pascal есть два способа создания объединений. Один из них — стандартный способ через вариантную запись. Второй — нестандартный способ объявления переменной как абсолютной, то есть она размещается в том же месте памяти, что и другая переменная, или по абсолютному адресу. Хотя все компиляторы Pascal поддерживают вариантные записи, только некоторые поддерживают абсолютные переменные.
В данном примере все следующие типы являются целочисленными: байт состоит из 8 бит, слово — из 16 бит, а целое число — из 32 бит.
В следующем примере показана нестандартная абсолютная форма:
var A : Целое число ; B : массив [ 1 .. 4 ] абсолютных байтов A ; C : Целое число абсолютное 0 ;
В первом примере каждый из элементов массива B сопоставляется с одним из определенных байтов переменной A. Во втором примере переменной C присваивается точный машинный адрес 0.
В следующем примере запись имеет варианты, некоторые из которых имеют то же местоположение, что и другие:
тип Форма = ( Круг , Квадрат , Треугольник ) ; Размеры = record case Фигура : Форма Круга : ( Диаметр : действительный ) ; Квадрат : ( Ширина : действительная ) ; Треугольник : ( Сторона : действительный ; Угол1 , Угол2 : 0 .. 360 ) конец ;
В PL/I первоначальным термином для объединения было cell , [5] которое до сих пор принимается как синоним объединения несколькими компиляторами. Декларация объединения похожа на определение структуры, где элементы на одном уровне в декларации объединения занимают одно и то же хранилище. Элементы объединения могут быть любым типом данных, включая структуры и массивы. [6] : стр. 192–193 Здесь vers_num и vers_bytes занимают одни и те же места хранения.
1 vers_info union , 5 vers_num fixed binary , 5 vers_bytes pic '(2)A';
Альтернативой объявлению объединения является атрибут DEFINED, который допускает альтернативные объявления хранилища, однако типы данных базовой и определяемой переменных должны совпадать. [6] : стр. 289–293
Rust реализует как помеченные, так и не помеченные объединения. В Rust помеченные объединения реализуются с помощью enum
ключевого слова . В отличие от перечисляемых типов в большинстве других языков, варианты перечисления в Rust могут содержать дополнительные данные в форме кортежа или структуры, что делает их помеченными объединениями, а не простыми перечисляемыми типами. [7]
Rust также поддерживает немаркированные объединения с помощью union
ключевого слова . Расположение памяти объединений в Rust по умолчанию не определено, [8] но объединение с #[repr(C)]
атрибутом будет размещено в памяти точно так же, как эквивалентное объединение в C. [9] Чтение полей объединения может быть выполнено только внутри unsafe
функции или блока, поскольку компилятор не может гарантировать, что данные в объединении будут допустимы для типа поля; если это не так, это приведет к неопределенному поведению . [10]
В C и C++ синтаксис следующий:
union < имя > { < тип данных > < имя первой переменной > ; < тип данных > < имя второй переменной > ; . . . < тип данных > < имя n - й переменной > ; } < имя переменной union > ;
Структура также может быть членом союза, как показано в следующем примере:
имя_объединения1 { имя_структуры2 { int a ; float b ; char c ; } svar ; int d ; } uvar ;
В этом примере переменная определяется uvar
как объединение (помеченное как name1
), которое содержит два члена, структуру (помеченную как name2
) с именем svar
(которая, в свою очередь, содержит три члена) и целочисленную переменную с именем d
.
Объединения могут возникать внутри структур и массивов, и наоборот:
struct { int flags ; char * name ; int utype ; union { int ival ; float fval ; char * sval ; } u ; } symtab [ NSYM ];
Число ival обозначается как , symtab[i].u.ival
а первый символ строки sval — как , *symtab[i].u.sval
или symtab[i].u.sval[0]
.
Типы объединений были введены в PHP 8.0. [11] Значения неявно «помечаются» типом языком и могут быть извлечены с помощью «gettype()».
Пример класса { private int | float $foo ; публичная функция squareAndAdd ( float | int $bar ) : int | float { return $bar ** 2 + $this -> foo ; } }
Поддержка типизации была введена в Python 3.5. [12] Новый синтаксис для типов объединений был введен в Python 3.10. [13]
Пример класса : foo = 0 def square_and_add ( self , bar : int | float ) - > int | float : return bar ** 2 + self.foo
Типы объединений поддерживаются в TypeScript. [14] Значения неявно «помечены» типом языком и могут быть извлечены с помощью typeof
вызова примитивных значений и instanceof
сравнения для сложных типов данных. Типы с перекрывающимся использованием (например, метод среза существует как для строк, так и для массивов, оператор плюс работает как для строк, так и для чисел) не нуждаются в дополнительном сужении для использования этих функций.
function successor ( n : number | bigint ) : number | bigint { // типы, поддерживающие те же операции, не нуждаются в сужении return ++ n ; } function dependsOnParameter ( v : string | Array < string > | number ) { // отдельные типы требуют сужения if ( v instanceof Array ) { // сделать что-то } else if ( typeof ( v ) === "string" ) { // сделать что-то еще } else { // должно быть числом } }
Тегированные объединения в Rust используют enum
ключевое слово и могут содержать варианты кортежей и структур:
enum Foo { Bar ( i32 ), Baz { x : String , y : i32 }, }
Непомеченные объединения в Rust используют union
ключевое слово:
union Foo { bar : i32 , baz : bool , }
Чтение из полей немаркированного объединения приводит к неопределенному поведению , если данные в объединении не являются допустимым типом поля и, следовательно, требуют unsafe
блока:
let x = Foo { bar : 10 }; let y = unsafe { x . bar }; // Это установит y в 10 и не приведет к неопределенному поведению. let z = unsafe { x . baz }; // Это приведет к неопределенному поведению, так как значение, сохраненное в x, не является допустимым логическим значением.
Схема композиции типов, принятая в C, во многом обязана Algol 68, хотя она, возможно, не появилась в той форме, которую одобрили бы приверженцы Algol. Центральным понятием, которое я заимствовал из Algol, была структура типов, основанная на атомарных типах (включая структуры), составленных в массивы, указатели (ссылки) и функции (процедуры). Концепция объединений и приведений Algol 68 также оказала влияние, которое проявилось позже.