Ponteiros para funções

1. Introdução aos Ponteiros para Funções

Em C, funções não são apenas blocos de código — elas ocupam endereços na memória, assim como variáveis. Um ponteiro para função armazena o endereço de uma função, permitindo que ela seja chamada indiretamente. Esse recurso é essencial para implementar callbacks, tabelas de despacho e até mesmo simular orientação a objetos.

A sintaxe básica para declarar um ponteiro que aponta para uma função que recebe dois inteiros e retorna um inteiro é:

int (*ptr)(int, int);

O nome da função, sem os parênteses de chamada, representa seu endereço. Exemplo prático:

#include <stdio.h>

int soma(int a, int b) {
    return a + b;
}

int main() {
    int (*ptr)(int, int) = soma;  // ptr aponta para soma
    printf("Resultado: %d\n", ptr(3, 4));  // 7
    return 0;
}

2. Declaração e Inicialização

A forma geral é:

tipo_retorno (*nome_ponteiro)(tipo_param1, tipo_param2, ...);

Os parênteses em torno de *nome_ponteiro são obrigatórios — sem eles, tipo_retorno *nome_ponteiro(...) seria interpretado como uma função que retorna um ponteiro.

Inicialização correta:

int (*op)(int, int) = soma;  // certo
int (*op2)(int, int) = &soma; // também válido (o & é opcional)

Erros comuns:

int *op(int, int);  // declara uma função, não um ponteiro
int (*op)(int, int);
op = soma();        // erro: chama soma e tenta atribuir o retorno

3. Chamando Funções Através de Ponteiros

Há duas formas equivalentes:

int r1 = (*ptr)(10, 20);  // chamada explícita (desreferencia o ponteiro)
int r2 = ptr(10, 20);     // chamada implícita (açúcar sintático)

Exemplo comparativo:

#include <stdio.h>

int multiplica(int a, int b) {
    return a * b;
}

int main() {
    int (*ptr)(int, int) = multiplica;

    printf("Chamada direta: %d\n", multiplica(5, 6));     // 30
    printf("Chamada explícita: %d\n", (*ptr)(5, 6));      // 30
    printf("Chamada implícita: %d\n", ptr(5, 6));         // 30

    return 0;
}

A forma implícita é mais legível e amplamente utilizada.

4. Ponteiros para Funções como Parâmetros (Callbacks)

Callbacks são funções passadas como argumento para outra função, que as chama em determinado momento. O exemplo clássico é a função qsort da biblioteca padrão:

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

int comparar(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int main() {
    int arr[] = {5, 2, 9, 1, 5, 6};
    size_t n = sizeof(arr) / sizeof(arr[0]);

    qsort(arr, n, sizeof(int), comparar);  // callback

    for (size_t i = 0; i < n; i++)
        printf("%d ", arr[i]);  // 1 2 5 5 6 9

    return 0;
}

Implementação de um mecanismo de callback personalizado:

#include <stdio.h>

void processar(int *dados, size_t n, int (*filtro)(int)) {
    for (size_t i = 0; i < n; i++) {
        if (filtro(dados[i]))
            printf("%d ", dados[i]);
    }
    printf("\n");
}

int par(int x) { return x % 2 == 0; }
int impar(int x) { return x % 2 != 0; }

int main() {
    int nums[] = {1,2,3,4,5,6};
    processar(nums, 6, par);    // 2 4 6
    processar(nums, 6, impar);  // 1 3 5
    return 0;
}

5. Arrays de Ponteiros para Funções (Tabelas de Despacho)

Um array de ponteiros para funções permite selecionar dinamicamente qual função executar, substituindo longas cadeias de if-else ou switch.

Calculadora simples:

#include <stdio.h>

int somar(int a, int b)     { return a + b; }
int subtrair(int a, int b)  { return a - b; }
int multiplicar(int a, int b){ return a * b; }
int dividir(int a, int b)   { return b ? a / b : 0; }

int main() {
    int (*ops[4])(int, int) = {somar, subtrair, multiplicar, dividir};
    char opcoes[] = {'+', '-', '*', '/'};

    int a = 10, b = 3;
    for (int i = 0; i < 4; i++)
        printf("%d %c %d = %d\n", a, opcoes[i], b, ops[i](a, b));

    return 0;
}

Vantagens: código mais limpo, fácil manutenção e desempenho previsível.

6. Ponteiros para Funções e Structs (OOP em C)

Podemos simular métodos armazenando ponteiros para funções dentro de structs:

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

typedef struct {
    char nome[50];
    void (*falar)(const char*);
} Animal;

void latir(const char *nome) {
    printf("%s: Au au!\n", nome);
}

void miar(const char *nome) {
    printf("%s: Miau!\n", nome);
}

int main() {
    Animal cachorro = {"Rex", latir};
    Animal gato = {"Mimi", miar};

    cachorro.falar(cachorro.nome);  // Rex: Au au!
    gato.falar(gato.nome);          // Mimi: Miau!

    return 0;
}

Polimorfismo simples: diferentes structs podem ter o mesmo "método" com comportamentos distintos.

7. Casos Avançados e Boas Práticas

Ponteiros para funções que retornam ponteiros para funções

int (*obter_operacao(char op))(int, int) {
    if (op == '+') return somar;
    if (op == '-') return subtrair;
    return NULL;
}

Typedef para simplificar

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

Operacao ops[4] = {somar, subtrair, multiplicar, dividir};

Cuidados importantes

  • Escopo: a função apontada deve existir enquanto o ponteiro for usado. Nunca retorne ponteiros para funções locais (dentro de outras funções) — elas são destruídas ao sair do escopo.
  • Validade: verifique se o ponteiro não é NULL antes de chamar a função.
  • Legibilidade: use typedef para declarações complexas e evite aninhamentos excessivos.

Conclusão

Ponteiros para funções são uma ferramenta poderosa em C. Eles permitem escrever código flexível e reutilizável, implementar callbacks, construir tabelas de despacho e até simular conceitos de orientação a objetos. Com a prática, você perceberá que esse recurso está presente em muitas bibliotecas e sistemas embarcados, sendo indispensável para o programador C avançado.

Referências