Ponteiros para funções

Em nossos programas, não são apenas as variáveis que ocupam espaços na memória: as funções também. Assim, outra possível aplicação de ponteiros consiste na criação de ponteiros para funções.

🧑‍💻 O código

A declaração de ponteiros para funções segue um formato um pouco diferente do que estávamos acostumados. Além de declarar o tipo de retorno da função ou procedimento (void, nesse caso), precisamos também informar o tipo de dado de cada parâmetro, caso exista.

Acompanhe:

tipo (*nomeDoPonteiro)(tipoDoParametro);
nomeDoPonteiro = nomeDaFuncao; // sem parênteses

Para facilitar a compreensão há, a seguir, alguns exemplos:

👀 Casos de exemplo

1️⃣ Procedimento sem parâmetro

Para declararmos um ponteiro para um procedimento que não recebe parâmetros, o processo é bem simples: atribuímos um nome ao ponteiro e, em seguida, inserimos () (parênteses vazios) para indicar que aquele procedimento não recebe parâmetro nenhum.

Execute o código abaixo e veja que o procedimento procedimentoSemParametro será executado normalmente, como se tivesse sido chamado da forma tradicional.

#include <stdio.h>

void procedimentoSemParametro(){
    printf("Procedimento sem parametro.\n");
}

int main()
{
    void (*proc1)();
    proc1 = procedimentoSemParametro;
    proc1();
}

2️⃣ Procedimento com um parâmetro

Quando o procedimento recebe um parâmetro, precisamos simplesmente incluir o indicador do tipo de dado.

#include <stdio.h>

void procedimentoComUmParametro(int num){
    printf("Procedimento com um parametro: %d.\n", num);
}

int main()
{
    void (*proc2)(int);
    proc2 = procedimentoComUmParametro;
    proc2(2);
}

3️⃣ Procedimento com dois parâmetros

No caso de recebimento de mais de um parâmetro, seus tipos devem ser declarados junto ao ponteiro.

#include <stdio.h>

void procedimentoComDoisParametros(int num1, int num2){
    printf("Procedimento com dois parametros: %d e %d.\n", num1, num2);
}

int main()
{
    void (*proc3)(int, int);
    proc3 = procedimentoComDoisParametros;
    proc3(7,14);
}

4️⃣ Função com um parâmetro

Na declaração do ponteiro para uma função (que retorna valor), substituímos void pelo tipo de dado retornado. No caso do código abaixo, int.

#include <stdio.h>

int funcaoComUmParametro(int num){
    printf("Funcao com um parametro: %d.\n", num);
    return num * 2;
}

int main()
{
    int (*proc4)(int);
    proc4 = funcaoComUmParametro;
    int ret = proc4(7);
    printf("Retornou: %d\n", ret);
}

5️⃣ Passando uma função como parâmetro

Uma das vantagens que alcançamos quando utilizamos ponteiros para funções está na possibilidade de passar uma função para outra. Observe a seguir qual seria a prática padrão:

#include <stdio.h>

void ehPar(int n){
    if(n%2==0) printf("%d eh par\n", n);
    else printf("%d eh impar\n", n);
}

void receberFuncao(void *pont){
    void (*)(int) pont(2);
}

int main()
{

    void (*func)(int);
    func = ehPar;
    
    receberFuncao(func);
    // receberFuncao(ehPar); também funciona passando diretamente a função

    return 0;
}

Quando passamos o ponteiro para a função, perdemos a referência de que se trata de um ponteiro para função. Logo, precisamos fazer uma conversão de tipos, indicando que aquele ponteiro contém o endereço de memória de uma função.

Uma possibilidade é indicar, na assinatura da função, que o parâmetro esperado é um ponteiro para função:

void receberFuncao(void (*pont)(int)){
    pont(2);
}

Ou, alternativamente, efetuar a conversão de tipos dentro da função:

void receberFuncao(void *pont) {
    ((void (*)(int))pont)(2);
}

O resultado final, então, ficaria da seguinte forma:

#include <stdio.h>

void ehPar(int n){
    if(n%2==0) printf("%d eh par\n", n);
    else printf("%d eh impar\n", n);
}

void receberFuncao(void (*pont)(int)){
    pont(2);
}

int main()
{

    void (*func)(int);
    func = ehPar;
    
    receberFuncao(func);
    // receberFuncao(ehPar); também funciona passando diretamente a função

    return 0;
}

🤹‍♂️ Aplicações e benefícios

Embora o uso de ponteiros para funções para confuso e desnecessário, ele tem algumas vantagens:

  • Flexibilidade: Ponteiros para funções permitem que você altere dinamicamente a função que será chamada em tempo de execução, proporcionando flexibilidade no design do programa.
  • CallBacks: Facilita a implementação de callbacks, onde funções são passadas como argumentos para outras funções, permitindo a execução de código personalizado em eventos específicos.

📖 Bibliografia

Livros

  • Deitel, P., & Deitel, H. (2022). C how to program: With case studies in applications and SystemsProgramming, global edition (9th ed.). Pearson Education.