O typedef faz uma coisinha só: dá a um tipo que já existe outro nome. Nenhum tipo novo é criado, nenhuma memória é reservada, e nada sobre como o valor se comporta muda. É uma ferramenta de nomenclatura - e em C, onde os nomes de tipo embutidos são compridos (unsigned long long, struct Employee, void (*)(int)), um bom nome vale muito.
A forma básica
A sintaxe se lê como uma declaração de variável com typedef colado na frente. Onde iria o nome da variável, você põe o novo nome do tipo:
unsigned long count; // declara uma variavel chamada count
typedef unsigned long ulong; // declara um TIPO chamado ulong
Essa é a regra inteira, e ela explica todo typedef que você vai ler na vida, inclusive os de aparência estranha mais adiante nesta página.
Repare no que o par Celsius/Fahrenheit não compra: segurança de tipos. Os dois são apenas double, então passar um valor em Fahrenheit onde se espera Celsius compila numa boa. Os nomes documentam a intenção para humanos; eles não criam uma verificação.
O idioma typedef struct
Essa é a razão de a maior parte dos programas em C ter algum typedef. Em C o nome do tipo de uma struct inclui a palavra-chave:
struct Point { int x; int y; };
struct Point p; /* a palavra "struct" todas as vezes */
Um typedef encolhe isso:
Leia com a regra de antes: tire o typedef e você tem struct Point {...} Point;, a declaração de uma variável chamada Point. Devolva o typedef e Point vira um nome de tipo.
Com tag ou sem tag
Você pode deixar a tag totalmente de fora, criando uma struct anônima com apenas o nome do typedef:
typedef struct {
int x;
int y;
} Point; /* nao existe "struct Point" - apenas "Point" */
Isso é mais arrumado, e para um registro de dados simples é o estilo comum. Mas falha no momento em que a struct precisa mencionar a si mesma, porque o nome do typedef não é utilizável até a própria declaração dele terminar:
/* NAO compila: "Node" ainda nao e um tipo dentro das proprias chaves. */
typedef struct {
int value;
Node *next;
} Node;
Mantenha a tag e a autorreferência funciona, porque struct Node é utilizável assim que a tag é vista:
A regra prática: mantenha a tag. Ela custa uma palavra, casa com o nome do typedef para que nada fique confuso, e deixa a porta aberta para autorreferência e para declarações antecipadas em cabeçalhos. Veja structs e ponteiros para o que aquele nó encadeado vira.
Declarações antecipadas e tipos opacos
Como um ponteiro para uma struct tem tamanho conhecido mesmo quando o conteúdo da struct é desconhecido, um cabeçalho pode entregar um tipo sem revelar seus membros:
/* stack.h */
typedef struct Stack Stack; /* declarado, nao definido */
Stack *stack_create(void);
void stack_push(Stack *s, int value);
int stack_pop(Stack *s);
void stack_destroy(Stack *s);
A struct Stack { ... }; completa mora em stack.c e ninguém de fora pode tocar nos seus membros. Esse padrão de tipo opaco é como bibliotecas em C impõem encapsulamento, e é exatamente o que o FILE é na biblioteca padrão - você recebe um FILE * do fopen e não é informado de nada sobre o que há dentro. Veja manipulação de arquivos.
typedef para enums e unions
O mesmo idioma vale para os outros dois tipos compostos:
Sem o typedef, estes seriam enum Color c; e union Number n;. Veja enums e unions para saber o que eles realmente fazem.
typedef para um ponteiro de função
Aqui o typedef deixa de ser conveniência e fica perto de necessário. A sintaxe crua de um ponteiro de função é notoriamente difícil de ler:
void (*handler)(int, const char *); /* uma variavel */
void register_cb(void (*cb)(int, const char *)); /* um parametro */
Nomeie o tipo uma vez e os dois ficam legíveis:
Aplique de novo a regra de tirar o typedef: int (*IntBinaryOp)(int, int); sozinho declararia uma variável chamada IntBinaryOp que aponta para uma função que recebe dois ints e devolve um int. Com o typedef, isso vira o nome do tipo. Mais em ponteiros de função.
typedef vs #define
Eles parecem intercambiáveis para apelidos simples e não são:
#define INT_PTR int *
typedef int *int_ptr;
INT_PTR a, b; /* expande para: int *a, b; -- b e um int comum! */
int_ptr c, d; /* tanto c quanto d sao int * */
O #define é substituição cega de texto feita antes de o compilador ver o código; o typedef é uma declaração de verdade que o compilador entende, e ela se aplica a todos os nomes da lista. Use typedef para tipos e reserve #define para constantes e macros.
Quando um typedef atrapalha
Typedefs também podem esconder informação de que o leitor precisa. O exemplo mais nítido é esconder um ponteiro:
typedef struct Node *NodeRef; /* o asterisco desaparece */
NodeRef n = get_node();
Olhando para NodeRef n, um leitor não consegue dizer se n pode ser NULL, se precisa ser liberado, se n e uma cópia dele compartilham o mesmo objeto, ou se os membros são alcançados com . ou ->. As quatro perguntas são respondidas na hora por struct Node *n.
A biblioteca padrão concorda: ela faz typedef da struct (FILE) e deixa o asterisco no ponto de uso (FILE *fp). Duas diretrizes razoáveis decorrem disso:
- Faça typedef de structs, unions, enums e ponteiros de função - o ruído removido não carrega informação.
- Não faça typedef de um ponteiro só para encurtá-lo, e não faça typedef de um tipo básico para disfarçá-lo (
typedef int Boolean;convida alguém a guardar7nele - useboolde<stdbool.h>, tratado em booleanos).
Os próprios nomes de largura fixa da biblioteca padrão em <stdint.h> (uint32_t, int64_t, size_t) são todos typedefs, e são o melhor argumento a favor do recurso: dizem exatamente o que são, são portáveis e nada é escondido.
Perguntas frequentes
O que o typedef faz em C?
Ele dá a um tipo já existente um segundo nome. typedef unsigned long ulong; significa que ulong e unsigned long são o mesmo tipo dali em diante. Ele não cria nenhum tipo novo nem nenhum armazenamento novo - apenas uma forma mais curta ou mais descritiva de escrever um que você já tem.
O que é o idioma typedef struct em C?
typedef struct Point { int x; int y; } Point; declara a struct e nomeia o tipo como Point numa instrução só, então você pode escrever Point p; em vez de struct Point p;. Manter a tag (struct Point) importa quando a struct precisa se referir a si mesma, como faz um nó de lista encadeada.
Qual a diferença entre typedef e #define?
O typedef é tratado pelo compilador e cria um apelido de tipo de verdade; o #define é uma substituição de texto feita pelo pré-processador antes da compilação. Essa diferença morde com ponteiros: #define PTR int* faz PTR a, b; expandir para int* a, b; - só a é ponteiro. typedef int *PTR; faz os dois serem ponteiros.
Devo criar um typedef para um tipo ponteiro em C?
Normalmente não. typedef struct Node *NodeRef; esconde o fato de que o tipo é um ponteiro, então o leitor não sabe se a variável pode ser NULL, se precisa ser liberada, ou se precisa de ->. O FILE * da biblioteca padrão mantém o asterisco visível exatamente por essa razão. Faça typedef da struct, não do ponteiro para ela.