Callbacks na prática
Nesta aula, você aprenderá a usar callbacks em C, passando funções como argumentos para outras funções. Exploraremos o uso de qsort com comparadores, casos de uso práticos e exemplos completos para dominar essa técnica essencial.
Callbacks são uma técnica fundamental em C que permite passar funções como argumentos para outras funções. Isso possibilita escrever código mais genérico e reutilizável, delegando comportamentos específicos para o chamador. Nesta aula, vamos entender como declarar e usar ponteiros para funções, aplicar callbacks com a função de ordenação qsort e explorar casos de uso comuns.
Dominar callbacks é essencial para programação em C, especialmente em bibliotecas e sistemas embarcados, onde a flexibilidade de comportamento é crucial. Vamos começar pelo básico: como passar uma função como argumento.
Passando funções como argumento
Em C, funções não são valores de primeira classe, mas podemos usar ponteiros para funções. Um ponteiro para função armazena o endereço de uma função, permitindo que ela seja chamada indiretamente. A sintaxe para declarar um ponteiro para função é:
tipo_retorno (*nome_ponteiro)(parametros);Por exemplo, para um ponteiro que aponta para uma função que recebe dois inteiros e retorna um inteiro:
int (*operacao)(int, int);Podemos atribuir a esse ponteiro o endereço de uma função compatível, como soma ou subtrai. Para chamar a função através do ponteiro, usamos a sintaxe operacao(a, b) ou (*operacao)(a, b).
Um exemplo completo de passar funções como argumento:
#include <stdio.h>
int soma(int a, int b) { return a + b; }
int subtrai(int a, int b) { return a - b; }
void executa(int a, int b, int (*func)(int, int)) {
printf("Resultado: %d\n", func(a, b));
}
int main() {
int x = 10, y = 5;
executa(x, y, soma);
executa(x, y, subtrai);
return 0;
}No código acima, a função executa recebe um ponteiro para função func e a chama com os argumentos passados. Isso é um callback simples: a função executa chama de volta a função fornecida.
qsort com comparador
A função qsort da biblioteca padrão é um exemplo clássico de uso de callbacks. Ela ordena um array de qualquer tipo usando um comparador fornecido pelo usuário. A assinatura de qsort é:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));O quarto parâmetro é um ponteiro para função que compara dois elementos. A função comparadora deve retornar um inteiro negativo se o primeiro elemento for menor que o segundo, zero se forem iguais, e positivo se o primeiro for maior.
Vamos ordenar um array de inteiros:
#include <stdio.h>
#include <stdlib.h>
int compar_int(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int main() {
int arr[] = {5, 2, 8, 1, 9, 3};
size_t n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), compar_int);
for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
return 0;
}O comparador recebe ponteiros const void * e precisa convertê-los para o tipo correto antes de desreferenciar. Isso permite que qsort funcione com qualquer tipo de dado, desde que o comparador saiba como interpretá-los.
Casos de uso
Callbacks são amplamente usados em C para:
- Ordenação e busca: como
qsortebsearch, que exigem comparadores personalizados. - Tratamento de eventos: em GUIs ou sistemas embarcados, callbacks são registrados para responder a eventos como cliques ou interrupções.
- Estratégias algorítmicas: funções como
map,filterereducepodem ser implementadas com callbacks para operar em coleções. - Personalização de comportamento: bibliotecas podem permitir que o usuário forneça funções para customizar a saída ou o processamento.
Um exemplo prático é usar callbacks para implementar uma função map que aplica uma transformação a cada elemento de um array:
#include <stdio.h>
void map(int *arr, size_t n, int (*func)(int)) {
for (size_t i = 0; i < n; i++) {
arr[i] = func(arr[i]);
}
}
int dobro(int x) { return x * 2; }
int main() {
int arr[] = {1, 2, 3, 4};
size_t n = sizeof(arr) / sizeof(arr[0]);
map(arr, n, dobro);
for (size_t i = 0; i < n; i++) printf("%d ", arr[i]);
return 0;
}Exemplos
Vamos consolidar com um exemplo mais completo: um sistema de registro de eventos que usa callbacks para notificar diferentes partes do programa.
#include <stdio.h>
// Tipo de callback para evento
typedef void (*event_handler)(int event_id);
void registrar_evento(event_handler handler) {
// Simula um evento
handler(42);
}
void meu_handler(int id) {
printf("Evento recebido: %d\n", id);
}
int main() {
registrar_evento(meu_handler);
return 0;
}Outro exemplo: usar qsort para ordenar strings em ordem alfabética.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compar_str(const void *a, const void *b) {
return strcmp(*(const char **)a, *(const char **)b);
}
int main() {
const char *nomes[] = {"João", "Maria", "Ana", "Pedro"};
size_t n = sizeof(nomes) / sizeof(nomes[0]);
qsort(nomes, n, sizeof(char *), compar_str);
for (size_t i = 0; i < n; i++) printf("%s\n", nomes[i]);
return 0;
}Perceba que o comparador de strings recebe ponteiros para char * (ponteiro para ponteiro), pois o array é de ponteiros.
Boas práticas e observações finais
- Sempre verifique se o ponteiro de função não é NULL antes de chamá-lo, para evitar crashes.
- Documente claramente o contrato do callback: parâmetros, retorno e significado.
- Use
typedefpara simplificar declarações de ponteiros de função e melhorar a legibilidade. - Em callbacks que recebem
const void *, faça a conversão de tipo com cuidado para evitar violações de aliasing. - Prefira funções estáticas para callbacks internos, reduzindo o escopo e evitando conflitos de nomes.
Referências
- Ponteiros para funções - cppreference
- qsort - cppreference
- GNU C Library: Function Pointers
- Learn-C.org: Function Pointers
- GeeksforGeeks: Function Pointer in C
Exercícios
Escreva uma função
apply_twiceque recebe um inteiro e um ponteiro para função que modifica um inteiro, e retorna o resultado de aplicar a função duas vezes ao valor inicial.✓ Resposta:int apply_twice(int x, int (*func)(int)) { return func(func(x)); }Crie um comparador para
qsortque ordene números de ponto flutuante em ordem decrescente.✓ Resposta:int compar_double_desc(const void *a, const void *b) { double da = *(const double *)a; double db = *(const double *)b; if (da < db) return 1; if (da > db) return -1; return 0; }Implemente uma função
filterque recebe um array de inteiros, seu tamanho, um predicado (função que retorna 1 para manter, 0 para descartar) e um ponteiro para um array de saída. A função deve preencher o array de saída com os elementos que satisfazem o predicado e retornar o novo tamanho.✓ Resposta:size_t filter(const int *arr, size_t n, int (*pred)(int), int *out) { size_t j = 0; for (size_t i = 0; i < n; i++) { if (pred(arr[i])) out[j++] = arr[i]; } return j; }Explique por que o comparador de
qsortrecebe ponteiros paraconst voide como isso permite ordenar qualquer tipo de dado.✓ Resposta: A funçãoqsorté genérica e não sabe o tipo dos elementos. Usarvoid *permite que ela trate qualquer tipo de dado, econstgarante que os elementos não sejam modificados. O comparador converte os ponteiros para o tipo correto, permitindo que a comparação seja feita de forma específica.Escreva um programa que usa
qsortpara ordenar um array de structsPessoa(com campos nome e idade) por idade.✓ Resposta:#include <stdio.h> #include <stdlib.h> typedef struct { char nome[50]; int idade; } Pessoa; int compar_pessoa(const void *a, const void *b) { const Pessoa *pa = a; const Pessoa *pb = b; return pa->idade - pb->idade; } int main() { Pessoa p[] = {{"Ana", 25}, {"João", 30}, {"Maria", 20}}; size_t n = sizeof(p) / sizeof(p[0]); qsort(p, n, sizeof(Pessoa), compar_pessoa); for (size_t i = 0; i < n; i++) printf("%s: %d\n", p[i].nome, p[i].idade); return 0; }