typedef делает одну небольшую вещь: даёт уже существующему типу ещё одно имя. Новый тип не создаётся, память не отводится, и в поведении значения ничего не меняется. Это инструмент именования, а в C, где встроенные имена типов длинны (unsigned long long, struct Employee, void (*)(int)), хорошее имя стоит немало.
Базовая форма
Синтаксис читается как объявление переменной с приделанным спереди typedef. Там, где стояло бы имя переменной, вы пишете новое имя типа:
unsigned long count; // объявляет переменную с именем count
typedef unsigned long ulong; // объявляет ТИП с именем ulong
Это всё правило целиком, и оно объясняет любой typedef, который вам встретится, включая странные на вид ниже на этой странице.
Обратите внимание, чего пара Celsius/Fahrenheit вам не даёт: типобезопасности. Оба типа — просто double, поэтому передача значения в Фаренгейтах туда, где ожидаются Цельсии, спокойно скомпилируется. Имена документируют намерение для людей, но не создают проверки.
Идиома typedef struct
Именно из-за неё typedef вообще встречается в большинстве программ на C. В C имя типа структуры включает ключевое слово:
struct Point { int x; int y; };
struct Point p; /* слово "struct" каждый раз */
typedef это сворачивает:
Прочитайте это по правилу из начала: уберите typedef — и получится struct Point {...} Point;, объявление переменной с именем Point. Верните typedef — и Point становится именем типа.
С тегом или без
Тег можно вообще опустить, получив анонимную структуру, у которой есть только имя из typedef:
typedef struct {
int x;
int y;
} Point; /* никакого "struct Point" не существует - только "Point" */
Это опрятнее, и для простой записи с данными такой стиль распространён. Но он ломается в тот момент, когда структуре нужно упомянуть саму себя, потому что имя из typedef недоступно, пока его собственное объявление не завершится:
/* НЕ компилируется: "Node" внутри собственных скобок ещё не тип. */
typedef struct {
int value;
Node *next;
} Node;
Оставьте тег — и самоссылка заработает, потому что struct Node доступен сразу, как только тег увиден:
Практическое правило: сохраняйте тег. Он стоит одного слова, совпадает с именем из typedef, так что путаницы нет, и оставляет открытой дверь для самоссылок и предварительных объявлений в заголовках. Во что вырастает этот связанный узел, см. в статье структуры и указатели.
Предварительные объявления и непрозрачные типы
Поскольку у указателя на структуру известный размер даже тогда, когда содержимое структуры неизвестно, заголовок может выдать тип, не раскрывая его полей:
/* stack.h */
typedef struct Stack Stack; /* объявлено, но не определено */
Stack *stack_create(void);
void stack_push(Stack *s, int value);
int stack_pop(Stack *s);
void stack_destroy(Stack *s);
Полное struct Stack { ... }; живёт в stack.c, и никто снаружи не может тронуть его поля. Такой шаблон непрозрачного типа — способ, которым библиотеки на C обеспечивают инкапсуляцию, и ровно им является FILE в стандартной библиотеке: вы получаете FILE * от fopen, и вам ничего не сообщают о том, что внутри. См. работа с файлами.
typedef для перечислений и объединений
Та же идиома применима к двум другим составным видам:
Без typedef это были бы enum Color c; и union Number n;. Чем они на самом деле занимаются, см. в статьях перечисления и объединения.
typedef для указателя на функцию
Вот здесь typedef перестаёт быть удобством и становится почти необходимостью. Сырой синтаксис указателя на функцию знаменит своей нечитаемостью:
void (*handler)(int, const char *); /* переменная */
void register_cb(void (*cb)(int, const char *)); /* параметр */
Назовите тип один раз — и оба становятся читаемыми:
Примените правило «убрать typedef» ещё раз: сама по себе строка int (*IntBinaryOp)(int, int); объявила бы переменную с именем IntBinaryOp, указывающую на функцию, которая принимает два int и возвращает int. С typedef это становится именем типа. Подробнее — в статье указатели на функции.
typedef против #define
Для простых псевдонимов они выглядят взаимозаменяемыми, но таковыми не являются:
#define INT_PTR int *
typedef int *int_ptr;
INT_PTR a, b; /* раскрывается в: int *a, b; -- b обычный int! */
int_ptr c, d; /* и c, и d являются int * */
#define — слепая текстовая подстановка, выполняемая до того, как компилятор увидит код; typedef — настоящее объявление, которое компилятор понимает и которое относится к каждому имени в списке. Используйте typedef для типов, а #define оставьте для констант и макросов.
Когда typedef вредит
Typedef может и скрыть информацию, которая нужна читателю. Самый острый пример — сокрытие указателя:
typedef struct Node *NodeRef; /* звёздочка исчезает */
NodeRef n = get_node();
Глядя на NodeRef n, читатель не может понять, может ли n быть NULL, нужно ли его освобождать, разделяют ли n и его копия один объект и как обращаться к полям — через . или ->. На все четыре вопроса мгновенно отвечает запись struct Node *n.
Стандартная библиотека с этим согласна: она делает typedef для структуры (FILE) и оставляет звёздочку в месте использования (FILE *fp). Отсюда вытекают два разумных ориентира:
- Стоит делать typedef для структур, объединений, перечислений и указателей на функции: убранный шум не несёт информации.
- Не стоит делать typedef для указателя просто чтобы сократить запись, и не стоит переименовывать базовый тип, чтобы его замаскировать (
typedef int Boolean;провоцирует кого-нибудь положить туда7— используйтеboolиз<stdbool.h>, о чём в статье логические значения).
Собственные имена фиксированной ширины из <stdint.h> (uint32_t, int64_t, size_t) — все typedef, и они лучший аргумент в пользу этого средства: они говорят ровно то, чем являются, они переносимы, и ничего не скрыто.
Часто задаваемые вопросы
Что делает typedef в C?
Он даёт существующему типу второе имя. typedef unsigned long ulong; означает, что с этого момента ulong и unsigned long — один и тот же тип. Ни нового типа, ни новой памяти при этом не появляется, только более короткий или более выразительный способ записать то, что у вас уже есть.
Что такое идиома typedef struct в C?
typedef struct Point { int x; int y; } Point; одной инструкцией объявляет структуру и называет тип Point, так что можно писать Point p; вместо struct Point p;. Сохранять тег (struct Point) важно, когда структуре нужно сослаться на саму себя, как узлу связного списка.
Чем typedef отличается от #define?
typedef обрабатывается компилятором и создаёт настоящий псевдоним типа; #define — это текстовая подстановка, выполняемая препроцессором до компиляции. Разница кусается на указателях: #define PTR int* превращает PTR a, b; в int* a, b;, где указатель только a. А typedef int *PTR; делает указателями оба.
Стоит ли делать typedef для типа указателя в C?
Обычно нет. typedef struct Node *NodeRef; скрывает то, что тип является указателем, и читатель не может понять, может ли переменная быть NULL, нужно ли её освобождать и обращаться ли к полям через ->. В стандартной библиотеке FILE * сохраняет звёздочку видимой именно поэтому. Делайте typedef для структуры, а не для указателя на неё.