Un puntero a función , también llamado puntero a subrutina o puntero a procedimiento , es un puntero que hace referencia a código ejecutable, en lugar de a datos. Al desreferenciar el puntero a función, se obtiene la función referenciada , que puede invocarse y recibir argumentos como en una llamada a función normal. Esta invocación también se conoce como llamada "indirecta", ya que la función se invoca indirectamente a través de una variable en lugar de directamente a través de un identificador o dirección fijos.
Los punteros a funciones permiten ejecutar código diferente en tiempo de ejecución. También se pueden pasar a una función para habilitar las devoluciones de llamada .
Los punteros a funciones son compatibles con los lenguajes de programación de tercera generación (como PL/I , COBOL , Fortran , [ 1 ] dBASE dBL y C ) y los lenguajes de programación orientados a objetos (como C++ , C# y D ). [ 2 ]
Punteros a funciones simples
La implementación más sencilla de un puntero a función (o subrutina) consiste en una variable que contiene la dirección de la función dentro de la memoria ejecutable. Los lenguajes de tercera generación más antiguos , como PL/I y COBOL , así como lenguajes más modernos como Pascal y C, generalmente implementan los punteros a función de esta manera. [ 3 ]
Los punteros a funciones se introdujeron en la versión 9.0 de C#delegate* , como . [ 4 ]
Ejemplo en C
El siguiente programa en C ilustra el uso de dos punteros a funciones:
func1toma un parámetro de doble precisión (double) y devuelve otro double, y se asigna a una función que convierte centímetros a pulgadas.func2Recibe un puntero a una matriz de caracteres constante, así como un número entero, y devuelve un puntero a un carácter. Este puntero se asigna a una función de manejo de cadenas de C que devuelve un puntero a la primera aparición de un carácter dado en una matriz de caracteres.
#include <stdio.h> #include <string.h>double cm_to_inches ( double cm ) { return cm / 2.54 ; }// "strchr" forma parte del manejo de cadenas de C (es decir, no necesita declaración) // Ver https://en.wikipedia.org/wiki/C_string_handling#Functionsint main ( void ) { double ( * func1 )( double ) = cm_to_inches ; char * ( * func2 )( const char * , int ) = strchr ; printf ( "%f %s" , func1 ( 15.0 ), func2 ( "Wikipedia" , 'p' )); // imprime "5.905512 pedia" return 0 ; }El siguiente programa utiliza un puntero a función para invocar indirectamente una de dos funciones ( sino cos) desde otra función ( compute_sum, calculando una aproximación de la integración de Riemann de la función ). El programa opera haciendo que la función mainllame a la función compute_sumdos veces, pasándole un puntero a la función de la biblioteca sinla primera vez, y un puntero a la función cosla segunda vez. La función compute_suma su vez invoca indirectamente una de las dos funciones desreferenciando su argumento de puntero a función funcpvarias veces, sumando los valores que devuelve la función invocada y devolviendo la suma resultante. Las dos sumas se escriben en la salida estándar mediante main.
#include <math.h>#include <stdio.h>// Función que recibe un puntero a función como argumento.double compute_sum ( double ( * funcp )( double ), double lo , double hi ) {doble suma = 0.0 ;// Agrega los valores devueltos por la función '*funcp' a la que apunta.para ( int i = 0 ; i <= 100 ; i ++ ) {// Utilice el puntero a función 'funcp' para invocar la funcióndoble x = i / 100.0 * ( alto - bajo ) + bajo ;doble y = funcp ( x );suma += y ;}devolver suma / 101.0 * ( alto - bajo );}cuadrado doble ( doble x ) {devolver x * x ;}int main ( void ) {suma doble ;// Utilizar la función de la biblioteca estándar 'sin()' como la función a la que apuntasuma = calcular_suma ( seno , 0.0 , 1.0 );printf ( "sum(sin): %g \n " , sum );// Utilizar la función de la biblioteca estándar 'cos()' como la función a la que apuntasuma = suma_calculada ( cos , 0,0 , 1,0 );printf ( "sum(cos): %g \n " , sum );// Utilizar la función definida por el usuario 'square()' como la función a la que apuntasuma = calcular_suma ( cuadrado , 0.0 , 1.0 );printf ( "sum(square): %g \n " , sum );devolver 0 ;}Funtores
Los functores, u objetos función, son similares a los punteros a funciones y pueden utilizarse de forma parecida. Un functor es un objeto de un tipo de clase que implementa el operador de llamada a función , lo que permite utilizarlo dentro de expresiones con la misma sintaxis que una llamada a función. Los functores son más potentes que los simples punteros a funciones, ya que pueden contener sus propios valores de datos y permiten al programador emular cierres . También se utilizan como funciones de devolución de llamada si es necesario utilizar una función miembro como tal. [ 5 ]
Muchos lenguajes orientados a objetos "puros" no admiten punteros a funciones. Sin embargo, se puede implementar algo similar en este tipo de lenguajes, utilizando referencias a interfaces que definen un único método (función miembro). Los lenguajes CLI como C# y Visual Basic .NET implementan punteros a funciones con tipado seguro mediante delegados .
En otros lenguajes que admiten funciones de primera clase , las funciones se consideran datos y pueden ser pasadas, devueltas y creadas dinámicamente directamente por otras funciones, eliminando la necesidad de punteros a funciones.
El uso extensivo de punteros a funciones para llamar a otras funciones puede ralentizar el código en los procesadores modernos, ya que un predictor de bifurcaciones puede no ser capaz de determinar adónde bifurcar (depende del valor del puntero a la función en tiempo de ejecución), aunque este efecto puede exagerarse, ya que a menudo se compensa ampliamente con una reducción significativa de las búsquedas en tablas no indexadas.
punteros de método
C++ incluye soporte para programación orientada a objetos , por lo que las clases pueden tener métodos (generalmente denominados funciones miembro). Las funciones miembro no estáticas (métodos de instancia) tienen un parámetro implícito (el puntero `this` ), que es el puntero al objeto sobre el que opera, por lo que el tipo del objeto debe incluirse como parte del tipo del puntero a la función. El método se utiliza entonces en un objeto de esa clase mediante uno de los operadores de "puntero a miembro": ` .*this` o ->*`this` (para un objeto o un puntero a un objeto, respectivamente).
Aunque los punteros a funciones en C y C++ pueden implementarse como simples direcciones, de modo que normalmente sizeof(Fx) == sizeof(void*), los punteros a miembros en C++ a veces se implementan como " punteros gordos ", normalmente dos o tres veces el tamaño de un puntero a función simple, para poder manejar métodos virtuales y herencia virtual .
En C++
En C++, además del método utilizado en C, también es posible utilizar la plantilla de clase de la biblioteca estándar de C++ std::function , cuyas instancias son objetos de función:
importar std ;static double derivative ( const std :: function < double ( double ) > & f , double x0 , double eps ) { double eps2 = eps / 2 ; double lo = x0 - eps2 ; double hi = x0 + eps2 ; return ( f ( hi ) - f ( lo )) / eps ; }static double f ( double x ) { return x * x ; }int main () { double x = 1 ; std :: println ( "d/dx(x^2) [@ x = {}] = {}" , x , derivative ( f , x , 1e-5 )); return 0 ; }Punteros a funciones miembro en C++
Así es como C++ utiliza punteros a funciones al trabajar con funciones miembro de clases o estructuras. Estas se invocan mediante un puntero a objeto o una llamada `this`. Son de tipo seguro, ya que solo se pueden llamar miembros de esa clase (o sus derivados) utilizando un puntero de ese tipo. Este ejemplo también muestra el uso de un ` typedef` para el puntero a la función miembro, añadido para simplificar. Los punteros a funciones miembro estáticas se implementan al estilo tradicional de C, ya que no se requiere un puntero a objeto para la llamada `this`.
importar std ;clase Foo { público : [[ nodiscard ]] static int add ( int i , int j ) noexcept { return i + j ; }[[ nodiscard ]] static int mult ( int i , int j ) noexcept { return i * j ; }[[ nodiscard ]] static int negate ( int i ) noexcept { return - i ; } };int bar1 ( int i , int j , Foo * pFoo , int ( Foo ::* pfn )( int , int )) { return ( pFoo ->* pfn )( i , j ); }typedef int ( Foo ::* Foo_pfn )( int , int );int bar2 ( int i , int j , Foo * pFoo , Foo_pfn pfn ) { return ( pFoo ->* pfn )( i , j ); }typedef auto ( * PFN )( int ) -> int ; // Solo C++, lo mismo que: typedef int(*PFN)(int);int bar3 ( int i , PFN pfn ) { return pfn ( i ); }int main () { Foo foo ; std :: println ( "Foo::add(2,4) = {}" , bar1 ( 2 , 4 , & foo , & Foo :: add )); std :: println ( "Foo::mult(3,5) = {}" , bar2 ( 3 , 5 , & foo , & Foo :: mult )); std :: println ( "Foo::negate(6) = {}" , bar3 ( 6 , & Foo :: negate )); return 0 ; }Sintaxis alternativa de C y C++
La sintaxis de C y C++ que se muestra arriba es la canónica que se usa en todos los libros de texto, pero es difícil de leer y explicar. Incluso los ejemplos anteriores usan esta sintaxis. Sin embargo, todos los compiladorestypedef de C y C++ admiten un mecanismo más claro y conciso para declarar punteros a funciones: usar , pero sin almacenar el puntero como parte de la definición. Nótese que la única forma en que este tipo de puede usarse realmente es con un puntero, lo que resalta su naturaleza de puntero.typedeftypedef
C y C++
// Esto declara 'F', una función que acepta un 'char' y devuelve un 'int'. La definición se encuentra en otra parte. int F ( char c );// Esto define 'Fn', un tipo de función que acepta un 'char' y devuelve un 'int'. typedef int Fn ( char c );// Esto define 'fn', una variable de tipo puntero a 'Fn', y le asigna la dirección de 'F'. Fn * fn = & F ; // Nótese que '&' no es necesario, pero resalta lo que se está haciendo.// Esto llama a 'F' usando 'fn', asignando el resultado a la variable 'a' int a = fn ( 'A' );// Esto define 'Call', una función que acepta un puntero a 'Fn', lo llama y devuelve el resultado. int Call ( Fn * fn , char c ) { return fn ( c ); } // Call(fn, c)// Esto llama a la función 'Call', pasándole 'F' y asignando el resultado a 'call'. int call = Call ( & F , 'A' ); // Nuevamente, '&' no es necesario.// HEREDADO: Tenga en cuenta que para mantener las bases de código existentes, aún se puede utilizar primero el estilo de definición anterior; // luego el tipo original se puede definir en términos de él utilizando el nuevo estilo.// Esto define 'PFn', un tipo de puntero a tipo Fn. typedef Fn * PFn ;// 'PFn' se puede usar donde se pueda usar 'Fn *' PFn pfn = F ; int CallP ( PFn fn , char c );C++
Estos ejemplos utilizan las definiciones anteriores. En particular, tenga en cuenta que la definición anterior para Fnpuede utilizarse en definiciones de punteros a funciones miembro:
// Esto define 'C', una clase con funciones estáticas y miembros similares, // y luego crea una instancia llamada 'c' class C { public : static int Static ( char c ); int Member ( char c ); } c ; // C// Esto define 'p', un puntero a 'C' y le asigna la dirección de 'c' C * p = & c ;// Esto asigna un puntero a 'Static' a 'fn'. // Como no hay 'this', 'Fn' es el tipo correcto; y 'fn' se puede usar como se indicó anteriormente. fn = & C :: Static ;// Esto define 'm', un puntero a un miembro de 'C' de tipo 'Fn', // y le asigna la dirección de 'C::Member'. // Se puede leer de derecha a izquierda como todos los punteros: // "'m' es un puntero a un miembro de la clase 'C' de tipo 'Fn'" Fn C ::* m = & C :: Member ;// Esto usa 'm' para llamar a 'Member' en 'c', asignando el resultado a 'cA' int cA = ( c . * m )( 'A' );// Esto usa 'm' para llamar a 'Member' en 'p', asignando el resultado a 'pA' int pA = ( p ->* m )( 'A' );// Esto define 'Ref', una función que acepta una referencia a 'C', // un puntero a un miembro de 'C' de tipo 'Fn' y un 'char', // llama a la función y devuelve el resultado int Ref ( C & r , Fn C ::* m , char c ) { return ( r . * m )( c ); } // Ref(r, m, c)// Esto define 'Ptr', una función que acepta un puntero a 'C', // un puntero a un miembro de 'C' de tipo 'Fn' y un 'char', // llama a la función y devuelve el resultado int Ptr ( C * p , Fn C ::* m , char c ) { return ( p ->* m )( c ); } // Ptr(p, m, c)// HEREDADO: Tenga en cuenta que para mantener las bases de código existentes, aún se puede utilizar primero el estilo de definición anterior; // luego el tipo original se puede definir en términos de él utilizando el nuevo estilo.// Esto define 'FnC', un tipo de puntero a miembro de la clase 'C' de tipo 'Fn' typedef Fn C ::* FnC ;// 'FnC' se puede usar dondequiera que se pueda usar 'Fn C::*' FnC fnC = & C :: Member ; int RefP ( C & p , FnC m , char c );PL/I
Los procedimientos PL/I pueden anidarse; es decir, el procedimiento A puede contener el procedimiento B, que a su vez puede contener el procedimiento C. Además de los datos declarados en B, B también puede hacer referencia a cualquier dato declarado en A, siempre que no sobrescriba su definición. Del mismo modo, C puede hacer referencia a datos tanto en A como en B. Por lo tanto, las variables de entrada PL/I deben contener el contexto [ 6 ] para proporcionar al procedimiento C las direcciones de los valores de los datos en B y A en el momento en que se llamó a C.
Véase también
Referencias
- ↑ Andrew J. Miller. "Ejemplos de Fortran" . Consultado el 14 de septiembre de 2013 .
- ↑ "Tutoriales de punteros a funciones" . Logotipo. Archivado del original el 16 de mayo de 2011. Consultado el 13 de abril de 2011.
Los punteros a funciones son punteros, es decir, variables, que apuntan a la dirección de una función
. - ↑ "Tutoriales de punteros a funciones" . Logotipo. Archivado del original el 16/05/2011 . Consultado el 13/04/2011 .
Nota importante: ¡Un puntero a función siempre apunta a una función con una firma específica! Por lo tanto, todas las funciones que desee utilizar con el mismo puntero a función deben tener los mismos parámetros y tipo de retorno.
- ↑ "Punteros a funciones en C# 9" . docs.microsoft.com . Consultado el 27 de febrero de 2021 .
- ↑ "Expertise: Intermediate Language: C++: Use Functor for Callbacks in C++" . DevX.com. 31/01/2005 . Consultado el 13/04/2011 .
Si desea utilizar una función miembro como función de devolución de llamada, dicha función debe estar asociada a un objeto de la clase antes de poder ser llamada. En este caso, puede utilizar functor [con un ejemplo en esta página].
- ↑ Abrahams, Paul (marzo de 1978). El lenguaje de programación PL/I (PDF) . Laboratorio de Matemáticas e Informática Courant, Universidad de Nueva York. págs. 22–24 . Recuperado el 4 de abril de 2025 .
Enlaces externos
- Preguntas frecuentes sobre punteros a funciones , cosas que se deben evitar con los punteros a funciones, información sobre el uso de objetos de función.
- Tutoriales sobre punteros a funciones archivados el 30/06/2018 en Wayback Machine , una guía sobre punteros a funciones, devoluciones de llamada y objetos de función (functores) en C/C++.
- Punteros a funciones miembro y los delegados de C++ más rápidos posibles , artículo de CodeProject por Don Clugston
- Tutoriales sobre punteros archivados el 5 de abril de 2009 en Wayback Machine , documentación y tutoriales de C++.
- Explicación de punteros en C Archivado el 9 de junio de 2019 en Wayback Machine Una guía visual de punteros en C
- Punteros a funciones y devoluciones de llamada seguras en la programación de Windows , artículo de CodeProject por R. Selvam
- El libro de C , Punteros a funciones en C por "El libro de C"
- Punteros a funciones en dBASE dBL , Puntero a función en dBASE dBL
- Punteros (programación informática)
- Subrutinas