Объединения и перечислимые типы в языке С
Теоретическая часть
14.1. Объединения
Объединение ( union ) – это тип, который позволяет хранить различные типы данных в одном и том же пространстве памяти (но не одновременно)[14.1].
Объединения образуются во многом подобно структурам. Существуют шаблоны объединений и переменные типа объединения. Они могут быть определены с помощью одного или двух действий, причем в последнем случае используется дескриптор объединения [14.1].
Пример шаблона объединения с дескриптором hold:
union hold { int digit; double bigf; char letter; };
Объединение может хранить значение типа int, или double, или char. Структура с такими же полями способна хранить все типы одновременно. Пример определения трех переменных объединения:
union hold fit; // переменная объединения типа hold union hold save[10];// массив из 10 переменных объединения union hold *ptr; // указатель на переменную типа hold
Первое объявление создает единственную переменную fit. Компилятор выделяет пространство памяти, достаточное для того, чтобы хранить наибольшую из описанных вариантов, а именно тип double, который требует для себя обычно 8 байт. Второе объявление save[10] создает массив с 10 элементами, каждый из которых имеет размер в 8 байт. Третье объявление создает указа-тель, который может содержать адрес объединения hold.
Рассмотрим варианты инициализации объединения [14.1]:
union hold valA;// создали переменную valA по шаблону union hold valA.letter = 'R'; union hold valB = valB;// инициализация одного объединения другим union hold valC = {88};// инициализация числового элемента
Рассмотрим варианты использования объединения:
union hold fit; fit.digit = 23; // Число 23 хранится в переменной fit, 2 байта fit.bigf = 6.78; // Число 23 затерто, хранится 6.78, 8 байтов fit.letter = 'h';// Число 6.78 затерто, хранится символ h,1 байт
Операция точки показывает, какой тип данных используется в текущий момент [14.1]. За один раз запоминается только одно значение. Нельзя одновременно хранить значение типа char и значение типа int, даже если для этого имеется достаточно пространства (памяти для 8 байт). Следить за тем, какие значения на текущий момент хранятся в объединении, входит в обязанности программиста. Бывает, что на различных этапах выполнения программы одни переменные могут быть не нужны, в то время как другие, наоборот, используются только в текущей части программы, поэтому объединения экономят пространство, вместо того чтобы впустую тратить память на не использующиеся в данный момент переменные.
При инициализации полей объединения вместо операции точки можно использовать операцию стрелки, если используется указатель на объединение. В частности, для рассмотренных примеров:
int x; ptr = &fit; x = ptr->digit;
В содержимом объединения (в качестве инициализаторов) могут быть структуры. При этом по правилам использования объединения обращаться можно будет только к одной из структур.
Фактически объединение является структурой, в которой все элементы имеют нулевое смещение от ее начала. Она имеет достаточную длину, чтобы в нее поместился самый длинный элемент, и при этом выравнивание выполняется правильно для всех типов данных в объединении [14.2]. Над объединениями разрешено выполнять те же операции, что и над структурами: присваивать или копировать как единое целое, брать адрес и обращаться к отдельным элементам.
Объединения могут употребляться в структурах и массивах и наоборот. Способ обращения к члену объединения в структуре (или к члену структуры в объединении) полностью идентичен обращению к элементу вложенной структуры. Объединение можно инициализировать только данными того типа, который имеет его первый элемент [14.2].
Смысловое отличие объединения от структуры состоит в том, что записать информацию в объединение можно с помощью одного из его элементов, а выбрать данные из того участка памяти можно с помощью другого элемента того же объединения [14.3].
К объединениям может быть применен оператор typedef, после чего можно вводить обозначения объединяющих типов, не требующие применения служебного слова union. Рассмотрим пример:
typedef union data { char str[79+1]; int a; double x; } new_data;
Определения новых переменных (например, student1, student2 ) объединений будут выглядеть таким образом:
new_data student1, student2;
Объединения не относятся ни к скалярным данным, ни к данным агрегирующих типов [14.3].
Объединения не могут сравниваться операциями "==" и "!=" по тем же самым причинам, что и структуры, поскольку элементы объединения не обязательно хранятся в последовательных байтах памяти.
Объединения часто используются для специального преобразования типов, поскольку к хранящимся в объединении данным можно обращаться разными способами [14.4].
14.2. Перечислимые типы
Перечислимый (enumerated) и тип служит для объявления символических имен, представляющих целочисленные константы [14.1].
Можно сказать, что enumerated type (перечислимый тип) – это тип данных, заданных списком принадлежащих ему значений.
Назначение перечислимых типов заключается в том, чтобы повысить удобочитаемость программы [14.1]. Синтаксис в этом случае аналогичен синтаксису, который используется для описания структур.
Примеры объявления перечислимого типа:
enum spectrum {red, orange, yellow, green, blue, violet}; enum spectrum color;
Первое объявление устанавливает spectrum как имя дескриптора, который позволяет использовать enum spectrum в качестве имени типа. Второе объявление делает color переменной этого типа. Идентификаторы, заключенные в фигурные скобки, перечисляют возможные значения, которые может принимать переменная spectrum. Соответственно, возможными значениями color являются red, orange, yellow и т. д. Но эти возможные значения являются целочисленными, т.е. 0, 1, 2, 3, 4, 5. Другими словами, значения в enum начинаются с 0, если не задано иное число, и всегда увеличиваются на 1.
В общем случае перечислимые константы имеют тип int, но перечислимые переменные не так жестко привязаны к целочисленному типу данных, поскольку этот тип может содержать перечислимые константы. Например, перечислимые константы переменной spectrum имеют диапазон 0 ... 5 (как в массиве), поэтому компилятор может выбрать тип unsigned char для представления переменной color.
В языке программирования С к перечислимой переменной можно применять операции инкрементирования "++" и декрементирования "––". Например,
for (color = red; color <= violet; ++color) ...;
По умолчанию константам в перечислимом списке присваиваются целые значения 0, 1, 2 и так далее. В то же время возможны и присваиваемые значения, например:
enum levels {low = 100, medium = 500, high = 2000};
Если назначить конкретное значение одной из констант, то все следующие константы будут пронумерованы последовательно в возрастающем порядке, например:
enum feline {cat, lynx = 10, puma, tiger};
В этом случае cat (кошка) получает значение 0 по умолчанию, lynx (рысь), puma (пума), tiger (тигр), соответственно, получают значения 10, 11, 12.
Перечисления особенно полезны там, где не требуется преобразования значений (целого типа) в имена (массив символов). В частности, перечисления часто используются в компиляторах для создания таблицы соответствия символов [14.4].
Практическая часть
Пример 1. Для переменной типа объединения предусмотрите ввод, и вывод элементов ее полей.
Программный код решения примера:
#include <stdio.h> #include <conio.h> #include <string.h> #define N 79 union hold { char str[N+1]; double bigf; char ch; int digit; }; int main (void) { double D2; int i = 0, digit2; char str2[80], ch2; union hold fit, *PTR = &fit; //PTR = &fit; // вариант взятия адреса printf("\n\t Fields of the \"union\":\n \ 1) string, 2) double, 3) character, 4) integer\n"); do { printf("\n Enter %d field of the \"union\": ", i+1); _flushall(); if (i == 0) { gets_s(str2, N); strcpy_s(PTR->str, strlen(str2) + 1, str2); printf(" The first field: %s\n", PTR->str); i++; } else if (i == 1) { scanf_s("%lf", &D2); printf(" The second field: %1.4f\n", PTR->bigf = D2); i++; } else if (i == 2) { scanf_s("%c", &ch2); printf(" The third field: %c\n", PTR->ch = ch2); i++; } else { scanf_s("%d", &digit2); printf(" The fourth field: %d\n", PTR->digit = digit2); i++; } } while (i < 4); printf("\n 1 field: %s\n 2 field: %1.4f\n \ 3 field: %c\n 4 field: %d\n", \ PTR->str, PTR->bigf, PTR->ch, PTR->digit); printf("\n Press any key: "); _getch(); return 0; }
Результат выполнения программы показан на рис. 14.1.
Как видно из результата выполнения программы, заполнение четырех полей объединения возможно поочередно. После "прохода" всех полей строчные поля не сохранились.