4.3. Объединения - StriderAJR/StudentCpp GitHub Wiki
Объединение - пользовательский тип данных, позволяющий нескольким переменным занимать один участок памяти.
Зачем? Для экономии памяти, конечно же.
Вот пример объединения:
union ExampleUnion
{
char oneByte;
short twoBytes;
int fourBytes;
};
union
- ключевое слово для создания типа данных объединения.
ExampleUnion
- название объединения на ваш выбор.
Далее идет перечисление переменных, которые "объединены".
Суть объединения заключается в том, что несмотря на то, что мы создали внутри объединения 3 переменные, хранится они будут в одной области памяти.
namespace Unions
{
void main()
{
ExampleUnion e;
cout << sizeof(e) << endl;
e.oneByte = '#';
cout << e.oneByte << endl; // #
cout << e.twoBytes << endl; // неопределенное значение
cout << e.fourBytes << endl; // неопределенное значение
cout << endl;
e.oneByte = 0; // Обнулили для чистоты эксперимента, чтобы вы не думали, что '#' так и осталась в памяти
e.twoBytes = 36;
cout << e.oneByte << endl; // $
cout << e.twoBytes << endl; // 36
cout << e.fourBytes << endl; // неопределенное значение
cout << endl;
e.fourBytes = 37;
cout << e.oneByte << endl; // %
cout << e.twoBytes << endl; // 37
cout << e.fourBytes << endl; // 37
cout << endl;
e.fourBytes = 65825;
cout << e.oneByte << endl; // !
cout << e.twoBytes << endl; // 289
cout << e.fourBytes << endl; // 65582
cout << endl;
union Distance
{
int km;
long long m;
};
Distance japan, china;
japan.m = 256000;
china.km = 200;
japan.km = japan.m / 1000;
china.m = china.km * 1000;
}
}
В начале создается переменная типа объединения точно также как и любая другая переменная.
Интересно, а сколько байт памяти было выделено под такую переменную?
cout << sizeof(e) << endl;
На экран выведется число "4", т.е. 4 байта.
В объединении есть 3 переменных по 1, 2 и 4 байта соответственно типам данных char
, short
и int
.
И все они должны храниться в одной области памяти, а значит под переменную объединения нужно выделить столько памяти, сколько требуется для наибольшей переменной внутри объединения.
Итак, как же это будет выглядеть в памяти?
Представим 4 байта под переменную e
в памяти в таком виде:
Сейчас память пуста (ну на самом деле там либо нули, либо мусор в зависимости от компилятора, но не суть) Заполним ее:
e.oneByte = '#';
Код этого символа равен 35 в таблице ASCII
Теперь память выглядит следующим образом:
Только первый байт заполнился числом 35, что соответствует коду символа '#', а остальные остались нетронутыми... Т.е. так и остались непроинициализированы, и что выведется, если мы попробуем их вывести на экран - неизвестно.
e.twoBytes = 36;
И вот что будет в памяти (twoBytes
затрагивает уже 2 ячейки памяти, т.е. 2 байта):
По правилам линейности памяти слева располагается младший адрес, но short
занимает 2 байта, т.е. у нас в одном байте будет храниться число 36, а другой байт - незначащий ноль.
Из математики в целых числах незначащие нули располагаются в старшей части, но компьютер располагает старшие адреса справа - нумерация ячеек идет слева направо. Поэтому число 36 идет в первой, младшей ячейке, а незначащий ноль - в левой, старшей ячейке.
Когда мы обращаемся к e.oneByte
берется только 1 байт, причем первый, а там лежит число 36, что равно коду '#'.
Машине все равно откуда и какое число лежит в памяти.
Программа взяла запрашиваемый байт из памяти и преобразовала в символ.
И хотя мы обнулили до этого переменную oneByte
, там оказалось число, потому что когда мы инициализировали переменную twoByte
, изменилось 2 байта, один из которых используется переменной oneByte
.
И последний вопрос, который возникает у всех: А зачем такие сложности?
Ответ: Для экономии памяти
Если вы знаете, что вам понадобится только одна из переменных единовременно, а остальные нет, но какая из них вы не знаете заранее, то объединения неплохо позволят сэкономить память.
Реальный пример:
Допустим в программе нужно хранить расстояние до различных стран, но в каком-то случае это расстояние нужно хранить в метрах (для точных вычислений), а в каких-то в километрах.
1 км = 1000 м
Поэтому если взять переменную, хранящую расстояние типа int
, то в худшем случае расстояние до нее же будет в 1000 раз больше и типа данных int
уже не хватит, больше int
есть тип данных long long
:
В любой момент времени мы можем перезаписать значения, если в программе нам понадобилось переключить формат расстояния. Естественно старые значения мы потеряем!
Если бы мы хранили расстояния в виде структуры, то нам понадобилось бы 4 + 8 = 12 байт памяти А так объединение потребовало только 8 байт. В данном случае выгода 50%
Минус объединений же заключается в том, что необходимо знать, в каком поле хранятся "полезные" данные, иначе есть вероятность считать "мусор", т.е. неправильные данные, неполные, лишние или непроинициализированные.