Publicidade

Ponteiros de Função em C

Passe funções como dados. Essencial para qsort, callbacks e tabelas de despacho.

💪 É normal não pegar na primeira tentativa
A sintaxe de declaração de ponteiros de função é uma das mais espinhosas de C. Tudo bem aprendê-la quando você precisar, em vez de antecipadamente.
Como voltar a ela
  1. Fique à vontade com funções e ponteiros primeiro
  2. Comece usando typedef para dar ao tipo um nome legível
  3. Só saber chamar qsort já cobre a maioria das necessidades do mundo real
  4. Fixe um fato: funções também têm endereços
💡 Dica: leia a declaração a partir do nome, indo para fora — int (*fp)(int) significa "fp é um ponteiro para uma função que recebe um int e retorna um int".

O que é um ponteiro de função?

Toda função tem um endereço na memória, e um ponteiro de função simplesmente guarda esse endereço. Quando você tem um, pode armazenar funções em variáveis, passá-las como argumentos e colocá-las em arrays.
#include <stdio.h>

int square(int x) { return x * x; }

int main(void) {
    // fp points to "a function taking int, returning int"
    int (*fp)(int) = square;  // function name decays to its address

    printf("%d\n", fp(5));   // 25
    printf("%d\n", (*fp)(5)); // 25, older style
    return 0;
}
Regra: o nome de uma função usada como expressão se transforma automaticamente em seu endereço. Então fp = square e fp = &square são equivalentes, assim como fp(5) e (*fp)(5).

Lendo a declaração / typedef

Como ler

int (*fp)(int, int);
① o nome é fp
(*fp)fp é um ponteiro
(int, int) — para uma função que recebe dois ints
④ o int externo — e retorna um int
→ "um ponteiro para uma função que recebe dois ints e retorna um int"

Cuidado com os parênteses

int *fp(int);       // ❌ NOT a function pointer
                    // it's the prototype of a function returning int*

int (*fp)(int);     // ✅ function pointer

Mais limpo com typedef

typedef int (*CmpFn)(const void *, const void *);

void sort(int *a, int n, CmpFn cmp) {
    /* ... */
}
Truque: typedef transforma todo aquele tipo bagunçado em um único nome. Uma vez definido CmpFn, CmpFn x; declara um ponteiro de função.

Callbacks

Aqui está uma rotina genérica "faça X em cada elemento" em que quem chama fornece o que X é:
#include <stdio.h>

void for_each(int *a, int n, void (*fn)(int)) {
    for (int i = 0; i < n; i++) fn(a[i]);
}

void print_int(int v)    { printf("%d ", v); }
void print_square(int v) { printf("%d ", v * v); }

int main(void) {
    int a[] = {1, 2, 3, 4, 5};
    for_each(a, 5, print_int);    printf("\n");
    for_each(a, 5, print_square); printf("\n");
    return 0;
}
Separar como iterar do que fazer é um padrão fundamental em sistemas orientados a eventos, GUIs, APIs assíncronas e ordenação.

Ordenando qualquer coisa com qsort

O qsort padrão recebe uma função de comparação como ponteiro de função, o que significa que pode ordenar qualquer tipo — ints, strings, structs, o que você quiser.

Assinatura

void qsort(void *base,
           size_t nmemb,
           size_t size,
           int (*compar)(const void *, const void *));

int em ordem crescente

#include <stdio.h>
#include <stdlib.h>

int cmp_int_asc(const void *p1, const void *p2) {
    int a = *(const int *)p1;
    int b = *(const int *)p2;
    if (a < b) return -1;
    if (a > b) return 1;
    return 0;
}

int main(void) {
    int a[] = {5, 2, 8, 1, 9, 3};
    qsort(a, 6, sizeof(int), cmp_int_asc);
    for (int i = 0; i < 6; i++) printf("%d ", a[i]);
    // 1 2 3 5 8 9
}
Pegadinha: return a - b; parece funcionar, mas pode causar overflow para valores grandes ou negativos. Sempre compare e retorne -1, 0 ou 1.

Struct com ordenação por duas chaves

struct Student {
    char name[32];
    int score;
};

// higher score first, ties broken by name
int cmp_student(const void *p1, const void *p2) {
    const struct Student *a = p1;
    const struct Student *b = p2;
    if (a->score != b->score)
        return (b->score > a->score) ? 1 : -1;
    return strcmp(a->name, b->name);
}
qsort(students, n, sizeof(struct Student), cmp_student);

Tabelas de despacho

Em vez de um switch gigante, use um array de ponteiros de função para um código limpo e orientado a dados:
#include <stdio.h>

double op_add(double a, double b) { return a + b; }
double op_sub(double a, double b) { return a - b; }
double op_mul(double a, double b) { return a * b; }
double op_div(double a, double b) { return b != 0 ? a / b : 0; }

struct OpEntry {
    char sym;
    double (*fn)(double, double);
};

static const struct OpEntry ops[] = {
    {'+', op_add}, {'-', op_sub},
    {'*', op_mul}, {'/', op_div},
};

double calc(char op, double a, double b) {
    for (int i = 0; i < sizeof(ops) / sizeof(ops[0]); i++) {
        if (ops[i].sym == op) return ops[i].fn(a, b);
    }
    return 0;
}

int main(void) {
    printf("%.2f\n", calc('+', 3, 4));
    printf("%.2f\n", calc('*', 6, 7));
}
Adicionar um novo operador é tão simples quanto adicionar uma linha à tabela — sem mudanças de lógica em nenhum outro lugar.

Desafios

Desafio 1: Ordenação decrescente
Escreva um comparador que ordene um array de int em ordem decrescente.
Desafio 2: Ordenar strings
Ordene char *words[] = {"banana","apple","cherry"}; em ordem alfabética. Note que o comparador recebe const char **.
Desafio 3: uma função filter
Implemente int filter(int *a, int n, int *out, int (*pred)(int)). Ela deve copiar cada elemento para o qual pred retorna diferente de zero. Teste com um predicado de número par.
Desafio 4: máquina de estados de semáforo
Implemente vermelho → verde → amarelo → vermelho como um array de ponteiros de função que tratam cada estado.

Quiz de Revisão

Confira sua compreensão desta aula!

Q1. O que int (*fp)(int) declara?

Um ponteiro para uma função que recebe um int e retorna um int
Uma função que retorna um ponteiro para int
Uma função que recebe um array de int

Os parênteses agrupam *fp, então se lê "fp é um ponteiro para uma função". Sem eles, int *fp(int) significa algo totalmente diferente.

Q2. Qual é o protótipo correto para uma função de comparação do qsort?

int cmp(const void *a, const void *b)
int cmp(int a, int b)
int cmp(void *a, void *b, size_t n)

qsort precisa lidar com tipos arbitrários, então os argumentos chegam como const void *. Faça o cast para o tipo real dentro da função de comparação.

Q3. Qual é a forma correta de chamar uma função por meio de um ponteiro de função fp?

fp(10) ou (*fp)(10)
&fp(10)
*fp[10]

Em C, você pode chamar um ponteiro de função como se fosse um nome de função comum, usando (). O estilo antigo com desreferência explícita (*fp)(...) é equivalente.