Você já reparou como é repetitivo escrever struct toda vez que declara uma variável de um tipo que você mesmo criou? struct Pessoa p; struct Data d; — a palavra struct aparece sem parar. O C oferece uma ferramenta para eliminar essa repetição e, mais do que isso, para dar aos seus tipos nomes limpos e expressivos: o typedef. É uma mudança pequena na sintaxe, mas grande no conforto de leitura, e um passo essencial antes de organizarmos código em projetos maiores.
O que o typedef faz
O typedef cria um apelido (um sinônimo) para um tipo existente. Ele não cria um tipo novo — apenas dá um segundo nome, mais curto ou mais claro, a um tipo que já existe:
#include <stdio.h>
typedef unsigned long long Grande;
int main(void) {
Grande populacao = 8000000000ULL; // em vez de "unsigned long long"
printf("%llu\n", populacao);
return 0;
}
Aqui, Grande passa a significar unsigned long long. Toda vez que você escreve Grande, o compilador entende unsigned long long. É puramente uma conveniência de nomenclatura — mas conveniência que, aplicada a tipos longos ou complicados, torna o código muito mais legível.
typedef com structs: o uso mais comum
O verdadeiro brilho do typedef aparece com structs, eliminando a repetição da palavra struct. Compare as duas formas:
#include <stdio.h>
// Sem typedef: exige "struct" em toda declaração
struct DataAntiga {
int dia, mes, ano;
};
// Com typedef: o tipo passa a se chamar apenas "Data"
typedef struct {
int dia, mes, ano;
} Data;
int main(void) {
struct DataAntiga d1; // precisa do "struct"
Data d2; // limpo: só "Data"
d1.dia = 15;
d2.dia = 20;
printf("%d %d\n", d1.dia, d2.dia);
return 0;
}
Na segunda definição, a struct não tem nome próprio (é uma struct anônima), mas o typedef lhe dá o apelido Data. A partir daí, você declara Data d2; sem a palavra struct. Essa é a forma que você verá na maioria do código C moderno — ela deixa os tipos definidos pelo usuário tão naturais de usar quanto os tipos nativos.
A forma com nome duplo
Há também uma variação que dá tanto um nome de struct quanto um apelido de typedef — útil quando a struct precisa se referir a si mesma (como veremos nas listas encadeadas):
typedef struct Pessoa {
char nome[50];
int idade;
} Pessoa;
Isso cria dois nomes para o mesmo tipo: struct Pessoa e Pessoa, que passam a ser intercambiáveis. Parece redundante, mas é necessário num caso específico: quando um campo da struct precisa ser um ponteiro para a própria struct. Dentro da definição, o apelido Pessoa ainda não existe (ele só fica pronto ao final), então o campo recursivo precisa usar struct Pessoa:
typedef struct No {
int valor;
struct No *proximo; // aqui "No" (o apelido) ainda não existe — usamos "struct No"
} No;
Guarde este exemplo com carinho: ele é, literalmente, o nó de uma lista encadeada, a estrutura que construiremos na Fase 5. O campo proximo aponta para outro nó do mesmo tipo, e é isso que permite encadear elementos. Por ora, apenas note por que precisamos do nome struct No além do apelido No.
Deixando funções e tipos mais legíveis
O typedef também melhora a legibilidade de assinaturas de funções. Uma função que recebe e devolve suas structs fica mais limpa:
#include <stdio.h>
typedef struct {
double x, y;
} Ponto;
Ponto somar(Ponto a, Ponto b) { // sem typedef: struct Ponto somar(struct Ponto a...)
Ponto r = {a.x + b.x, a.y + b.y};
return r;
}
int main(void) {
Ponto a = {1.0, 2.0};
Ponto b = {3.0, 4.0};
Ponto s = somar(a, b);
printf("(%.1f, %.1f)\n", s.x, s.y); // (4.0, 6.0)
return 0;
}
Sem o typedef, a assinatura seria struct Ponto somar(struct Ponto a, struct Ponto b) — funcional, mas carregada. Com ele, Ponto somar(Ponto a, Ponto b) lê quase como pseudocódigo. Em programas com muitas structs e funções, esse ganho se acumula e faz diferença real na clareza.
Uma questão de bom senso
O typedef é poderoso, mas convém usá-lo com critério. Para structs, enums e tipos longos, ele é quase sempre bem-vindo. Já para tipos simples, há um debate de estilo: alguns programadores gostam de typedef int Idade; para dar significado semântico; outros acham que esconder que Idade é apenas um int prejudica a clareza. Há um consenso, porém, sobre um uso perigoso — esconder ponteiros dentro de um typedef:
typedef int *PInt; // PInt é "ponteiro para int"
PInt a, b; // ARMADILHA: a é ponteiro, mas b também? Sim — mas não é óbvio!
O problema é que PInt a, b; faz ambos ponteiros, mas quem lê pode facilmente pensar que só a é ponteiro. Como o asterisco fica escondido dentro do apelido, a natureza de ponteiro deixa de ser visível no ponto de uso — e ponteiros são exatamente o que você mais quer enxergar claramente em C. A recomendação geral é: use typedef livremente para structs e enums, mas evite escondê-lo sobre tipos ponteiro, salvo em casos muito bem justificados (como ponteiros para função, que veremos adiante e onde o typedef de fato ajuda).
Um exemplo consolidando a Fase 3
Vamos reunir struct, enum e typedef num único tipo bem desenhado:
#include <stdio.h>
#include <string.h>
typedef enum {
ATIVO,
INATIVO,
SUSPENSO
} StatusConta;
typedef struct {
char titular[50];
int numero;
double saldo;
StatusConta status;
} Conta;
const char *nome_status(StatusConta s) {
switch (s) {
case ATIVO: return "ativa";
case INATIVO: return "inativa";
case SUSPENSO: return "suspensa";
default: return "desconhecida";
}
}
void extrato(const Conta *c) { // ponteiro const: lê sem copiar
printf("Conta %d (%s): R$ %.2f — %s\n",
c->numero, c->titular, c->saldo, nome_status(c->status));
}
int main(void) {
Conta minha = {.numero = 1001, .saldo = 750.0, .status = ATIVO};
strcpy(minha.titular, "Marcelo Fontes");
extrato(&minha);
return 0;
}
Repare como o código ficou expressivo: Conta, StatusConta, ATIVO — os nomes falam a linguagem do problema, sem struct ou enum poluindo as declarações, e a função extrato recebe um const Conta * (a técnica de leitura eficiente da aula Ponteiros para Struct e o Operador Seta). Esse é o padrão de código C limpo que buscaremos daqui em diante.
O que vem a seguir
Hoje aprendemos a dar nomes limpos aos nossos tipos com typedef, eliminando a repetição de struct e deixando declarações e funções mais legíveis — e vimos o cuidado a tomar para não esconder ponteiros. Guardamos também um exemplo que reaparecerá em breve: o nó autorreferente de uma lista. Na próxima aula, damos um passo diferente, mergulhando na primeira etapa da compilação que estudamos lá no início: o pré-processador. Vamos dominar #define, macros e as diretivas condicionais — as ferramentas que atuam antes mesmo de o compilador ver seu código.
Fontes e leituras recomendadas
- The C Programming Language (K&R), Kernighan & Ritchie — Cap. 6.7, sobre
typedef - cppreference —
typedefdeclaration — https://en.cppreference.com/w/c/language/typedef - Modern C, Jens Gustedt — discussão sobre nomeação de tipos e legibilidade — https://gustedt.gitlabpages.inria.fr/modern-c/
- Linux Kernel Coding Style — a visão (crítica) do kernel sobre
typedef— https://www.kernel.org/doc/html/latest/process/coding-style.html - 21st Century C, Ben Klemens (O'Reilly) — uso idiomático de
typedefem código moderno
Exercícios
Exercício 1
Crie um typedef chamado Temperatura para o tipo double. Declare uma variável desse tipo, atribua 36.5 e imprima-a. O programa se comporta diferente de usar double diretamente? Por quê?
Ver resposta
✓ Resposta:
#include <stdio.h>
typedef double Temperatura;
int main(void) {
Temperatura t = 36.5;
printf("%.1f\n", t);
return 0;
}
O programa se comporta exatamente igual a usar double diretamente, porque typedef não cria um tipo novo — apenas um apelido. Temperatura é double para todos os efeitos; o compilador os trata de forma idêntica. O ganho é só de legibilidade/semântica: o nome Temperatura comunica a intenção ao leitor.
Exercício 2
Reescreva esta definição usando typedef com struct anônima, de modo que se possa declarar Retangulo r; sem a palavra struct:
struct Retangulo { double largura, altura; };
Ver resposta
✓ Resposta:
typedef struct {
double largura, altura;
} Retangulo;
A struct fica anônima, e o apelido Retangulo permite declarar variáveis sem a palavra struct: Retangulo r;.
Exercício 3
Defina, com typedef, um tipo Aluno (struct com nome, matricula e media) e escreva uma função void imprimir(const Aluno *a) que exiba os três campos. Note como a assinatura fica mais limpa que a versão com struct.
Ver resposta
✓ Resposta:
#include <stdio.h>
typedef struct {
char nome[50];
int matricula;
double media;
} Aluno;
void imprimir(const Aluno *a) {
printf("%s (mat. %d) — média %.1f\n", a->nome, a->matricula, a->media);
}
A assinatura void imprimir(const Aluno *a) é mais limpa que void imprimir(const struct Aluno *a) — o typedef removeu a palavra struct do ponto de uso.
Exercício 4
Explique por que a linha typedef int *PInt; PInt a, b; pode enganar quem lê o código. O que exatamente a e b são?
Ver resposta
✓ Resposta: A linha engana porque, ao declarar PInt a, b;, quem lê pode pensar que apenas a é ponteiro (talvez esperando que b seja um int comum). Na verdade, ambos são ponteiros para int: como PInt já significa "ponteiro para int", tanto a quanto b recebem esse tipo. O asterisco está escondido dentro do apelido, então a natureza de ponteiro não é visível na declaração. Se escrevêssemos sem o typedef, int *a, b;, ficaria claro (e correto) que só a é ponteiro e b é int — o oposto do que o typedef sugere. Por isso se recomenda evitar esconder ponteiros em typedef.
Exercício 5
Usando a forma typedef struct Nome { ... } Nome;, defina um tipo No para o nó de uma lista encadeada, com um campo int valor e um campo que aponta para o próximo nó. Explique por que, dentro da struct, o campo de ligação precisa usar struct No * em vez de apenas No *.
Ver resposta
✓ Resposta:
typedef struct No {
int valor;
struct No *proximo; // ponteiro para o próximo nó
} No;
Dentro da definição da struct, o apelido No ainda não está pronto — o typedef só o cria ao final, depois da chave de fechamento. Portanto, no momento em que declaramos o campo proximo, o nome No não existe como tipo ainda; precisamos nos referir à struct pelo seu nome de struct, struct No. É exatamente por isso que usamos a forma com nome duplo (typedef struct No { ... } No;): o nome struct No fica disponível imediatamente, permitindo a autorreferência, enquanto o apelido No fica disponível para uso externo depois. Fora da struct, você pode usar No * normalmente.