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%

Минус объединений же заключается в том, что необходимо знать, в каком поле хранятся "полезные" данные, иначе есть вероятность считать "мусор", т.е. неправильные данные, неполные, лишние или непроинициализированные.

⚠️ **GitHub.com Fallback** ⚠️