C Ponteiros para Funções
Você já sabe que variáveis moram em endereços. As funções também: as instruções de cada função ficam guardadas na memória do programa, e o nome da função é, no fundo, o endereço onde elas começam. Um ponteiro para função guarda esse endereço, e com ele você pode escolher qual função chamar enquanto o programa roda: a operação de uma calculadora, a ação de um item de menu, o critério de uma ordenação. É assim que o qsort da biblioteca padrão ordena qualquer coisa.
- Como declarar um ponteiro para função (
int (*op)(int, int)) e por que os parênteses são obrigatórios. - Como guardar uma função nele e chamá-la:
op = somar;eop(6, 3). - Como escolher a operação de uma calculadora durante a execução.
- Como passar uma função para outra função (o callback), como faz o
qsort. - Como montar um menu com um array de ponteiros de função.
- Como o
typedefdeixa esses tipos legíveis. - Os erros reais: tipo de função incompatível, parênteses esquecidos, chamar no lugar de apontar e chamar um ponteiro nulo.
Esta aula supõe que você já leu Funções e Ponteiros. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: o botão programável
O controle remoto da TV da Ana tem um botão com uma estrela, o "favorito". Ele não faz nada de fábrica: a Ana escolhe o que ele faz. Hoje ele abre o canal de esportes; amanhã ela reprograma e ele passa a abrir o aplicativo de música. O botão é sempre o mesmo, mas a ação guardada nele muda.
Um ponteiro para função é esse botão. Você guarda nele "qual função rodar" e, quando aperta (chama o ponteiro), roda a função que estiver guardada naquele momento.
| No controle remoto | Em C |
|---|---|
| As ações possíveis (canal de esportes, música…) | As funções: somar, multiplicar… |
| O botão favorito | O ponteiro: op |
| Programar o botão | op = somar; |
| Apertar o botão | op(6, 3) |
| O botão só aceita ações "de TV" | O ponteiro só aceita funções com os mesmos parâmetros e o mesmo retorno |
1. Declarar, guardar e chamar
#include <stdio.h>
int somar(int a, int b) {
return a + b;
}
int multiplicar(int a, int b) {
return a * b;
}
int main(void) {
int (*op)(int, int);
op = somar;
printf("op(6, 3) = %d\n", op(6, 3));
op = multiplicar;
printf("op(6, 3) = %d\n", op(6, 3));
return 0;
}
$ gcc -std=c23 -Wall -Wextra pf1.c -o pf1 $ ./pf1 op(6, 3) = 9 « op guardava somar op(6, 3) = 18 « a mesma linha, agora com multiplicar
A mesma chamada op(6, 3) deu dois resultados, porque a função guardada em op mudou entre uma e outra. Agora, a parte que assusta todo mundo, a declaração:
int | (*op) | (int, int) | ; |
|---|---|---|---|
| o que a função devolve | op é um ponteiro | os parâmetros que a função recebe | fim |
op é um ponteiro (*op) para uma função que recebe dois int ((int, int)) e devolve int".- int (*op)(int, int);
- É a mesma "forma" do protótipo
int somar(int a, int b);, com(*op)no lugar do nome. Os nomes dos parâmetros podem ficar de fora. - (*op)
- Os parênteses são obrigatórios. Sem eles,
int *op(int, int);declara uma função chamadaopque devolveint *. É o erro mais comum deste assunto (veja "Quando dá errado"). - op = somar;
- O nome de uma função, sem parênteses, é o endereço dela. Com parênteses (
somar(6, 3)) você estaria chamando a função e guardando o resultado, 9, o que não é um endereço. - op(6, 3)
- Chama a função para onde
opaponta, como seopfosse o nome dela.
op = somar; e op = &somar; fazem o mesmo; op(6, 3) e (*op)(6, 3) também. Código antigo usa muito o (*op)(…) para deixar claro que op é um ponteiro. Quer uma prova de que &somar e somar são a mesma coisa? Compare os dois com ==, e o próprio gcc avisa: warning: self-comparison always evaluates to true ("comparar uma coisa consigo mesma sempre dá verdadeiro").
Onde as funções moram
Para ver os endereços, dá para mostrar a função com %p, convertendo para (void *):
#include <stdio.h>
int somar(int a, int b) { return a + b; }
int multiplicar(int a, int b) { return a * b; }
int main(void) {
int (*op)(int, int) = somar;
printf("somar mora em %p\n", (void *)somar);
printf("multiplicar mora em %p\n", (void *)multiplicar);
printf("op guarda %p\n", (void *)op);
printf("op mora em %p\n", (void *)&op);
return 0;
}
$ gcc -std=c23 -Wall -Wextra ondemora.c -o ondemora $ ./ondemora somar mora em 0x56f0e9d1c169 « na região do código do programa multiplicar mora em 0x56f0e9d1c181 « 0x18 (24) bytes depois: o tamanho das instruções de somar op guarda 0x56f0e9d1c169 « o endereço de somar op mora em 0x7ffdaf0af500 « o próprio op é uma variável local, na pilha
Dois detalhes honestos sobre esse programa. Primeiro, o padrão do C não garante que um endereço de função caiba num void *; no Linux cabe, mas com -pedantic o gcc avisa: ISO C forbids conversion of function pointer to object pointer type ("o C padrão proíbe converter ponteiro de função em ponteiro de dado"). Use isso só para curiosidade. Segundo, no editor do guia a saída é bem diferente: somar mora em 0x1, multiplicar mora em 0x2. No WebAssembly, as funções ficam numa tabela separada da memória e o "endereço" é só a posição nessa tabela. A ideia continua a mesma: um número que identifica a função.
Experimente: rode. Depois crie uma função int maior(int a, int b) que devolve o maior dos dois números, guarde-a em op e mostre op(6, 3) mais uma vez.
#include <stdio.h>
int somar(int a, int b) {
return a + b;
}
int multiplicar(int a, int b) {
return a * b;
}
int main(void) {
int (*op)(int, int);
op = somar;
printf("op(6, 3) = %d\n", op(6, 3));
op = multiplicar;
printf("op(6, 3) = %d\n", op(6, 3));
return 0;
}
2. Escolher a operação: uma calculadora
A vantagem aparece quando a função é escolhida durante a execução, a partir do que o usuário digitou. A calculadora da Ana lê uma conta como 6 * 3, escolhe a função uma vez e depois chama op sem precisar saber qual é:
#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 main(void) {
int a = 0, b = 0;
char sinal = ' ';
printf("Conta (ex.: 6 + 3): ");
if (scanf("%d %c %d", &a, &sinal, &b) != 3) {
printf("Não entendi a conta.\n");
return 1;
}
int (*op)(int, int) = NULL;
if (sinal == '+') {
op = somar;
} else if (sinal == '-') {
op = subtrair;
} else if (sinal == '*') {
op = multiplicar;
}
if (op == NULL) {
printf("Não conheço a operação '%c'.\n", sinal);
return 1;
}
printf("%d %c %d = %d\n", a, sinal, b, op(a, b));
return 0;
}
- scanf("%d %c %d", …)
- Lê um número, um caractere (o sinal) e outro número. O espaço antes do
%cpula os espaços digitados. Conferir o retorno (3 itens lidos) é a regra do guia (aula Entrada com scanf). - int (*op)(int, int) = NULL;
- O ponteiro começa nulo: "nenhuma função escolhida ainda". Toda variável nasce com valor, e ponteiros também.
- op = somar;
- Cada
ifsó escolhe; ninguém faz conta ainda. - if (op == NULL)
- Se o sinal não era conhecido,
opcontinua nulo. Chamar um ponteiro nulo derruba o programa, então a conferência é obrigatória. - op(a, b)
- Uma linha só faz a conta, qualquer que seja a operação.
$ gcc -std=c23 -Wall -Wextra calc.c -o calc $ ./calc Conta (ex.: 6 + 3): 6 * 3 6 * 3 = 18 $ ./calc Conta (ex.: 6 + 3): 10 - 4 10 - 4 = 6 $ ./calc Conta (ex.: 6 + 3): 8 / 2 Não conheço a operação '/'.
"Mas um if que já fizesse a conta não daria no mesmo?" Neste programa pequeno, quase. A diferença aparece quando a escolha é feita num lugar e o uso em outro: a função escolhida pode ser guardada, passada para outras funções, colocada num array. As próximas seções mostram isso.
Missão: ensine a calculadora a dividir. Crie a função int dividir(int a, int b) (com return a / b;) e acrescente um else if que a escolha quando o sinal for '/'. A entrada 8 / 2 já está preparada; a saída deve terminar com 8 / 2 = 4.
#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 main(void) {
int a = 0, b = 0;
char sinal = ' ';
printf("Conta (ex.: 6 + 3): ");
if (scanf("%d %c %d", &a, &sinal, &b) != 3) {
printf("Não entendi a conta.\n");
return 1;
}
int (*op)(int, int) = NULL;
if (sinal == '+') {
op = somar;
} else if (sinal == '-') {
op = subtrair;
} else if (sinal == '*') {
op = multiplicar;
}
if (op == NULL) {
printf("Não conheço a operação '%c'.\n", sinal);
return 1;
}
printf("%d %c %d = %d\n", a, sinal, b, op(a, b));
return 0;
}
#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 a / b; }
int main(void) {
int a = 0, b = 0;
char sinal = ' ';
printf("Conta (ex.: 6 + 3): ");
if (scanf("%d %c %d", &a, &sinal, &b) != 3) {
printf("Não entendi a conta.\n");
return 1;
}
int (*op)(int, int) = NULL;
if (sinal == '+') {
op = somar;
} else if (sinal == '-') {
op = subtrair;
} else if (sinal == '*') {
op = multiplicar;
} else if (sinal == '/') {
op = dividir;
}
if (op == NULL) {
printf("Não conheço a operação '%c'.\n", sinal);
return 1;
}
printf("%d %c %d = %d\n", a, sinal, b, op(a, b));
return 0;
}
Depois de cumprir a missão, troque o que está no campo Entrada por 8 / 0, rode de novo e veja o programa morrer com Exceção de ponto flutuante: divisão inteira por zero. Uma calculadora de verdade conferiria o b antes (aula Operadores Aritméticos).
3. Passar uma função para outra: o callback
Um ponteiro para função pode ser parâmetro. Assim, quem chama decide uma parte do trabalho da função chamada. A função aplicar abaixo percorre um array e aplica em cada item "alguma transformação", que ela recebe pronta:
#include <stdio.h>
int dobro(int x) { return x * 2; }
int quadrado(int x) { return x * x; }
void aplicar(int *v, int n, int (*f)(int)) {
for (int i = 0; i < n; i++) {
v[i] = f(v[i]);
}
}
void mostrar(const int *v, int n) {
for (int i = 0; i < n; i++) {
printf("%d ", v[i]);
}
printf("\n");
}
int main(void) {
int pontos[4] = {1, 2, 3, 4};
aplicar(pontos, 4, dobro);
mostrar(pontos, 4);
aplicar(pontos, 4, quadrado);
mostrar(pontos, 4);
return 0;
}
$ gcc -std=c23 -Wall -Wextra aplicar.c -o aplicar $ ./aplicar 2 4 6 8 « cada ponto dobrou 4 16 36 64 « e depois foi ao quadrado
- int (*f)(int)
- O terceiro parâmetro de
aplicaré um ponteiro para uma função que recebe uminte devolve umint. - v[i] = f(v[i]);
aplicarnão sabe se está dobrando, elevando ao quadrado ou outra coisa. Ela só chamaf.- aplicar(pontos, 4, dobro)
- Passa a função
dobro(sem parênteses!) como argumento.
Uma função passada para outra, que a chama "de volta" na hora certa, se chama callback ("chamar de volta"). É como deixar seu número com a pizzaria: você não fica ligando para perguntar se a pizza saiu; eles chamam de volta quando estiver pronta.
O callback mais famoso: qsort
A biblioteca padrão tem uma função de ordenação, qsort ("quick sort", ordenação rápida), que ordena arrays de qualquer tipo. Como ela não sabe comparar seus dados (números? nomes? fichas de alunos?), você entrega uma função que compara dois itens:
#include <stdio.h>
#include <stdlib.h>
int comparar(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
if (x < y) return -1;
if (x > y) return 1;
return 0;
}
int main(void) {
int notas[] = {7, 3, 10, 5, 9};
size_t n = sizeof notas / sizeof notas[0];
qsort(notas, n, sizeof notas[0], comparar);
printf("Notas:");
for (size_t i = 0; i < n; i++) {
printf(" %d", notas[i]);
}
printf("\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra ordena.c -o ordena $ ./ordena Notas: 3 5 7 9 10
- qsort(notas, n, sizeof notas[0], comparar)
- O array, quantos itens, o tamanho de cada item e a função que compara. O protótipo é
void qsort(void *base, size_t n, size_t tamanho, int (*comparar)(const void *, const void *)): o último parâmetro é um ponteiro para função. - const void *a, const void *b
- O
qsortentrega os endereços de dois itens comovoid *, porque não sabe o tipo deles (aula Ponteiro void*). A sua função converte para o tipo certo. - return -1 / 1 / 0
- A regra do
qsort: negativo seavem antes, positivo seavem depois, zero se tanto faz.
Repare que você nunca chama comparar. Quem chama é o qsort, quantas vezes precisar, durante a ordenação. Os detalhes (ordenar nomes, structs, critérios de desempate) estão em Ordenação (qsort), e o bsearch, que usa a mesma ideia para procurar, em Busca.
return x - y;?
Muita gente escreve a comparação como return x - y;. Funciona com notas pequenas, mas com números grandes a subtração pode estourar o int e dar o sinal errado. As duas comparações com < e > nunca estouram.
4. Um array de ponteiros de função: o menu
Ponteiros para funções podem ir para um array, como qualquer outro valor. Com isso, um menu vira uma tabela: o número escolhido é o índice, e o item do array é a ação.
#include <stdio.h>
void novo_jogo(void) { printf("Começando um jogo novo...\n"); }
void carregar(void) { printf("Carregando o jogo salvo...\n"); }
void recordes(void) { printf("Recorde: Ana, 1200 pontos\n"); }
int main(void) {
void (*acoes[])(void) = {novo_jogo, carregar, recordes};
const char *nomes[] = {"Novo jogo", "Carregar", "Recordes"};
int total = sizeof acoes / sizeof acoes[0];
for (int i = 0; i < total; i++) {
printf("%d) %s\n", i + 1, nomes[i]);
}
printf("Escolha: ");
int escolha = 0;
if (scanf("%d", &escolha) != 1 || escolha < 1 || escolha > total) {
printf("Opção inválida.\n");
return 1;
}
acoes[escolha - 1]();
return 0;
}
- void (*acoes[])(void)
- Leia a partir do nome:
acoesé um array ([]) de ponteiros (*) para funções sem parâmetros ((void)) que não devolvem nada (void). O tamanho sai da lista entre chaves: 3. - {novo_jogo, carregar, recordes}
- Os endereços das três funções, na ordem do menu.
- sizeof acoes / sizeof acoes[0]
- Quantos itens o array tem (veja Arrays). Se você acrescentar uma função na lista, o menu cresce sozinho.
- escolha < 1 || escolha > total
- Conferência obrigatória: um índice fora do array pegaria um "endereço" qualquer da memória e o programa pularia para lá.
- acoes[escolha - 1]()
- Pega o ponteiro na posição escolhida (o menu começa em 1, o array em 0) e chama a função. Os
()no fim são a chamada.
$ gcc -std=c23 -Wall -Wextra menu.c -o menu $ ./menu 1) Novo jogo 2) Carregar 3) Recordes Escolha: 3 Recorde: Ana, 1200 pontos $ ./menu 1) Novo jogo 2) Carregar 3) Recordes Escolha: 7 Opção inválida.
| Índice | Nome no menu | O que acoes[i] guarda | acoes[i]() faz |
|---|---|---|---|
| 0 | 1) Novo jogo | endereço de novo_jogo | Começando um jogo novo... |
| 1 | 2) Carregar | endereço de carregar | Carregando o jogo salvo... |
| 2 | 3) Recordes | endereço de recordes | Recorde: Ana, 1200 pontos |
switch com um case por opção; com o array, acrescentar uma opção é acrescentar uma linha na lista.Experimente: rode e digite um número quando o programa pedir. Depois crie uma função void sair(void) que mostre Até logo! e acrescente-a ao fim dos dois arrays (acoes e nomes): o menu ganha a opção 4 sem você mexer em mais nada.
#include <stdio.h>
void novo_jogo(void) { printf("Começando um jogo novo...\n"); }
void carregar(void) { printf("Carregando o jogo salvo...\n"); }
void recordes(void) { printf("Recorde: Ana, 1200 pontos\n"); }
int main(void) {
void (*acoes[])(void) = {novo_jogo, carregar, recordes};
const char *nomes[] = {"Novo jogo", "Carregar", "Recordes"};
int total = sizeof acoes / sizeof acoes[0];
for (int i = 0; i < total; i++) {
printf("%d) %s\n", i + 1, nomes[i]);
}
printf("Escolha: ");
int escolha = 0;
if (scanf("%d", &escolha) != 1 || escolha < 1 || escolha > total) {
printf("Opção inválida.\n");
return 1;
}
acoes[escolha - 1]();
return 0;
}
5. typedef: dando um nome ao tipo
Declarações como void (*acoes[])(void) são difíceis de ler, e ficam piores quando uma função devolve um ponteiro para função. Veja uma função escolher que recebe o sinal e devolve a operação, escrita sem ajuda:
int (*escolher(char sinal))(int, int) {
if (sinal == '+') return somar;
if (sinal == '-') return subtrair;
return NULL;
}
Compila, funciona, e quase ninguém consegue ler: "escolher é uma função que recebe um char e devolve um ponteiro para função que recebe dois int e devolve int". O typedef (aula typedef) dá um nome a esse tipo, e daí em diante você usa o nome:
#include <stdio.h>
typedef int (*Operacao)(int, int);
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; }
Operacao escolher(char sinal) {
switch (sinal) {
case '+': return somar;
case '-': return subtrair;
case '*': return multiplicar;
default: return NULL;
}
}
int main(void) {
const char *contas = "+-*/";
for (const char *s = contas; *s != '\0'; s++) {
Operacao op = escolher(*s);
if (op == NULL) {
printf("8 %c 2: operação desconhecida\n", *s);
} else {
printf("8 %c 2 = %d\n", *s, op(8, 2));
}
}
return 0;
}
- typedef int (*Operacao)(int, int);
- Escreva a declaração do ponteiro como se fosse uma variável e ponha
typedefna frente: o "nome da variável" (Operacao) vira o nome do tipo. - Operacao escolher(char sinal)
- Agora sim, se lê de primeira: "
escolherdevolve umaOperacao". - Operacao op = escolher(*s);
- Uma variável do tipo
Operacao, comoint x. - "+-*/"
- Uma string usada como lista de sinais; o laço anda por ela com um ponteiro até o
'\0'(aula Ponteiros e Strings).
$ gcc -std=c23 -Wall -Wextra typedef.c -o typedef $ ./typedef 8 + 2 = 10 8 - 2 = 6 8 * 2 = 16 8 / 2: operação desconhecida
Com o typedef, o menu da seção 4 também fica mais claro: typedef void (*Acao)(void); e depois Acao acoes[] = {novo_jogo, carregar, recordes};.
typedef do tipo da função
Também dá para dar nome ao tipo da função (sem o asterisco) e escrever o * na hora de usar: typedef int Operacao(int, int); e depois Operacao *op = somar;. Alguns projetos preferem assim porque o * fica à vista e ninguém esquece que op é um ponteiro. As duas formas estão certas; escolha uma e use sempre a mesma.
Formas diferentes e qual usar
| Situação | Prefira |
|---|---|
| A escolha é simples e a função é usada uma vez, ali mesmo | Um if/switch que chama a função direto. Ponteiro de função seria complicação sem ganho. |
| A função é escolhida num lugar e usada em outro (ou várias vezes) | Uma variável ponteiro para função, iniciada com NULL |
| Uma função precisa de um "pedaço de comportamento" que quem chama decide (ordenar, filtrar, transformar) | Um parâmetro ponteiro para função (callback), como o qsort |
| Muitas opções numeradas (menu, comandos, estados de um jogo) | Um array de ponteiros de função, com conferência do índice |
| O tipo aparece mais de uma vez ou tem mais de um par de parênteses | typedef |
Quando dá errado
error: initialization of ‘int (*)(int, int)’ from incompatible pointer type ‘int (*)(int)’
int somar(int a, int b) { return a + b; }
int dobro(int x) { return x * 2; }
…
int (*op)(int, int) = dobro;
$ gcc -std=c23 -Wall -Wextra pfe1.c -o pfe1
pfe1.c: In function ‘main’:
pfe1.c:7:27: error: initialization of ‘int (*)(int, int)’ from incompatible pointer type ‘int (*)(int)’ [-Wincompatible-pointer-types]
7 | int (*op)(int, int) = dobro;
| ^~~~~
pfe1.c:4:5: note: ‘dobro’ declared here
4 | int dobro(int x) { return x * 2; }
| ^~~~~
O que significa: o ponteiro espera uma função com dois int (int (*)(int, int) é o jeito de escrever esse tipo sem nome), e dobro recebe um só. Desde o gcc 14 isso é erro (antes era aviso, e o programa chamava a função com os argumentos errados). O Clang do editor diz incompatible function pointer types initializing 'int (*)(int, int)' with an expression of type 'int (int)'.
Como consertar: a função guardada tem de ter exatamente os mesmos parâmetros e o mesmo retorno do ponteiro. Ou ajuste a função, ou declare o ponteiro com o tipo dela (int (*f)(int)).
error: function ‘op’ is initialized like a variable
$ gcc -std=c23 -Wall -Wextra pfe2.c -o pfe2
pfe2.c: In function ‘main’:
pfe2.c:6:5: error: function ‘op’ is initialized like a variable
6 | int *op(int, int) = somar;
| ^~~
O que significa: faltaram os parênteses em volta de *op. Sem eles, int *op(int, int) é o protótipo de uma função op que devolve int *, e função não pode receber valor com =: "a função op está sendo inicializada como uma variável". O Clang diz illegal initializer (only variables can be initialized).
Como consertar: int (*op)(int, int) = somar;. Os parênteses "grudam" o * no nome.
error: assignment to ‘int (*)(int, int)’ from ‘int’ makes pointer from integer without a cast
$ gcc -std=c23 -Wall -Wextra pfe3.c -o pfe3
pfe3.c: In function ‘main’:
pfe3.c:7:8: error: assignment to ‘int (*)(int, int)’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
7 | op = somar(6, 3);
| ^
O que significa: somar(6, 3) chama a função e dá 9, um int. Você tentou guardar o número 9 num ponteiro para função.
Como consertar: para guardar a função, escreva o nome sem parênteses: op = somar;. Os parênteses e os argumentos ficam para a hora de chamar: op(6, 3).
Falha de segmentação (imagem do núcleo gravada) ao chamar o ponteiro
int (*op)(int, int) = NULL;
printf("Vou chamar op...\n");
printf("%d\n", op(6, 3));
$ gcc -std=c23 -Wall -Wextra pfe4.c -o pfe4 $ ./pfe4 Vou chamar op... Falha de segmentação (imagem do núcleo gravada) ./pfe4
O que significa: o programa tentou pular para o endereço zero para rodar "a função" que está lá, e não há função nenhuma. Compilou sem aviso, porque o compilador não sabe o que op vai valer. No editor do guia acontece o mesmo: Falha de segmentação (imagem do núcleo gravada), código 139.
Como consertar: antes de chamar um ponteiro que pode estar vazio, confira: if (op != NULL) …. Num array de ações, confira o índice antes de usá-lo.
Referência rápida
| Escrita | O que é |
|---|---|
int (*op)(int, int); | Ponteiro para função que recebe dois int e devolve int |
int *op(int, int); | Cuidado: é uma função que devolve int * |
op = somar; / op = &somar; | Guarda a função (as duas formas são iguais) |
op(6, 3) / (*op)(6, 3) | Chama a função guardada (iguais) |
int (*op)(int, int) = NULL; | Começa sem função; confira com if (op != NULL) antes de chamar |
void f(int (*g)(int)) | Função que recebe outra função (callback) |
void (*acoes[])(void) = {a, b, c}; | Array de ponteiros de função |
acoes[i]() | Chama a função da posição i |
typedef int (*Operacao)(int, int); | Dá o nome Operacao ao tipo do ponteiro |
qsort(v, n, sizeof v[0], comparar); | Ordena com a sua função de comparação (stdlib.h) |
int comparar(const void *a, const void *b) | A forma que o qsort exige: negativo, zero ou positivo |
Exercícios
1. Declarar
Complete para declarar f, um ponteiro para função que recebe um double e devolve double:
2. Guardar a função
Complete para que op passe a apontar para a função subtrair:
3. typedef
Complete para dar o nome Acao ao tipo "ponteiro para função sem parâmetros que não devolve nada":
4. O que declara int *f(int);?
5. No qsort(notas, n, sizeof notas[0], comparar), quem chama a função comparar?
6. Num menu com acoes[escolha - 1](), por que conferir se escolha está entre 1 e o total?
Missão final: ranking do maior para o menor
Missão: o programa ordena as notas da turma da Ana do menor para o maior. Para o ranking, ela quer do maior para o menor. Mude só a função comparar (o qsort fica como está). Dica: basta inverter o que ela devolve. A saída deve ser:
Notas: 10 9 7 5 3
#include <stdio.h>
#include <stdlib.h>
int comparar(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
if (x < y) return -1;
if (x > y) return 1;
return 0;
}
int main(void) {
int notas[] = {7, 3, 10, 5, 9};
size_t n = sizeof notas / sizeof notas[0];
qsort(notas, n, sizeof notas[0], comparar);
printf("Notas:");
for (size_t i = 0; i < n; i++) {
printf(" %d", notas[i]);
}
printf("\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int comparar(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
if (x > y) return -1;
if (x < y) return 1;
return 0;
}
int main(void) {
int notas[] = {7, 3, 10, 5, 9};
size_t n = sizeof notas / sizeof notas[0];
qsort(notas, n, sizeof notas[0], comparar);
printf("Notas:");
for (size_t i = 0; i < n; i++) {
printf(" %d", notas[i]);
}
printf("\n");
return 0;
}
Quer ir além? Mantenha as duas funções, crescente e decrescente, e escolha qual passar ao qsort com um ponteiro int (*criterio)(const void *, const void *). É o mesmo truque da calculadora, agora decidindo a ordem.
Resumo
- Funções moram na memória; o nome de uma função, sem parênteses, é o endereço dela.
int (*op)(int, int);declara um ponteiro para função; os parênteses em(*op)são obrigatórios (int *op(int, int)seria uma função que devolveint *).- Guarde com
op = somar;e chame comop(6, 3). O tipo da função guardada tem de bater exatamente com o do ponteiro (desde o gcc 14, é erro se não bater). - Comece com
NULLe confira antes de chamar: chamar um ponteiro nulo dá falha de segmentação. - Passar uma função como parâmetro é um callback; o
qsortrecebe a sua função de comparação e a chama quando precisa. - Um array de ponteiros de função transforma um menu numa tabela (confira o índice!).
typedef int (*Operacao)(int, int);dá nome ao tipo e deixa as declarações legíveis.