Union c что это
На структуры во многом похожи объединения. Объединения (union) также позволяют определить свой тип данных и также хранят набор элементов, но в отличие от структуры все элементы объединения имеют нулевое смещение. А это значит, что разные элементы занимают в памяти один и тот же участок, то есть в памяти они накладываются друг на друга.
Для определения объединений применяется ключевое слово union и следующий формальный синтаксис:
union имя_объединения < тип элемент1; тип элемент2; . тип элементN; >;
Фактически объединение определяется точно также, как и структура, только вместо слова struct используется ключевое слово union .
Так, создадим простейшее объединение, которое хранит символ и его числовой код из таблицы ASCII:
union ascii < int digit; char letter; >;
Объединение ascii хранит в одном и том же участки памяти объект int (числовой код символа) и объект char (сам символ). Конкретный размер выделенной памяти будет зависеть от системы и реализации, но в общем случае это будет выглядеть примерно следующим образом:

В этом случае объединение сode на большинстве платформ будет занимать 4 байта. Длина элементов, как здесь, может быть разной, и в этом случае размер объединения вычисляется по наибольшему элементу.
После определения объединения мы можем создать его переменную и присвоить ей какое-либо значение:
union ascii code;
При определении переменной объединения мы ее можем сразу инициализировать, но стоит учитывать, что инициализировать мы можем только первый элемент объединения. В данном случае это элемент digit типа int, поэтому мы можем передать ему только целое число:
union ascii code = ;
Для обращения к элементам объединения, как и в случае со структурами, можно использовать операцию «точка»:
#include union ascii < int digit; char letter; >; int main(void) < union ascii code; code.digit = 120; printf("%d - %c \n", code.digit, code.letter); // 120 - x printf("%d - %d \n", code.digit, code.letter); // 120 - 120 code.letter = 87; printf("%d - %c \n", code.digit, code.letter); // 87 - W return 0; >
Здесь создается переменная code, которая представляет объединение ascii. Далее его элементу digit (числовой код символа) присваивается число 120:
code.digit = 120;
Стоит отметить, что, так как оба элемента — letter и digit занимают одну и ту же память, то данные фактически одни и те же, только при обращении к code.digit данные интерпретируются как объект int, а при обращении к code.letter — как объект char.
printf("%d - %c \n", code.digit, code.letter); // 120 - x printf("%d - %d \n", code.digit, code.letter); // 120 - 120
И изменение одного из них приведет к изменению другого.
code.letter = 87; printf("%d - %c \n", code.digit, code.letter); // 87 - W
Анонимные объединения и установка псевдонима с помощью typedef
Также можно определять анонимные объединения:
#include union < int digit; char letter; >code1, code2; // переменные code1, code2 int main(void)
С помощью оператора typedef можно задать псевдоним для объединения:
typedef union ascii < int digit; char letter; >ascii_code;
Здесь псевдонимом является идентификатор ascii_code , поэтому следующие определения переменных будут аналогичны:
union ascii code = ; ascii_code code2 = ;
Также можно устанавливать псевдоним для анонимных объединений:
typedef union < int digit; char letter; >ascii_code;
Пример с объединениями
Ключевая возможность объединений состоит в том, что они могут применяться для хранения значений разных типов. Выше мы посмотрели, как объединение может хранить данные одновременно типов int и char. Но в подобном случае все несколько просто — int и char накладываются друг на друга — числовой код символа можно выразить и с помощью типа char, и с помощью типа int. Теперь посмотрим более сложный пример:
#include typedef enum < NODE_STRING, NODE_INT >node_type; typedef union < int int_value; char* str_value; >node_data; typedef struct < node_type type; node_data data; >node; void print_node(node n) < if(n.type == NODE_STRING)< printf("String: %s\n", n.data.str_value); >else if(n.type == NODE_INT) < printf("Int: %d\n", n.data.int_value); >> int main(void)
Здесь у нас определяется структура node, которая имеет два поля — тип структуры в виде перечисления node_type и непосредственно данные структуры в виде поля node_data. Перечисление node_type может принимать два значения — NODE_STRING и NODE_INT , указывая, что структура будет хранить соответственно или строку, или целое число. А объединение node_data имеет два поля:
typedef union < int int_value; char* str_value; >node_data;
Если структура представляет число, то для обращения к данным используется поле int_value , а если строку — то поле str_value
Для упрощения вывода данных структуры node определяем отдельную функцию print_node() :
void print_node(node n) < if(n.type == NODE_STRING)< printf("String: %s\n", n.data.str_value); >else if(n.type == NODE_INT) < printf("Int: %d\n", n.data.int_value); >>
В зависимости от типа структуры обращаем к полю n.data.str_value или n.data.int_value
В функции main для тестирования создаем пару структур и выводим их данные на консоль. Консольный вывод программы:
Int: 22 String: Hello World
Указатели на объединения
И как и со структурами, можно определять указатели на объединения. Для обращения к элементам объединения по указателю применяется тот же синтаксис, что и в случае со структурами:
(* указатель_на_объединение).имя_элемента указатель_на_объединение->имя_элемента
Используем указатели на объединения:
#include union ascii < int digit; char letter; >; int main(void) < union ascii code = ; union ascii * p_code = &code; printf("%d \n", p_code->digit); // 45 p_code->digit= 89; printf("%d \n", code.digit); // 89 return 0; >
Объединения
Объединения — это объект, позволяющий нескольким переменным различных типов занимать один участок памяти. Объявление объединения похоже на объявление структуры:
union union_type int i; char ch;
>;
Как и для структур, можно объявить переменную, поместив ее имя в конце определения или используя отдельный оператор объявления. Для объявления переменной cnvt объединения union_type следует написать:
union union_type cnvt;
В cnvt как целое число i, так и символ ch занимают один участок памяти. (Конечно, i занимает 2 или 4 байта, a ch — только 1.) Рисунок показывает, как i и ch разделяют один участок памяти (предполагается наличие 16-битных целых). Можно обратиться к данным, сохраненным в cnvt, как к целому числу, так и к символу.
write_int(1000, fp);
fclose(fp);
return 0;
>
/* вывод целого с помощью объединения */
int write_int (int num, FILE *fp) union pw wrd;
wrd.i = num;
putс(wrd.ch[0], fp); /* вывод первой половины */
return putc(wrd.ch[1], fp); /* вывод второй половины */
>
Хотя write_int() вызывается с целым, она использует объединение для записи обеих половинок целого в дисковый файл побайтно.
union
В C++17 и более поздних версиях std::variant class это типобезопасная альтернатива для union.
Это union определяемый пользователем тип, в котором все члены совместно используют одно расположение памяти. Это определение означает, union что в любое время может содержать не более одного объекта из списка элементов. Это также означает, что независимо от количества элементов union всегда используется достаточно памяти для хранения самого большого элемента.
Это union может быть полезно для экономии памяти при наличии большого количества объектов и ограниченной памяти. Тем не менее, union требуется дополнительная помощь, чтобы правильно использовать. Вы несете ответственность за обеспечение постоянного доступа к одному и тому же участнику, которому вы назначены. Если какие-либо типы элементов имеют нетривиальный конstructили, необходимо написать код, чтобы явным образом иstruct уничтожить этот элемент. Прежде чем использовать union, рассмотрите ли проблему, которую вы пытаетесь решить, можно лучше выразить с помощью базовых class и производных class типов.
Синтаксис
Параметры
tag
Имя типа, заданное параметру union.
member-list
Элементы, которые union могут содержаться.
Объявление union
Начните объявление с union помощью union ключевое слово и заключите список членов в фигурные скобки:
// declaring_a_union.cpp union RecordType // Declare a simple union type < char ch; int i; long l; float f; double d; int *int_ptr; >; int main() < RecordType t; t.i = 5; // t holds an int t.f = 7.25; // t now holds a float >
Использование union
В предыдущем примере любой код, обращаюющийся к union данным, должен знать, какой член содержит данные. Наиболее распространенное решение этой проблемы называется дискриминированным union. Он включает union в себя элементstruct, указывающий enum тип элемента, хранящийся в данный момент.union В следующем примере демонстрируется использование основного подхода:
#include using namespace std; enum class WeatherDataType < Temperature, Wind >; struct TempData < int StationId; time_t time; double current; double max; double min; >; struct WindData < int StationId; time_t time; int speed; short direction; >; struct Input < WeatherDataType type; union < TempData temp; WindData wind; >; >; // Functions that are specific to data types void Process_Temp(TempData t) <> void Process_Wind(WindData w) <> void Initialize(std::queue& inputs) < Input first; first.type = WeatherDataType::Temperature; first.temp = < 101, 1418855664, 91.8, 108.5, 67.2 >; inputs.push(first); Input second; second.type = WeatherDataType::Wind; second.wind = < 204, 1418859354, 14, 27 >; inputs.push(second); > int main(int argc, char* argv[]) < // Container for all the data records queueinputs; Initialize(inputs); while (!inputs.empty()) < Input const i = inputs.front(); switch (i.type) < case WeatherDataType::Temperature: Process_Temp(i.temp); break; case WeatherDataType::Wind: Process_Wind(i.wind); break; default: break; >inputs.pop(); > return 0; >
В предыдущем примере union имяstruct Input отсутствует, поэтому он называется анонимным union. Доступ к его членам можно получить напрямую, как если бы они являются членами .struct Дополнительные сведения об использовании анонимного unionсм. в разделе «Анонимныйunion «.
В предыдущем примере показана проблема, которую также можно решить с помощью class типов, производных от общей базы class. Вы можете ветвить код на основе типа среды выполнения каждого объекта в контейнере. Ваш код может быть проще поддерживать и понимать, но он также может быть медленнее, чем использование union. Кроме того, с unionпомощью приложения можно хранить несвязанные типы. Позволяет union динамически изменять тип хранимого значения, не изменяя тип самой переменной union . Например, можно создать разнородный массив MyUnionType , элементы которого хранят разные значения разных типов.
Это легко неправильно использовать Input struct в примере. Это до пользователя, чтобы использовать дискриминатор правильно для доступа к члену, в котором хранятся данные. Вы можете защититься от неправильного union private использования, сделав и предоставив специальные функции доступа, как показано в следующем примере.
Неограниченный union (C++11)
В C++03 и более ранних версиях union может содержатьstatic не члены данных, имеющие class тип, если у типа нет предоставленных пользователем constructors, destructors или операторов назначения. В C++11 эти ограничения отсутствуют. Если в него включен такой членunion, компилятор автоматически помечает какие-либо специальные функции-члены, которые не предоставляются пользователем. deleted union Если он является анонимным union внутри class или struct, то любые специальные функции-члены classstruct или не предоставленные пользователем помечены как deleted . В следующем примере показано, как обрабатывать этот случай. Один из членов имеет член, который требует этого специального union лечения:
// for MyVariant #include #include #include // for sample objects and output #include #include #include using namespace std; struct A < A() = default; A(int i, const string& str) : num(i), name(str) <>int num; string name; //. >; struct B < B() = default; B(int i, const string& str) : num(i), name(str) <>int num; string name; vector vec; // . >; enum class Kind < None, A, B, Integer >; #pragma warning (push) #pragma warning(disable:4624) class MyVariant < public: MyVariant() : kind_(Kind::None) < >MyVariant(Kind kind) : kind_(kind) < switch (kind_) < case Kind::None: break; case Kind::A: new (&a_) A(); break; case Kind::B: new (&b_) B(); break; case Kind::Integer: i_ = 0; break; default: _ASSERT(false); break; >> ~MyVariant() < switch (kind_) < case Kind::None: break; case Kind::A: a_.~A(); break; case Kind::B: b_.~B(); break; case Kind::Integer: break; default: _ASSERT(false); break; >kind_ = Kind::None; > MyVariant(const MyVariant& other) : kind_(other.kind_) < switch (kind_) < case Kind::None: break; case Kind::A: new (&a_) A(other.a_); break; case Kind::B: new (&b_) B(other.b_); break; case Kind::Integer: i_ = other.i_; break; default: _ASSERT(false); break; >> MyVariant(MyVariant&& other) : kind_(other.kind_) < switch (kind_) < case Kind::None: break; case Kind::A: new (&a_) A(move(other.a_)); break; case Kind::B: new (&b_) B(move(other.b_)); break; case Kind::Integer: i_ = other.i_; break; default: _ASSERT(false); break; >other.kind_ = Kind::None; > MyVariant& operator=(const MyVariant& other) < if (&other != this) < switch (other.kind_) < case Kind::None: this->~MyVariant(); break; case Kind::A: *this = other.a_; break; case Kind::B: *this = other.b_; break; case Kind::Integer: *this = other.i_; break; default: _ASSERT(false); break; > > return *this; > MyVariant& operator=(MyVariant&& other) < _ASSERT(this != &other); switch (other.kind_) < case Kind::None: this->~MyVariant(); break; case Kind::A: *this = move(other.a_); break; case Kind::B: *this = move(other.b_); break; case Kind::Integer: *this = other.i_; break; default: _ASSERT(false); break; > other.kind_ = Kind::None; return *this; > MyVariant(const A& a) : kind_(Kind::A), a_(a) < >MyVariant(A&& a) : kind_(Kind::A), a_(move(a)) < >MyVariant& operator=(const A& a) < if (kind_ != Kind::A) < this->~MyVariant(); new (this) MyVariant(a); > else < a_ = a; >return *this; > MyVariant& operator=(A&& a) < if (kind_ != Kind::A) < this->~MyVariant(); new (this) MyVariant(move(a)); > else < a_ = move(a); >return *this; > MyVariant(const B& b) : kind_(Kind::B), b_(b) < >MyVariant(B&& b) : kind_(Kind::B), b_(move(b)) < >MyVariant& operator=(const B& b) < if (kind_ != Kind::B) < this->~MyVariant(); new (this) MyVariant(b); > else < b_ = b; >return *this; > MyVariant& operator=(B&& b) < if (kind_ != Kind::B) < this->~MyVariant(); new (this) MyVariant(move(b)); > else < b_ = move(b); >return *this; > MyVariant(int i) : kind_(Kind::Integer), i_(i) < >MyVariant& operator=(int i) < if (kind_ != Kind::Integer) < this->~MyVariant(); new (this) MyVariant(i); > else < i_ = i; >return *this; > Kind GetKind() const < return kind_; >A& GetA() < _ASSERT(kind_ == Kind::A); return a_; >const A& GetA() const < _ASSERT(kind_ == Kind::A); return a_; >B& GetB() < _ASSERT(kind_ == Kind::B); return b_; >const B& GetB() const < _ASSERT(kind_ == Kind::B); return b_; >int& GetInteger() < _ASSERT(kind_ == Kind::Integer); return i_; >const int& GetInteger() const < _ASSERT(kind_ == Kind::Integer); return i_; >private: Kind kind_; union < A a_; B b_; int i_; >; >; #pragma warning (pop) int main() < A a(1, "Hello from A"); B b(2, "Hello from B"); MyVariant mv_1 = a; cout ; mv_1 = move(b); cout > c; >
Не union удается сохранить ссылку. Кроме того, не union поддерживается наследование. Это означает, что вы не можете использовать union как базу classили наследовать от другого classили иметь виртуальные функции.
Инициализация union
Можно объявить и инициализировать union в той же инструкции, назначив выражение, заключенное в фигурные скобки. Выражение вычисляется и назначается первому полю union.
#include using namespace std; union NumericType < short iValue; long lValue; double dValue; >; int main() < union NumericType Values = < 10 >; // iValue = 10 cout /* Output: 10 3.141600 */
Он NumericType union упорядочен в памяти (концептуально), как показано на следующем рисунке:

На схеме показаны 8 байт данных. Двойной тип dValue занимает весь 8 байт. Тип long lValue занимает первые 4 байта. Короткий тип iValue занимает первый байт.
Анонимные union
Анонимный union объявлен без class-name или declarator-list .
Имена, объявленные в анонимном, union используются напрямую, например переменные nonmember. Это означает, что имена, объявленные в анонимном, union должны быть уникальными в окружающем область.
Анонимный union имеет следующие ограничения:
- Если он объявлен в файле или пространстве имен область, он также должен быть объявлен как static .
- У него могут быть только public члены; наличие private и protected члены в анонимном union генерации ошибок.
- Он не может иметь функции-члены.
Зачем нужно объединение (union)?
уже не получится получить их назад — все перемешается? Способ сэкономить пару байтов или пару тактов и при этом сохранить читабельность? Не писать 4 разных функции, а написать одну, которая принимает union и в ней уже решать, что делать? В таком случае не проще ли принять void * и потом кастануть в тот тип, какой нужен? Как пример «Просто кастануть» приведу такой код: Классический пример:
typedef enum < STR, INT >tType; typedef struct < tType typ; // typ is separate. union < int ival; // ival and sval occupy same memory. char *sval; >; > tVal; void printer(tVal uni) < if (uni.type == INTEGER) // blah-blah uni.ival // Используем integer else ini.sval // В противном случае >
Функцию принтер можно переписать как-то так:
void printer(void* data, tType typ) < if (tType == INTEGER) < (int*)data // Чего-то делаем >>
Другой пример:
union < int a; int b; int c; >bar; bar.a = 20; bar.b = 50; // Значение a потеряли :(
Опять-таки какой в этом смысл, если я могу сначала завести отдельную переменную int a = 20; а затем изменю ее значение a = 50; и эффект точно такой же? Выглядит как сильное колдовство.
Отслеживать
user194374
задан 5 фев 2017 в 1:31
6,399 4 4 золотых знака 35 35 серебряных знаков 57 57 бронзовых знаков
6 ответов 6
Сортировка: Сброс на вариант по умолчанию
Union-ы (объединения) используют в двух случаях:
-
Для создания «универсального» типа данных, способного хранить не единственный, а один из предопределённых типов. Для этого к объединению добавляют целочисленное поле, указывающее тип хранимых в настоящий момент данных:
struct variant < union tag_value < int intValue; float floatValue >value; unsigned storedType; >;
6.5.2.3 Структуры и члены объединений 95) Если поле, используемое для чтения содержимого объекта-объединения, не является полем, использованным ранее для записи значения в этот объект, требуемая часть внутреннего представления объекта интерпретируется в соответствием с представлением затребованного типа согласно 6.2.6 (данный процесс известен также как type punning). Это представление может приводить к неопределённому поведению. 6.5.2.3 Structure and union members 95) If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.
9.5 Объединения [class.union] В объединении в каждый момент времени может быть активно только одно нестатическое поле; вследствие этого в объединении в любой момент времени может находиться не более одного значения. 9.5 Unions [class.union] In a union, at most one of the non-static data members can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.