Punteros de funciones en programación C con ejemplos

⚡ Resumen inteligente

En C, los punteros a funciones almacenan la dirección del código ejecutable, lo que permite a los programas llamar a funciones indirectamente, pasarlas como argumentos, construir tablas de despacho a partir de matrices e implementar funciones de devolución de llamada, como la rutina de comparación qsort, para diseños flexibles y reutilizables.

  • 🔗 Declaración: Un puntero a función utiliza el formato tipo_de_retorno (*nombre)(argumentos); los paréntesis alrededor del nombre son obligatorios.
  • 🔄 Intercambio por dirección: Pasar direcciones de variables a parámetros de puntero permite que una función cambie los valores originales de quien la llama, como en el ejemplo de intercambio.
  • 📦 Matrices: El nombre de un array ya es un puntero, por lo que las funciones pueden recibir un array o devolver un puntero a un array estático.
  • 🎛️ Tablas de despacho: Un array de punteros a funciones sustituye a las largas cadenas de sentencias switch o if, indexando directamente la operación elegida.
  • 🧊 punteros vacíos: Un parámetro o tipo de retorno void * acepta cualquier tipo, que la función convierte de nuevo antes de usarlo.
  • 📞 Devolución de llamada: Al pasar un puntero a una función como argumento, se habilitan las funciones de devolución de llamada, como la función de comparación qsort.
  • 🤖 Asistencia de IA: GitHub Copilot y los asistentes de IA generan la sintaxis de los punteros a funciones, las definiciones de tipos y las firmas de las funciones de devolución de llamada, y luego señalan los parámetros que no coinciden.

Punteros a funciones en C

Los punteros brindan grandes posibilidades a las funciones 'C' en las que estamos limitados a devolver un valor. Con parámetros de puntero, nuestras funciones ahora pueden procesar datos reales en lugar de una copia de los datos.

Para modificar los valores reales de las variables, la declaración de llamada pasa direcciones a parámetros de puntero en una función.

Ejemplo de punteros de funciones

Por ejemplo, el siguiente programa intercambia dos valores de dos:

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;}
}

Salida:

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

Ejemplo de punteros de funciones

El programa intercambia los valores reales de las variables porque la función accede a ellos por dirección usando puntero. Aquí discutiremos el proceso del programa:

  1. Declaramos que la función responsable del intercambioping Los dos valores variables, que toman dos punteros enteros como parámetros y devuelven cualquier valor cuando se llaman.
  2. En la función principal, declaramos e inicializamos dos variables enteras ('m' y 'n') y luego imprimimos sus valores respectivamente.
  3. Llamamos a la función swap() pasando la dirección de las dos variables como argumentos usando el símbolo comercial. Después de eso, imprimimos los nuevos valores intercambiados de las variables.
  4. Aquí definimos el contenido de la función swap() que toma dos direcciones de variables enteras como parámetros y declara una variable entera temporal utilizada como un tercer cuadro de almacenamiento para guardar una de las variables de valor que se colocará en la segunda variable.
  5. Guarde el contenido de la primera variable señalada por 'a' en la variable temporal.
  6. Almacene la segunda variable señalada por b en la primera variable señalada por a.
  7. Actualice la segunda variable (señalada por b) por el valor de la primera variable guardada en la variable temporal.

Funciones con parámetros de matriz

En C, no podemos pasar una matriz por valor a una función. Mientras que un nombre de matriz es un puntero (dirección), simplemente pasamos un nombre de matriz a una función, lo que significa pasar un puntero a la matriz.

Por ejemplo, consideremos el siguiente 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);}

Salida:

 Total summation is 471

Aquí explicaremos el código del programa con sus detalles.

Funciones con parámetros de matriz

  1. Declaramos y definimos la función add_array() que toma una dirección de matriz (puntero) con su número de elementos como parámetros y devuelve la suma total acumulada de estos elementos. El puntero se usa para iterar los elementos de la matriz (usando la notación p[k]) y acumulamos la suma en una variable local que se devolverá después de iterar toda la matriz de elementos.
  2. Declaramos e inicializamos una matriz de números enteros con cinco elementos enteros. Imprimimos la suma total pasando el nombre de la matriz (que actúa como dirección) y el tamaño de la matriz al agregar_matriz()función llamada como argumentos.

Funciones que devuelven una matriz

En C, podemos devolver un puntero a una matriz, como en el siguiente programa:

#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);}

Salida:

1
2
3
4
5

Y aquí discutiremos los detalles del programa.

Funciones que devuelven una matriz

  1. Definimos y declaramos una función que devuelve una dirección de matriz que contiene un valor entero y no toma ningún argumento.
  2. Declaramos un puntero entero que recibe la matriz completa creada después de llamar a la función e imprimimos su contenido iterando la matriz completa de cinco elementos.

Observe que se define un puntero, no una matriz, para almacenar la dirección de la matriz devuelta por la función. Observe también que cuando una función devuelve una variable local, tenemos que declararla como estática en la función.

Punteros de función

Como sabemos por definición que los punteros apuntan a una dirección en cualquier ubicación de la memoria, también pueden apuntar al comienzo del código ejecutable como funciones en la memoria.
Un puntero a función se declara con el *, la declaración general de su declaración es:

return_type (*function_name)(arguments)

Debe recordar que los paréntesis alrededor de (*nombre_función) son importantes porque sin ellos, el compilador pensará que nombre_función está devolviendo un puntero de tipo_retorno.
Después de definir el puntero de función, tenemos que asignarlo a una función. Por ejemplo, el siguiente programa declara una función ordinaria, define un puntero de función, asigna el puntero de función a la función ordinaria y luego llama a la función a través del puntero:

#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");}

Salida:

Hi
Hi
Hi

Punteros de funciones en C

  1. Definimos y declaramos una función estándar que imprime un texto Hola k veces indicado por el parámetro veces cuando se llama a la función.
  2. Definimos una función de puntero (con su declaración especial) que toma un parámetro entero y no devuelve nada.
  3. Inicializamos nuestra función de puntero con Hi_function, lo que significa que el puntero apunta a Hi_function().
  4. En lugar de la llamada a la función estándar por taping El nombre de la función con argumentos, llamamos solo a la función de puntero pasando el número 3 como argumentos, ¡y eso es todo!

Tenga en cuenta que el nombre de la función apunta a la dirección inicial del código ejecutable como un nombre de matriz que apunta a su primer elemento. Por lo tanto, instrucciones como function_ptr = &Hi_function y (*funptr)(3) son correctas.
NOTA: No es importante insertar el operador de dirección & y el operador de dirección indirecta * durante la asignación de función y la llamada de función.

Matriz de punteros de función

Una serie de punteros de función puede desempeñar un papel de interruptor o declaración if para tomar una decisión, como en el siguiente 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

Aquí discutimos los detalles del programa:

Matriz de punteros de función

  1. Declaramos y definimos cuatro funciones que toman dos argumentos enteros y devuelven un valor entero. Estas funciones suman, restantract, multiplica y divide los dos argumentos que indican qué función está siendo llamada por el usuario.
  2. Declaramos 4 números enteros para manejar operandos, tipo de operación y resultado respectivamente. Además, declaramos una matriz de cuatro punteros de función. Cada puntero de función del elemento de la matriz toma dos parámetros enteros y devuelve un valor entero.
  3. Asignamos e inicializamos cada elemento del array con la función ya declarada. Por ejemplo, el tercer elemento, que es el tercer puntero de función, apuntará a la función de operación de multiplicación.
  4. Buscamos operandos y tipo de operación por parte del usuario tecleado con el teclado.
  5. Llamamos al elemento de matriz apropiado (puntero de función) con argumentos y almacenamos el resultado generado por la función apropiada.

La instrucción int (*ope[4])(int, int); define la matriz de punteros de función. Cada elemento de la matriz debe tener los mismos parámetros y tipo de retorno.
El resultado de la declaración = ope[choice](x, y); ejecuta la función apropiada de acuerdo con la elección hecha por el usuario. Los dos números enteros ingresados ​​son los argumentos pasados ​​a la función.

Funciones que utilizan punteros vacíos

Los punteros nulos se utilizan durante las declaraciones de funciones. Usamos un tipo de devolución nulo * que permite devolver cualquier tipo. Si asumimos que nuestros parámetros no cambian al pasar a una función, la declaramos como constante.
Por ejemplo:

 void * cube (const void *);

Considere el siguiente 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

Aquí discutiremos los detalles del programa:

Funciones que utilizan punteros vacíos

  1. Definimos y declaramos una función que devuelve un valor entero y toma una dirección de variable inmutable sin un tipo de datos específico. Calculamos el valor cúbico de la variable de contenido (x) apuntada por el puntero num, y como es un puntero vacío, tenemos que escribirlo para convertirlo a un tipo de datos entero usando un puntero de notación específica (* tipo de datos) y devolvemos el valor del cubo.
  2. Declaramos el operando y la variable resultado. Además, inicializamos nuestro operando con el valor "4".
  3. Llamamos a la función del cubo pasando la dirección del operando y manejamos el valor de retorno en la variable de resultado.

Punteros de función como argumentos

Otra forma de explotar un puntero de función pasándolo como argumento a otra función a veces se le llama "función de devolución de llamada" porque la función receptora "la devuelve".
En el archivo de encabezado stdlib.h, la función Quicksort “qsort()” utiliza esta técnica, que es un algoritmo dedicado a ordenar una matriz.

void qsort(void *base, size_t num, size_t width, int (*compare)(const void *, const void *))
  • void *base: puntero vacío a la matriz.
  • size_t num: el número de elemento de la matriz.
  • size_t ancho El tamaño del elemento.
  • int (*compare (const void *, const void *): puntero de función compuesto por dos argumentos y devuelve 0 cuando los argumentos tienen el mismo valor, 0 cuando arg1 viene después de arg2.

El siguiente programa ordena una matriz de números enteros de pequeño a grande usando la función 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

Aquí discutiremos los detalles del programa:

Punteros de función como argumentos

  1. Definimos la función de comparación compuesta por dos argumentos y devuelve 0 cuando los argumentos tienen el mismo valor, 0 cuando arg1 viene después de arg2. Los parámetros son un tipo de punteros nulos convertidos al tipo de datos de matriz apropiado (entero)
  2. Definimos e inicializamos una matriz de enteros. El tamaño de la matriz se almacena en el número variable y el tamaño de cada elemento de la matriz se almacena en la variable de ancho usando sizeof() predefinido operador C.
  3. Llamamos a la función qsort y le pasamos el nombre, el tamaño, el ancho y la función de comparación del array, previamente definidos por el usuario, para ordenarlo en orden ascendente. La comparación se realizará tomando dos elementos del array en cada iteración hasta que todo el array esté ordenado.
  4. Imprimimos los elementos de la matriz para asegurarnos de que nuestra matriz esté bien ordenada iterando toda la matriz usando en bucle.

Preguntas Frecuentes

Sí. Un puntero a función debe coincidir exactamente con el tipo de retorno y la lista de parámetros de la función de destino. Asignar una función con una firma diferente genera un comportamiento indefinido, incluso si compila, ya que la llamada implicaría la lectura y el envío incorrectos de argumentos durante la ejecución.

typedef le da un nombre corto a un tipo de puntero a función complejo. Escribir typedef int (*Operación)(int, int); te permite declarar Operaagregar ción; en lugar de repetir la sintaxis completa del puntero, hace que los arreglos, parámetros y miembros de la estructura sean mucho más fáciles de leer.

Los punteros a funciones controlan las funciones de devolución de llamada, las tablas de salto o de despacho, los sistemas basados ​​en complementos y eventos, las máquinas de estados y las rutinas de biblioteca como qsort y bsearch. OperaLos sistemas de control y el firmware integrado dependen de ellos para seleccionar el comportamiento en tiempo de ejecución sin largas cadenas condicionales.

Un puntero de datos almacena la dirección de una variable en memoria, mientras que un puntero de función almacena la dirección de entrada del código ejecutable. Se llama a una función a través de un puntero de función para ejecutarla, en lugar de desreferenciarla para leer o escribir datos.

Sí. Un puntero a función puede establecerse en NULL para indicar que no se le asigna ninguna función. Llamar a un puntero a función NULL provoca un comportamiento indefinido, por lo que conviene proteger la llamada con `if (ptr != NULL)` antes de invocarla, especialmente para las funciones de devolución de llamada opcionales.

Sí. Colocar punteros a funciones como miembros de una estructura permite agrupar datos con operaciones relacionadas, imitando objetos y tablas de métodos virtuales. Este patrón es la base de muchas bibliotecas de C y controladores de dispositivos, donde cada instancia selecciona sus propias implementaciones de funciones.

Sí. Un asistente de codificación con IA puede generar declaraciones de punteros a funciones, definiciones de tipos y firmas de funciones de devolución de llamada a partir de una instrucción en lenguaje natural, explicar la sintaxis confusa e identificar parámetros o tipos de retorno incorrectos. Siempre compile y pruebe el código sugerido antes de confiar en él.

Copiloto de GitHub Sugiere sintaxis de punteros a funciones, completa los cuerpos de las funciones de devolución de llamada a partir de un comentario o firma, y ​​propone funciones de comparación de qsort. Abra los archivos de encabezado pertinentes para que sus sugerencias coincidan con los prototipos declarados y, a continuación, revise la lógica.

Resumir este post con: