typedef, enum и union

typedef: своё имя для типа

typedef создаёт псевдоним типа. Чаще всего его используют для структур:

typedef struct
{
    int id;
    int course;
    double score;
} Student;

Student s;
Student group[100];

Без typedef пришлось бы писать struct Student каждый раз. typedef не создаёт новый тип, а только удобное имя.

Можно задавать псевдонимы и для простых типов, но это редко улучшает читаемость:

typedef unsigned long long ull;
typedef double Score;

enum: именованные целые константы

enum задаёт набор именованных целых значений:

typedef enum
{
    STATUS_OK,
    STATUS_NOT_FOUND,
    STATUS_NO_MEMORY,
    STATUS_INVALID_INPUT
} Status;

По умолчанию значения идут 0, 1, 2, …. Можно задать явно:

typedef enum
{
    VALUE_INT = 1,
    VALUE_DOUBLE = 2
} ValueType;

enum удобно использовать для кодов возврата функций:

Status remove_by_id(Node **head, int id)
{
    if (head == NULL || *head == NULL)
    {
        return STATUS_INVALID_INPUT;
    }

    /* ... */

    return STATUS_OK;
}

Внутри enum — целое число, поэтому его можно сравнивать и печатать через %d, но лучше использовать именованные константы, а не «магические» числа.

union: несколько полей в одной памяти

union похож на структуру, но все его поля занимают одну и ту же область памяти. В каждый момент времени хранится только одно значение:

typedef union
{
    int i;
    double d;
} Number;

Number n;
n.i = 42;        /* сейчас хранится int */
n.d = 3.14;      /* int перезаписан */

Размер union равен размеру самого большого поля (с учётом выравнивания). Читать нужно то поле, которое было записано последним; иначе результат не определён.

Tagged union: значение + тип

Чтобы понимать, какое поле union действительно хранится, рядом кладут enum-тег:

typedef enum
{
    VALUE_INT = 1,
    VALUE_DOUBLE = 2
} ValueType;

typedef struct
{
    ValueType type;
    union
    {
        int i;
        double d;
    } as;
} Value;

Создание и вывод:

Value v;
v.type = VALUE_INT;
v.as.i = 42;

if (v.type == VALUE_INT)
{
    printf("%d\n", v.as.i);
}
else if (v.type == VALUE_DOUBLE)
{
    printf("%.2f\n", v.as.d);
}

Такой тип используется в high-вариантах ЛР3: кольцевой список хранит Value, а type показывает, как читать as.

Массив значений разных типов

Value values[3];

values[0].type = VALUE_INT;
values[0].as.i = 10;

values[1].type = VALUE_DOUBLE;
values[1].as.d = 2.5;

values[2].type = VALUE_INT;
values[2].as.i = -7;

for (int i = 0; i < 3; i++)
{
    if (values[i].type == VALUE_INT)
    {
        printf("%d %d\n", VALUE_INT, values[i].as.i);
    }
    else
    {
        printf("%d %.2f\n", VALUE_DOUBLE, values[i].as.d);
    }
}

enum и union в структуре

typedef struct
{
    int id;
    ValueType type;
    union
    {
        int i;
        double d;
    } as;
} Record;

Обращение к полям: r.type, r.as.i, r.as.d. Перед чтением as всегда проверяйте type.

Частые ошибки

ОшибкаЧто не так
забыли ; после } enum/union/structошибка компиляции
читают не то поле unionнеопределённое поведение
путают enum и строкуenum — целые константы, не текст
используют «магические» числа вместо enumкод труднее читать
typedef struct { ... }; без имени после }нет псевдонима, если он нужен
сравнивают union целикомв C нельзя сравнивать объединения целиком

Документация

Что запомнить

  1. typedef создаёт удобное имя типа, но не новый тип.
  2. enum — именованные целые константы; удобен для статусов и типов.
  3. union хранит одно из полей в общей памяти.
  4. Tagged union = enum-тег + union; перед чтением проверяйте тег.
  5. В lab3 Value хранит int или double, строк нет.

Подтверждение

—