Ponteiros de funções em programação C com exemplos

⚡ Resumo Inteligente

Em C, os ponteiros de função armazenam o endereço do código executável, permitindo que os programas chamem funções indiretamente, as passem como argumentos, construam tabelas de despacho a partir de arrays e implementem funções de retorno de chamada, como a rotina de comparação qsort, para projetos flexíveis e reutilizáveis.

  • 🔗 Declaração: Um ponteiro de função usa a forma tipo_de_retorno (*nome)(argumentos); os parênteses em torno do nome são obrigatórios.
  • 🔄 Troca por endereço: Passar endereços de variáveis ​​para parâmetros de ponteiro permite que uma função altere os valores originais da função que a chamou, como no exemplo de troca.
  • 📦 Matrizes: O nome de um array já é um ponteiro, portanto as funções podem receber um array ou retornar um ponteiro para um array estático.
  • 🎛️ Tabelas de despacho: Um array de ponteiros de função substitui longas cadeias de switch ou if, indexando diretamente a operação escolhida.
  • 🧊 ponteiros vazios: Um parâmetro ou tipo de retorno void * aceita qualquer tipo, que a função converte de volta antes de usar.
  • 📞 Chamadas de retorno: Passar um ponteiro de função como argumento ativa funções de retorno de chamada, como a função de comparação `qsort`.
  • 🤖 Assistência de IA: O GitHub Copilot e os assistentes de IA criam a estrutura da sintaxe do ponteiro de função, typedefs e assinaturas de retorno de chamada, e então sinalizam parâmetros incompatíveis.

Ponteiros de função em C

Os ponteiros oferecem grandes possibilidades para funções 'C', nas quais estamos limitados a retornar um valor. Com parâmetros de ponteiro, nossas funções agora podem processar dados reais em vez de uma cópia de data .

Para modificar os valores reais das variáveis, a instrução de chamada passa endereços para parâmetros de ponteiro em uma função.

Exemplo de ponteiros de funções

Por exemplo, o próximo programa troca dois valores de dois:

void swap (int *a, int *b);
int main() {
  int m = 25;
  int n = 100;
  printf("m is %d, n is %d\n", m, n);
  swap(&m, &n);
  printf("m is %d, n is %d\n", m, n);
  return 0;}
void swap (int *a, int *b) {
  int temp;
  temp = *a;
  *a = *b;
  *b = temp;}
}

Saída:

m is 25, n is 100
m is 100, n is 25

Exemplo de ponteiros de funções

O programa troca os valores reais das variáveis ​​porque a função as acessa por endereço usando apontador. Aqui discutiremos o processo do programa:

  1. Declaramos a função responsável pela troca.ping Os dois valores variáveis, que recebem dois ponteiros inteiros como parâmetros e retornam qualquer valor quando são chamados.
  2. Na função principal, declaramos e inicializamos duas variáveis ​​inteiras ('m' e 'n') e depois imprimimos seus valores respectivamente.
  3. Chamamos a função swap() passando o endereço das duas variáveis ​​​​como argumentos usando o símbolo e comercial. Depois disso, imprimimos os novos valores trocados das variáveis.
  4. Aqui definimos o conteúdo da função swap() que recebe dois endereços de variáveis ​​inteiras como parâmetros e declara uma variável inteira temporária usada como uma terceira caixa de armazenamento para salvar uma das variáveis ​​de valor que será colocada na segunda variável.
  5. Salve o conteúdo da primeira variável apontada por 'a' na variável temporária.
  6. Armazene a segunda variável apontada por b na primeira variável apontada por a.
  7. Atualize a segunda variável (apontada por b) pelo valor da primeira variável salva na variável temporária.

Funções com parâmetros de array

Em C, não podemos passar um array por valor para uma função. Considerando que o nome de um array é um ponteiro (endereço), apenas passamos um nome de array para uma função, o que significa passar um ponteiro para o array.

Por exemplo, consideramos o seguinte programa:

int add_array (int *a, int num_elements);
int main() {
  int Tab[5] = {100, 220, 37, 16, 98};
  printf("Total summation is %d\n", add_array(Tab, 5)); 
  return 0;}
int add_array (int *p, int size) {
  int total = 0;
  int k;
  for (k = 0; k < size; k++) {
    total += p[k];  /* it is equivalent to total +=*p ;p++; */}
 return (total);}

Saída:

 Total summation is 471

Aqui, explicaremos o código do programa com seus detalhes

Funções com parâmetros de array

  1. Declaramos e definimos a função add_array() que recebe um endereço de array (ponteiro) com o número de seus elementos como parâmetros e retorna a soma total acumulada desses elementos. O ponteiro é usado para iterar os elementos do array (usando a notação p[k]), e acumulamos o somatório em uma variável local que será retornada após iterar todo o array de elementos.
  2. Declaramos e inicializamos um array inteiro com cinco elementos inteiros. Imprimimos o somatório total passando o nome do array (que atua como endereço) e o tamanho do array para o add_array()função chamada como argumentos.

Funções que retornam um array

Em C, podemos retornar um ponteiro para um array, como no programa a seguir:

#include <stdio.h>
int * build_array();
int main() {
  int *a;
  a = build_array(); /* get first 5 even numbers */
  for (k = 0; k < 5; k++)
    printf("%d\n", a[k]);
  return 0;}
int * build_array() {
  static int Tab[5]={1,2,3,4,5};
   return (Tab);}

Saída:

1
2
3
4
5

E aqui discutiremos os detalhes do programa

Funções que retornam um array

  1. Definimos e declaramos uma função que retorna um endereço de array contendo um valor inteiro e não aceita nenhum argumento.
  2. Declaramos um ponteiro inteiro que recebe o array completo construído após a chamada da função e imprimimos seu conteúdo iterando todo o array de cinco elementos.

Observe que um ponteiro, e não um array, é definido para armazenar o endereço do array retornado pela função. Observe também que quando uma variável local está sendo retornada de uma função, temos que declará-la como estática na função.

Ponteiros de função

Como sabemos por definição que os ponteiros apontam para um endereço em qualquer local da memória, eles também podem apontar para o início do código executável como funções na memória.
Um ponteiro para função é declarado com * , a declaração geral de sua declaração é:

return_type (*function_name)(arguments)

Você deve lembrar que os parênteses em torno de (*nome_da_função) são importantes porque sem eles, o compilador pensará que o nome_da_função está retornando um ponteiro de tipo_de_retorno.
Depois de definir o ponteiro de função, temos que atribuí-lo a uma função. Por exemplo, o próximo programa declara uma função ordinária, define um ponteiro de função, atribui o ponteiro de função à função ordinária e depois chama a função através do ponteiro:

#include <stdio.h>
void Hi_function (int times); /* function */
int main() {
  void (*function_ptr)(int);  /* function pointer Declaration */
  function_ptr = Hi_function;  /* pointer assignment */
  function_ptr (3);  /* function call */
 return 0;}
void Hi_function (int times) {
  int k;
  for (k = 0; k < times; k++) printf("Hi\n");}

Saída:

Hi
Hi
Hi

Ponteiros de funções em C

  1. Definimos e declaramos uma função padrão que imprime um texto Hi k vezes indicado pelo parâmetro vezes quando a função é chamada
  2. Definimos uma função ponteiro (com sua declaração especial) que recebe um parâmetro inteiro e não retorna nada.
  3. Inicializamos nossa função de ponteiro com Hi_function, o que significa que o ponteiro aponta para Hi_function().
  4. Em vez da chamada de função padrão por taping O nome da função com argumentos, chamamos apenas a função ponteiro passando o número 3 como argumento, e é só isso!

Tenha em mente que o nome da função aponta para o endereço inicial do código executável como um nome de array que aponta para seu primeiro elemento. Portanto, instruções como function_ptr = &Hi_function e (*funptr)(3) estão corretas.
NOTA: Não é importante inserir o operador de endereço & e o operador de indireção * durante a atribuição de função e chamada de função.

Matriz de ponteiros de função

Uma matriz de ponteiros de função pode desempenhar uma função de switch ou de instrução if para tomar uma decisão, como no próximo programa:

#include <stdio.h>
int sum(int num1, int num2);
int sub(int num1, int num2);
int mult(int num1, int num2);
int div(int num1, int num2);

int main() 
{  int x, y, choice, result;
  int (*ope[4])(int, int);
  ope[0] = sum;
  ope[1] = sub;
  ope[2] = mult;
  ope[3] = div;
  printf("Enter two integer numbers: ");
  scanf("%d%d", &x, &y);
  printf("Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: ");
  scanf("%d", &choice);
  result = ope[choice](x, y);
  printf("%d", result);
return 0;}

int sum(int x, int y) {return(x + y);}
int sub(int x, int y) {return(x - y);}
int mult(int x, int y) {return(x * y);}
int div(int x, int y) {if (y != 0) return (x / y); else  return 0;}
Enter two integer numbers: 13 48
Enter 0 to sum, 1 to subtract, 2 to multiply, or 3 to divide: 2
624

Aqui, discutimos os detalhes do programa:

Matriz de ponteiros de função

  1. Declaramos e definimos quatro funções que recebem dois argumentos inteiros e retornam um valor inteiro. Essas funções somam, subtraem, etc.tract, multiplique e divida os dois argumentos referentes à função que está sendo chamada pelo usuário.
  2. Declaramos 4 inteiros para lidar com operandos, tipo de operação e resultado, respectivamente. Além disso, declaramos um array de quatro ponteiros de função. Cada ponteiro de função do elemento da matriz recebe dois parâmetros inteiros e retorna um valor inteiro.
  3. Atribuímos e inicializamos cada elemento do array com a função já declarada. Por exemplo, o terceiro elemento que é o terceiro ponteiro de função apontará para a função de operação de multiplicação.
  4. Buscamos operandos e tipo de operação do usuário digitado com o teclado.
  5. Chamamos o elemento apropriado da matriz (ponteiro de função) com argumentos e armazenamos o resultado gerado pela função apropriada.

A instrução int (*ope[4])(int, int); define a matriz de ponteiros de função. Cada elemento da matriz deve ter os mesmos parâmetros e tipo de retorno.
A instrução result = ope[choice](x, y); executa a função apropriada de acordo com a escolha feita pelo usuário. Os dois inteiros inseridos são os argumentos passados ​​para a função.

Funções usando ponteiros vazios

Ponteiros vazios são usados ​​durante declarações de funções. Usamos um tipo de retorno void * que permite retornar qualquer tipo. Se assumirmos que nossos parâmetros não mudam ao passar para uma função, nós os declaramos como const.
Por exemplo:

 void * cube (const void *);

Considere o seguinte programa:

#include <stdio.h>
void* cube (const void* num);
int main() {
  int x, cube_int;
  x = 4;
  cube_int = cube (&x);
  printf("%d cubed is %d\n", x, cube_int);
  return 0;}

void* cube (const void *num) {
  int result;
  result = (*(int *)num) * (*(int *)num) * (*(int *)num);
  return result;}

Resultado:

 4 cubed is 64

Aqui, discutiremos os detalhes do programa:

Funções usando ponteiros vazios

  1. Definimos e declaramos uma função que retorna um valor inteiro e recebe um endereço de variável imutável sem um tipo de dados específico. Calculamos o valor do cubo da variável de conteúdo (x) apontado pelo ponteiro num, e como é um ponteiro vazio, temos que digitá-lo para um tipo de dados inteiro usando um ponteiro de notação específica (* tipo de dados), e retornamos o valor do cubo.
  2. Declaramos o operando e a variável de resultado. Além disso, inicializamos nosso operando com valor “4”.
  3. Chamamos a função cubo passando o endereço do operando e tratamos o valor retornado na variável de resultado

Ponteiros de função como argumentos

Outra maneira de explorar um ponteiro de função, passando-o como argumento para outra função, às vezes chamada de “função de retorno de chamada” porque a função receptora “chama-o de volta”.
No arquivo de cabeçalho stdlib.h, a função Quicksort “qsort()” usa esta técnica que é um algoritmo dedicado a classificar um array.

void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *))
  • void *base: ponteiro vazio para o array.
  • size_t num: o número do elemento da matriz.
  • size_t largura O tamanho do elemento.
  • int (*compare (const void *, const void *) : ponteiro de função composto por dois argumentos e retorna 0 quando os argumentos têm o mesmo valor, <0 quando arg1 vem antes de arg2 e >0 quando arg1 vem depois de arg2.

O programa a seguir classifica um array de inteiros de um número pequeno para um grande usando a função qsort():

#include <stdio.h>
#include <stdlib.h>
int compare (const void *, const void *); 
int main() {
  int arr[5] = {52, 14, 50, 48, 13};
  int num, width, i;
  num = sizeof(arr)/sizeof(arr[0]);
  width = sizeof(arr[0]);
  qsort((void *)arr, num, width, compare);
  for (i = 0; i < 5; i++)
    printf("%d ", arr[ i ]);
  return 0;}
int compare (const void *elem1, const void *elem2) {
  if ((*(int *)elem1) == (*(int *)elem2))  return 0;
  else if ((*(int *)elem1) < (*(int *)elem2)) return -1;
  else return 1;}

Resultado:

 13 14 48 50 52

Aqui, discutiremos os detalhes do programa:

Ponteiros de função como argumentos

  1. Definimos a função de comparação composta por dois argumentos e retorna 0 quando os argumentos têm o mesmo valor, <0 quando arg1 vem antes de arg2 e> 0 quando arg1 vem depois de arg2. (inteiro)
  2. Definimos e inicializamos um array inteiro O tamanho do array é armazenado no Números variável e o tamanho de cada elemento da matriz é armazenado na variável de largura usando sizeof() predefinido Operador C.
  3. Chamamos a função qsort e passamos o nome do array, seu tamanho, largura e a função de comparação definida previamente pelo usuário para ordenar o array em ordem crescente. A comparação será realizada selecionando dois elementos do array a cada iteração, até que todo o array esteja ordenado.
  4. Imprimimos os elementos do array para ter certeza de que nosso array está bem classificado, iterando o array inteiro usando para laço.

Perguntas Frequentes

Sim. Um ponteiro de função deve corresponder exatamente ao tipo de retorno e à lista de parâmetros da função alvo. Atribuir um ponteiro a uma função com uma assinatura diferente resulta em comportamento indefinido, mesmo que o código compile, pois a chamada resultaria em erros de empilhamento e leitura de argumentos em tempo de execução.

typedef atribui um nome curto a um tipo de ponteiro de função complexo. Escrever typedef int (*Operação)(int, int); permite que você declare Operation add; em vez de repetir a sintaxe completa do ponteiro, torna os arrays, parâmetros e membros de structs muito mais fáceis de ler.

Os ponteiros de função controlam callbacks, tabelas de salto ou despacho, sistemas orientados a plugins e eventos, máquinas de estado e rotinas de biblioteca como qsort e bsearch. OperaSistemas de navegação e firmware embarcado dependem deles para selecionar o comportamento em tempo de execução sem longas cadeias condicionais.

Um ponteiro de dados armazena o endereço de uma variável na memória, enquanto um ponteiro de função armazena o endereço de entrada do código executável. Você chama uma função por meio de um ponteiro de função para executá-la, em vez de desreferenciá-la para ler ou escrever dados.

Sim. Um ponteiro de função pode ser definido como NULL para indicar que nenhuma função foi atribuída. Chamar um ponteiro de função NULL causa comportamento indefinido, portanto, proteja a chamada com `if (ptr != NULL)` antes de invocá-la, especialmente para callbacks opcionais.

Sim. Colocar ponteiros de função como membros de uma estrutura permite que uma estrutura agrupe dados com operações relacionadas, imitando objetos e tabelas de métodos virtuais. Esse padrão está na base de muitas bibliotecas C e drivers de dispositivo, onde cada instância seleciona suas próprias implementações de função.

Sim. Um assistente de codificação com IA pode gerar declarações de ponteiros de função, typedefs e assinaturas de callback a partir de um prompt em linguagem natural, explicar sintaxes confusas e identificar parâmetros ou tipos de retorno incompatíveis. Sempre compile e teste o código sugerido antes de confiar nele.

Copiloto do GitHub Sugere sintaxe de ponteiro de função, completa corpos de retorno de chamada a partir de um comentário ou assinatura e propõe funções de comparação qsort. Abra os arquivos de cabeçalho relevantes para que suas sugestões correspondam aos seus protótipos declarados e, em seguida, revise a lógica.

Resuma esta postagem com: