Указатели на структуры и оператор стрелки
Урок соединяет две большие темы — структуры и указатели: как работает указатель на структуру, зачем нужен оператор стрелки и как создавать структуры в динамической памяти.
Оператор стрелки p->field — это просто удобная запись для (*p).field: «разыменуй указатель и возьми поле». Через указатели на структуры строятся списки, деревья и все сложные структуры данных C.
Когда у нас есть указатель на структуру, доступ к полю выглядит иначе. Можно написать (*p).x — разыменовать и взять поле, — но это громоздко. Для этого придумали оператор стрелки:
struct Point { int x, y; };
struct Point pt = {3, 4};
struct Point *p = &pt; // указатель на структуру
printf("%d\n", (*p).x); // 3 — длинная запись
printf("%d\n", p->x); // 3 — то же через стрелку
p->x = 100; // меняем поле через указатель
printf("%d\n", pt.x); // 100 — изменился оригинал
Запись p->x полностью эквивалентна (*p).x, но читается легче. Это самый частый способ обращения к полям, потому что в реальном коде структуры почти всегда передаются и хранятся через указатели.
Как работает под капотом
Покажем, что стрелка — не магия, а сокращение. И зачем структуры размещают в динамической памяти:
struct Point *p = &pt;
p ----> pt: [ x: 3 ][ y: 4 ]
^
p хранит адрес структуры
p->x означает: (*p).x
|
1. *p — идём по адресу к структуре
2. .x — берём поле x
итог: 3
Динамическая структура:
struct Point *q = malloc(sizeof(struct Point));
q ----> [ ? ? ] (на куче)
q->x = 5; q->y = 9; // заполняем поля
free(q); // освобождаем
Размещение структуры в куче через malloc — основа динамических структур данных. Например, у узла связного списка есть поле-указатель на следующий узел, и каждый узел создаётся отдельным malloc. Так строятся списки, деревья, графы.
// Узел связного списка
struct Node {
int data;
struct Node *next; // указатель на такой же узел
};
struct Node *n = malloc(sizeof(struct Node));
n->data = 42;
n->next = NULL; // пока конец списка
printf("%d\n", n->data);
free(n);
Частые ошибки
- Точка вместо стрелки.
p.xдля указателя — ошибка; нужноp->xили(*p).x. - Разыменование без выделения.
struct Node *n; n->data = 5;безmalloc— запись по мусорному адресу. - Забыли free для каждого узла. В списках и деревьях нужно освобождать каждый узел, иначе утечка.
- Скобки в
(*p).x. Без скобок*p.xтрактуется неверно из-за приоритета — точка сильнее звёздочки.
Best practices
- Используйте стрелку
->для указателей — это стандарт и читается яснее, чем(*p).field. - Сразу после
mallocструктуры проверяйте результат наNULLи инициализируйте поля, особенно поля-указатели — вNULL. - Для каждой динамически созданной структуры предусмотрите парный
free; в связных структурах освобождайте узлы по порядку.
Связный список — идеальная иллюстрация указателей на структуры. Смоделируем его на Python через словари-узлы со ссылкой next.
# Узел списка как структура: data + ссылка на следующий
def make_node(data):
return {"data": data, "next": None} # next = NULL
# собираем список 1 -> 2 -> 3
head = make_node(1)
head["next"] = make_node(2)
head["next"]["next"] = make_node(3)
# проход по списку через "указатель" cur
cur = head
while cur is not None: # пока не NULL
print(cur["data"], end=" -> ")
cur = cur["next"] # cur = cur->next
print("NULL")
Та же логика на Python ▶ — поле next играет роль указателя struct Node *next, а проход cur = cur["next"] — это cur = cur->next в C. Так устроены все связные структуры.
Полный жизненный цикл связного списка
Указатели на структуры раскрываются в полную силу именно на динамических структурах, и связный список — канонический пример. Его сила в том, что вставка и удаление элемента в середину не требуют сдвига данных, как в массиве: достаточно перенаправить пару указателей next. Платой за это служит потеря быстрого доступа по индексу — чтобы добраться до n-го элемента, приходится пройти всю цепочку с начала. Но главная практическая трудность — корректное освобождение: чтобы не потерять остаток списка, перед free текущего узла нужно сохранить указатель на следующий, иначе после освобождения вы уже не сможете до него добраться. Эта аккуратность с порядком операций — типичный навык работы с динамическими структурами в C, и он переносится дальше на деревья, очереди и хеш-таблицы.
Итоги
Оператор стрелки p->field — это сокращение для (*p).field: разыменовать указатель и взять поле. Структуры почти всегда используют через указатели, в том числе размещая их в куче через malloc. Указатель-поле внутри структуры (struct Node *next) — основа связных списков, деревьев и графов. Главные правила — стрелка для указателей, проверка malloc на NULL и парный free для каждого узла.