Un puntatore a funzione è una variabile che contiene l'indirizzo di una funzione. Poiché si tratta di una variabile puntatore, sebbene con alcune proprietà limitate, è possibile utilizzarlo praticamente come qualsiasi altra variabile puntatore nelle strutture dati.
L'unica eccezione che mi viene in mente è quella di considerare il puntatore a funzione come un riferimento a qualcosa di diverso da un singolo valore. Fare l'aritmetica del puntatore incrementando o decrementando un puntatore a funzione o aggiungendo / sottraendo un offset a un puntatore a funzione non è in realtà alcuna utilità poiché un puntatore a funzione punta solo a una singola cosa, il punto di ingresso di una funzione.
La dimensione di una variabile del puntatore a funzione, il numero di byte occupati dalla variabile, può variare a seconda dell'architettura sottostante, ad esempio x32 o x64 o altro.
La dichiarazione per una variabile di puntatore a funzione deve specificare lo stesso tipo di informazioni di una dichiarazione di funzione affinché il compilatore C esegua i tipi di controlli che esegue normalmente. Se non si specifica un elenco di parametri nella dichiarazione / definizione del puntatore a funzione, il compilatore C non sarà in grado di verificare l'uso dei parametri. Ci sono casi in cui questa mancanza di controllo può essere utile, ma ricorda solo che è stata rimossa una rete di sicurezza.
Qualche esempio:
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.
Le prime due dichiarazioni sono in qualche modo simili in quanto:
func
è una funzione che accetta un int
e a char *
e restituisce anint
pFunc
è un puntatore a cui è assegnato l'indirizzo di una funzione che accetta un int
e a char *
e restituisce anint
Quindi da quanto sopra potremmo avere una linea sorgente in cui l'indirizzo della funzione func()
è assegnato alla variabile del puntatore a funzione pFunc
come in pFunc = func;
.
Notare la sintassi utilizzata con una dichiarazione / definizione del puntatore a funzione in cui le parentesi vengono utilizzate per superare le regole di precedenza dell'operatore naturale.
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
Diversi esempi di utilizzo
Alcuni esempi di utilizzo di un puntatore a funzione:
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
È possibile utilizzare elenchi di parametri a lunghezza variabile nella definizione di un puntatore a funzione.
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
Oppure non è possibile specificare un elenco di parametri. Questo può essere utile ma elimina la possibilità per il compilatore C di eseguire controlli sull'elenco degli argomenti fornito.
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);
Calchi in stile C.
È possibile utilizzare i cast in stile C con i puntatori a funzione. Tuttavia, tieni presente che un compilatore C potrebbe essere in ritardo sui controlli o fornire avvisi anziché errori.
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.
Confronta il puntatore a funzione con l'uguaglianza
È possibile verificare che un puntatore a funzione sia uguale a un indirizzo di funzione particolare usando if
un'istruzione sebbene non sia sicuro di quanto utile sarebbe. Altri operatori di confronto sembrerebbero avere ancora meno utilità.
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 matrice di puntatori di funzioni
E se vuoi avere una matrice di puntatori a funzione ciascuno degli elementi di cui l'elenco degli argomenti presenta differenze, puoi definire un puntatore a funzione con l'elenco degli argomenti non specificato (non void
che significa nessun argomento ma solo non specificato) qualcosa come il seguente sebbene tu potrebbe visualizzare avvisi dal compilatore C. Questo funziona anche per un parametro del puntatore a una funzione:
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.
}
Stile C namespace
Utilizzo di Globalstruct
con puntatori a funzione
Puoi usare il static
parola chiave per specificare una funzione il cui nome è ambito del file e quindi assegnarla a una variabile globale come modo per fornire qualcosa di simile alla namespace
funzionalità di C ++.
In un file di intestazione definire una struttura che sarà il nostro spazio dei nomi insieme a una variabile globale che lo utilizza.
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;
Quindi nel file sorgente 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};
Questo verrebbe quindi usato specificando il nome completo della variabile strutturale globale e il nome del membro per accedere alla funzione. Il const
modificatore viene utilizzato a livello globale in modo che non possa essere modificato accidentalmente.
int abcd = FuncThingsGlobal.func1 (a, b);
Aree di applicazione degli indicatori di funzione
Un componente di libreria DLL potrebbe fare qualcosa di simile namespace
all'approccio in stile C in cui una particolare interfaccia di libreria è richiesta da un metodo factory in un'interfaccia di libreria che supporta la creazione di struct
puntatori di funzioni contenenti .. Questa interfaccia di libreria carica la versione DLL richiesta, crea una struttura con i puntatori di funzione necessari, quindi restituisce la struttura al chiamante richiedente per l'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;
}
e questo potrebbe essere usato come in:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
Lo stesso approccio può essere utilizzato per definire un livello hardware astratto per il codice che utilizza un modello particolare dell'hardware sottostante. I puntatori a funzione sono compilati con funzioni specifiche dell'hardware da una fabbrica per fornire la funzionalità specifica dell'hardware che implementa le funzioni specificate nel modello hardware astratto. Questo può essere utilizzato per fornire un livello hardware astratto utilizzato dal software che chiama una funzione di fabbrica al fine di ottenere l'interfaccia di funzione hardware specifica, quindi utilizza i puntatori di funzione forniti per eseguire azioni per l'hardware sottostante senza bisogno di conoscere i dettagli di implementazione sulla destinazione specifica .
Puntatori a funzione per creare delegati, gestori e callback
È possibile utilizzare i puntatori a funzione come modo per delegare alcune attività o funzionalità. L'esempio classico in C è il puntatore alla funzione di delega di confronto utilizzato con le funzioni della libreria C standard qsort()
ebsearch()
per fornire l'ordine di confronto per ordinare un elenco di elementi o eseguire una ricerca binaria su un elenco ordinato di elementi. Il delegato della funzione di confronto specifica l'algoritmo di confronto utilizzato nell'ordinamento o nella ricerca binaria.
Un altro uso è simile all'applicazione di un algoritmo a un contenitore Libreria di modelli standard 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);
Un altro esempio è con il codice sorgente della GUI in cui viene registrato un gestore per un particolare evento fornendo un puntatore a funzione che viene effettivamente chiamato quando si verifica l'evento. Il framework Microsoft MFC con le sue mappe dei messaggi utilizza qualcosa di simile per gestire i messaggi di Windows che vengono recapitati a una finestra o thread.
Le funzioni asincrone che richiedono un callback sono simili a un gestore eventi. L'utente della funzione asincrona chiama la funzione asincrona per avviare un'azione e fornisce un puntatore a funzione che la funzione asincrona chiamerà una volta completata l'azione. In questo caso l'evento è la funzione asincrona che completa il suo compito.