Typedef: criando aliases de tipos

1. Introdução ao typedef

O typedef é uma palavra-chave da linguagem C que permite criar nomes alternativos (aliases) para tipos de dados existentes. Seu propósito principal é aumentar a legibilidade do código, simplificar declarações complexas e facilitar a manutenção de projetos de médio e grande porte.

A sintaxe básica é simples:

typedef <tipo_existente> <novo_nome>;

Após essa declaração, o novo nome pode ser usado em qualquer lugar onde o tipo original seria utilizado.

É importante distinguir typedef de #define. Enquanto #define é uma diretiva de pré-processamento que realiza substituição textual simples, typedef é uma instrução do compilador que cria um novo nome de tipo com escopo e regras de tipagem adequadas. Por exemplo:

#define PONTEIRO_INT int*
typedef int* pInt;

int main() {
    PONTEIRO_INT a, b;   // a é int*, b é int (apenas a recebe o ponteiro)
    pInt c, d;           // c e d são ambos int*
    return 0;
}

2. Aliases para Tipos Primitivos e Compostos

Com typedef, podemos renomear tipos nativos para tornar o código mais expressivo:

typedef unsigned long ulong;
typedef unsigned char byte;
typedef float real;

ulong distancia = 1000000UL;
byte status = 0xFF;
real pi = 3.14159f;

Para estruturas, o typedef elimina a necessidade de repetir a palavra-chave struct:

typedef struct {
    char nome[50];
    int idade;
    float salario;
} Pessoa;

Pessoa funcionario;  // Sem typedef: struct Pessoa funcionario;

O mesmo se aplica a unions e enums:

typedef enum { VERMELHO, VERDE, AZUL } Cor;
typedef union {
    int inteiro;
    float flutuante;
    char texto[20];
} Dado;

Cor minhaCor = VERDE;
Dado valor;
valor.flutuante = 3.14f;

3. Typedef com Ponteiros

Criar aliases para ponteiros pode simplificar declarações, mas requer cuidado:

typedef int* pInt;

pInt ptr1, ptr2;  // Ambos são int*
int* ptr3, ptr4;  // ptr3 é int*, ptr4 é int (erro comum)

Para ponteiros de função, o typedef é especialmente útil:

typedef int (*Operacao)(int, int);

int somar(int a, int b) { return a + b; }
int subtrair(int a, int b) { return a - b; }

int main() {
    Operacao op = somar;
    printf("Resultado: %d\n", op(10, 5));  // 15
    op = subtrair;
    printf("Resultado: %d\n", op(10, 5));  // 5
    return 0;
}

4. Typedef com Arrays

Arrays de tamanho fixo também podem receber aliases:

typedef int Vetor[10];
typedef float Matriz[3][4];

Vetor numeros;              // Equivalente a: int numeros[10];
Matriz tabela;              // Equivalente a: float tabela[3][4];

// Uso prático
void preencherVetor(Vetor v) {
    for (int i = 0; i < 10; i++) {
        v[i] = i * 2;
    }
}

Isso facilita a declaração de múltiplas variáveis com a mesma estrutura:

typedef char Linha[80];

Linha buffer1, buffer2, buffer3;  // Três buffers de 80 caracteres

5. Typedef e Estruturas Complexas (Structs Aninhadas)

Em listas encadeadas, o typedef é essencial para criar tipos autorreferenciados:

typedef struct No {
    int dado;
    struct No* proximo;  // Necessário usar struct No* aqui
} No;

// Agora podemos usar apenas "No"
No* cabeca = NULL;
No* novoNo = (No*)malloc(sizeof(No));

Com structs anônimas, a declaração fica ainda mais limpa:

typedef struct {
    int x;
    int y;
    struct {
        int largura;
        int altura;
    } dimensao;
} Ponto;

Ponto p = {10, 20, {800, 600}};

6. Boas Práticas e Convenções

A comunidade C adota algumas convenções para nomes de tipos:

  • Sufixo _t para tipos padrão (como size_t, time_t)
  • Maiúsculas para constantes e tipos definidos pelo usuário
  • Nomes descritivos que indiquem o propósito
typedef unsigned int uint32_t;
typedef struct { double real; double imag; } Complexo;
typedef void (*Callback)(int evento);

Quando usar typedef:
- Para encapsular complexidade (ponteiros de função, structs aninhadas)
- Para abstrair detalhes de implementação
- Para garantir consistência em projetos grandes

Quando evitar:
- Para tipos muito simples ou amplamente conhecidos
- Quando o alias oculta informações importantes (como ponteiros)
- Para criar "tipos mágicos" que ninguém entende

7. Exemplo Prático: Sistema de Cadastro com Typedef

Vamos construir um sistema simples de cadastro de clientes:

#include <stdio.h>
#include <string.h>

// Definição de tipos com typedef
typedef struct {
    int dia;
    int mes;
    int ano;
} Data;

typedef struct {
    int id;
    char nome[100];
    char email[100];
    Data nascimento;
    float saldo;
} Cliente;

typedef int (*Comparador)(Cliente, Cliente);

// Funções de comparação
int compararPorId(Cliente a, Cliente b) {
    return a.id - b.id;
}

int compararPorNome(Cliente a, Cliente b) {
    return strcmp(a.nome, b.nome);
}

// Função genérica de ordenação
void ordenarClientes(Cliente clientes[], int n, Comparador cmp) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (cmp(clientes[j], clientes[j + 1]) > 0) {
                Cliente temp = clientes[j];
                clientes[j] = clientes[j + 1];
                clientes[j + 1] = temp;
            }
        }
    }
}

int main() {
    Cliente clientes[3] = {
        {3, "Carlos", "carlos@email.com", {15, 3, 1990}, 1500.50},
        {1, "Ana", "ana@email.com", {20, 7, 1985}, 2500.00},
        {2, "Bruno", "bruno@email.com", {10, 11, 1995}, 1800.75}
    };

    printf("Ordenando por ID:\n");
    ordenarClientes(clientes, 3, compararPorId);
    for (int i = 0; i < 3; i++) {
        printf("%d - %s\n", clientes[i].id, clientes[i].nome);
    }

    printf("\nOrdenando por Nome:\n");
    ordenarClientes(clientes, 3, compararPorNome);
    for (int i = 0; i < 3; i++) {
        printf("%d - %s\n", clientes[i].id, clientes[i].nome);
    }

    return 0;
}

Sem typedef, o código seria muito mais verboso:

// Sem typedef (apenas para comparação)
struct Cliente {
    int id;
    char nome[100];
    // ...
};

int (*comparador)(struct Cliente, struct Cliente);

void ordenarClientes(struct Cliente clientes[], int n, 
                     int (*cmp)(struct Cliente, struct Cliente)) {
    // ...
}

O uso de typedef torna o código mais limpo, facilita mudanças futuras (como alterar a estrutura Cliente) e melhora a legibilidade geral do projeto.

Referências