Un puntero de función es una variable que contiene la dirección de una función. Dado que es una variable de puntero, aunque con algunas propiedades restringidas, puede usarla como lo haría con cualquier otra variable de puntero en las estructuras de datos.
La única excepción que se me ocurre es tratar el puntero de la función como si señalara algo más que un solo valor. Hacer aritmética de puntero incrementando o decrementando un puntero de función o sumando / restando un desplazamiento a un puntero de función no es realmente útil, ya que un puntero de función solo apunta a una sola cosa, el punto de entrada de una función.
El tamaño de una variable de puntero de función, el número de bytes ocupados por la variable, puede variar según la arquitectura subyacente, por ejemplo, x32 o x64 o lo que sea.
La declaración de una variable de puntero de función debe especificar el mismo tipo de información que una declaración de función para que el compilador de C realice los tipos de comprobaciones que normalmente realiza. Si no especifica una lista de parámetros en la declaración / definición del puntero de función, el compilador de C no podrá verificar el uso de parámetros. Hay casos en que esta falta de verificación puede ser útil, sin embargo, recuerde que se ha eliminado una red de seguridad.
Algunos ejemplos:
int func (int a, char *pStr); // declares a function
int (*pFunc)(int a, char *pStr); // declares or defines a function pointer
int (*pFunc2) (); // declares or defines a function pointer, no parameter list specified.
int (*pFunc3) (void); // declares or defines a function pointer, no arguments.
Las dos primeras declaraciones son algo similares en eso:
func
es una función que toma una int
y una char *
y devuelve unaint
pFunc
es un puntero de función a la que se asigna la dirección de una función que toma una int
y una char *
y devuelve unaint
Entonces, a partir de lo anterior, podríamos tener una línea de origen en la que la dirección de la función func()
se asigna a la variable puntero de la función pFunc
como en pFunc = func;
.
Observe la sintaxis utilizada con una declaración / definición de puntero de función en la que se utilizan paréntesis para superar las reglas de precedencia de operadores naturales.
int *pfunc(int a, char *pStr); // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr); // declares a function pointer that returns an int
Varios ejemplos de uso diferentes
Algunos ejemplos de uso de un puntero de función:
int (*pFunc) (int a, char *pStr); // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr); // declare a pointer to a function pointer variable
struct { // declare a struct that contains a function pointer
int x22;
int (*pFunc)(int a, char *pStr);
} thing = {0, func}; // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr)); // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr)); // declare a function pointer that points to a function that has a function pointer as an argument
Puede usar listas de parámetros de longitud variable en la definición de un puntero de función.
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
O no puede especificar una lista de parámetros en absoluto. Esto puede ser útil, pero elimina la oportunidad para que el compilador de C realice comprobaciones en la lista de argumentos proporcionada.
int sum (); // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int sum2(void); // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);
Bastidores estilo C
Puede usar conversiones de estilo C con punteros de función. Sin embargo, tenga en cuenta que un compilador de C puede ser flojo con respecto a las comprobaciones o proporcionar advertencias en lugar de errores.
int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum; // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum; // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum; // compiler error of bad cast generated, parenthesis are required.
Comparar el puntero de función con la igualdad
Puede verificar que un puntero de función sea igual a una dirección de función particular usando una if
declaración, aunque no estoy seguro de lo útil que sería. Otros operadores de comparación parecerían tener incluso menos utilidad.
static int func1(int a, int b) {
return a + b;
}
static int func2(int a, int b, char *c) {
return c[0] + a + b;
}
static int func3(int a, int b, char *x) {
return a + b;
}
static char *func4(int a, int b, char *c, int (*p)())
{
if (p == func1) {
p(a, b);
}
else if (p == func2) {
p(a, b, c); // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
} else if (p == func3) {
p(a, b, c);
}
return c;
}
Una matriz de punteros de función
Y si desea tener una matriz de punteros de función de cada uno de los elementos de los cuales la lista de argumentos tiene diferencias, entonces puede definir un puntero de función con la lista de argumentos sin especificar (lo void
que no significa que no haya argumentos sino simplemente sin especificar) algo como lo siguiente aunque puede ver advertencias del compilador de C. Esto también funciona para un parámetro de puntero de función a una función:
int(*p[])() = { // an array of function pointers
func1, func2, func3
};
int(**pp)(); // a pointer to a function pointer
p[0](a, b);
p[1](a, b, 0);
p[2](a, b); // oops, left off the last argument but it compiles anyway.
func4(a, b, 0, func1);
func4(a, b, 0, func2); // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);
// iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
func4(a, b, 0, p[i]);
}
// iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
(*pp)(a, b, 0); // pointer to a function pointer so must dereference it.
func4(a, b, 0, *pp); // pointer to a function pointer so must dereference it.
}
Estilo C namespace
Uso de Global struct
con punteros de función
Puede usar la static
palabra clave para especificar una función cuyo nombre es el alcance del archivo y luego asignarlo a una variable global como una forma de proporcionar algo similar a la namespace
funcionalidad de C ++.
En un archivo de encabezado, defina una estructura que será nuestro espacio de nombres junto con una variable global que lo use.
typedef struct {
int (*func1) (int a, int b); // pointer to function that returns an int
char *(*func2) (int a, int b, char *c); // pointer to function that returns a pointer
} FuncThings;
extern const FuncThings FuncThingsGlobal;
Luego, en el archivo fuente C:
#include "header.h"
// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
return a + b;
}
static char *func2 (int a, int b, char *c)
{
c[0] = a % 100; c[1] = b % 50;
return c;
}
const FuncThings FuncThingsGlobal = {func1, func2};
Esto se utilizaría especificando el nombre completo de la variable de estructura global y el nombre del miembro para acceder a la función. El const
modificador se usa en el global para que no se pueda cambiar por accidente.
int abcd = FuncThingsGlobal.func1 (a, b);
Áreas de aplicación de punteros de función
Un componente de biblioteca de DLL podría hacer algo similar al namespace
enfoque de estilo C en el que se solicita una interfaz de biblioteca particular de un método de fábrica en una interfaz de biblioteca que admite la creación de struct
punteros de función que contienen. Esta interfaz de biblioteca carga la versión de DLL solicitada, crea una estructura con los punteros de función necesarios, y luego devuelve la estructura al llamador solicitante para su uso.
typedef struct {
HMODULE hModule;
int (*Func1)();
int (*Func2)();
int(*Func3)(int a, int b);
} LibraryFuncStruct;
int LoadLibraryFunc LPCTSTR dllFileName, LibraryFuncStruct *pStruct)
{
int retStatus = 0; // default is an error detected
pStruct->hModule = LoadLibrary (dllFileName);
if (pStruct->hModule) {
pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
retStatus = 1;
}
return retStatus;
}
void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
if (pStruct->hModule) FreeLibrary (pStruct->hModule);
pStruct->hModule = 0;
}
y esto podría usarse como en:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
Se puede usar el mismo enfoque para definir una capa de hardware abstracta para el código que usa un modelo particular del hardware subyacente. Los punteros de función se completan con funciones específicas de hardware por una fábrica para proporcionar la funcionalidad específica de hardware que implementa las funciones especificadas en el modelo de hardware abstracto. Esto se puede usar para proporcionar una capa de hardware abstracta utilizada por el software que llama a una función de fábrica para obtener la interfaz de función de hardware específica y luego usa los punteros de función proporcionados para realizar acciones para el hardware subyacente sin necesidad de conocer los detalles de implementación sobre el objetivo específico .
Punteros de función para crear delegados, controladores y devoluciones de llamada
Puede usar punteros de función como una forma de delegar alguna tarea o funcionalidad. El ejemplo clásico en C es el puntero de función de delegado de comparación utilizado con las funciones de biblioteca estándar C qsort()
ybsearch()
para proporcionar el orden de clasificación para ordenar una lista de elementos o realizar una búsqueda binaria en una lista ordenada de elementos. El delegado de la función de comparación especifica el algoritmo de clasificación utilizado en la ordenación o la búsqueda binaria.
Otro uso es similar a la aplicación de un algoritmo a un contenedor de la Biblioteca de plantillas estándar de C ++.
void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for ( ; pList < pListEnd; pList += sizeItem) {
p (pList);
}
return pArray;
}
int pIncrement(int *pI) {
(*pI)++;
return 1;
}
void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for (; pList < pListEnd; pList += sizeItem) {
p(pList, pResult);
}
return pArray;
}
int pSummation(int *pI, int *pSum) {
(*pSum) += *pI;
return 1;
}
// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;
ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);
Otro ejemplo es con el código fuente de la GUI en el que se registra un controlador para un evento en particular al proporcionar un puntero de función que en realidad se llama cuando ocurre el evento. El marco Microsoft MFC con sus mapas de mensajes usa algo similar para manejar los mensajes de Windows que se entregan a una ventana o cadena.
Las funciones asincrónicas que requieren una devolución de llamada son similares a un controlador de eventos. El usuario de la función asincrónica llama a la función asincrónica para iniciar alguna acción y proporciona un puntero de función que la función asincrónica llamará una vez que se complete la acción. En este caso, el evento es la función asincrónica que completa su tarea.