Перечисления, typedef и объединения
Урок собирает три полезных инструмента типов: enum (именованные целочисленные константы), typedef (псевдонимы типов для читаемости) и union (несколько полей, делящих одну память).
enum даёт имена числам, typedef даёт короткие имена типам, а union — это место, где разные поля лежат в одной и той же памяти, и активно только одно из них.
Перечисление enum заменяет «магические числа» осмысленными именами. Под капотом это обычные целые, начинающиеся с 0:
enum Direction { NORTH, EAST, SOUTH, WEST }; // 0, 1, 2, 3
enum Direction d = EAST;
if (d == EAST) {
printf("Идём на восток\n");
}
printf("EAST = %d\n", EAST); // 1
Ключевое слово typedef создаёт псевдоним для типа — чаще всего чтобы не писать struct каждый раз и сделать код чище:
typedef struct {
int x, y;
} Point; // теперь просто Point, без слова struct
Point p = {1, 2}; // вместо struct ... p
Объединение union внешне похоже на структуру, но все его поля делят одну и ту же память. Размер union равен размеру самого большого поля, и в каждый момент осмысленно лишь одно из них:
union Value {
int i;
float f;
char c;
};
union Value v;
v.i = 65;
printf("%d\n", v.i); // 65
v.f = 3.14f; // та же память — теперь там float
printf("%f\n", v.f); // 3.14 (но v.i уже испорчен!)
Как работает под капотом
Главное отличие union от struct — раскладка в памяти. Структура кладёт поля рядом, union — поверх друг друга:
struct (поля РЯДОМ): union (поля ПОВЕРХ друг друга):
struct { int i; float f; } union { int i; float f; char c; }
+--------+--------+ +------------------+
| i (4) | f (4) | | общая память |
+--------+--------+ | 4 байта |
размер = 8 байт +------------------+
i, f, c — все живут ЗДЕСЬ,
размер = 4 байта (макс. поле)
активно только одно за раз!
union экономит память, когда значение может быть одного из нескольких типов, но никогда — нескольких сразу. Чтобы знать, какое поле сейчас активно, union обычно сопровождают enum-тегом — получается «размеченное объединение».
Частые ошибки
- Чтение «не того» поля union. Записали
v.f, читаетеv.i— получите мусор: память одна, но интерпретация разная. - Забыли отслеживать активное поле union. Без enum-тега невозможно понять, что в union лежит сейчас.
- Полагаются на конкретные числа enum. Значения по умолчанию 0,1,2..., но их легко сбить ручным присваиванием — лучше не завязываться на них.
- Путают union со struct. Ожидают, что поля union независимы, а они делят память.
Best practices
- Используйте
enumвместо «магических чисел» — код становится самодокументируемым. - Применяйте
typedefдля структур и сложных типов, чтобы повысить читаемость, но не злоупотребляйте им для простых типов. - union сопровождайте enum-тегом активного типа («размеченное объединение») — это делает его безопасным.
enum и «размеченное объединение» удобно показать на Python: имена-константы и явный тег типа, который говорит, как читать значение.
# enum как именованные константы
NORTH, EAST, SOUTH, WEST = 0, 1, 2, 3
d = EAST
print("Направление EAST =", d)
# "размеченное объединение": тег + значение в одной ячейке
def make_value(tag, value):
return {"tag": tag, "data": value} # tag говорит, как читать data
v = make_value("int", 65)
if v["tag"] == "int":
print("Целое:", v["data"])
v = make_value("float", 3.14) # перезаписали — теперь float
if v["tag"] == "float":
print("Дробное:", v["data"])
Та же логика на Python ▶ — поле tag играет роль enum-метки, без которой нельзя понять, как трактовать значение. Именно так делают union безопасным в C.
Размеченное объединение на практике
Связка enum плюс union — это не академическая экзотика, а рабочий паттерн, на котором держатся интерпретаторы, парсеры и форматы данных. Представьте «значение», которое может быть числом, строкой или булевым: хранить под каждый вариант отдельное поле расточительно, ведь активно всегда одно. Решение — структура с двумя полями: enum-тег, говорящий «сейчас здесь число», и union, физически хранящий это число (или строку, или булево) в общей памяти. Любой код, работающий с таким значением, сперва смотрит на тег, а потом читает соответствующее поле union — так делают, например, JSON-библиотеки и движки динамических языков. Это даёт компактность union и безопасность типобезопасного доступа одновременно. Освоив этот паттерн, вы поймёте, как языки вроде Python представляют свои «универсальные» значения на уровне C.
Итоги
Три инструмента типов: enum даёт имена целочисленным константам и убирает «магические числа»; typedef создаёт псевдонимы типов ради читаемости; union размещает несколько полей в общей памяти, экономя её, но позволяя хранить лишь одно значение за раз. Безопасный union всегда сопровождают enum-тегом активного поля.