Ein Funktionszeiger ist eine Variable, die die Adresse einer Funktion enthält. Da es sich jedoch um eine Zeigervariable mit einigen eingeschränkten Eigenschaften handelt, können Sie sie wie jede andere Zeigervariable in Datenstrukturen verwenden.
Die einzige Ausnahme, die ich mir vorstellen kann, besteht darin, den Funktionszeiger so zu behandeln, dass er auf etwas anderes als einen einzelnen Wert zeigt. Das Ausführen einer Zeigerarithmetik durch Inkrementieren oder Dekrementieren eines Funktionszeigers oder Hinzufügen / Subtrahieren eines Versatzes zu einem Funktionszeiger ist nicht wirklich nützlich, da ein Funktionszeiger nur auf eine einzelne Sache zeigt, den Einstiegspunkt einer Funktion.
Die Größe einer Funktionszeigervariablen, die Anzahl der von der Variablen belegten Bytes, kann abhängig von der zugrunde liegenden Architektur variieren, z. B. x32 oder x64 oder was auch immer.
Die Deklaration für eine Funktionszeigervariable muss dieselbe Art von Informationen wie eine Funktionsdeklaration angeben, damit der C-Compiler die normalerweise durchgeführten Überprüfungen durchführen kann. Wenn Sie in der Deklaration / Definition des Funktionszeigers keine Parameterliste angeben, kann der C-Compiler die Verwendung von Parametern nicht überprüfen. Es gibt Fälle, in denen diese mangelnde Überprüfung hilfreich sein kann. Denken Sie jedoch daran, dass ein Sicherheitsnetz entfernt wurde.
Einige Beispiele:
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.
Die ersten beiden Erklärungen sind insofern etwas ähnlich:
func
ist eine Funktion, die ein int
und ein nimmt und ein char *
zurückgibtint
pFunc
ist ein Funktionszeiger, dem die Adresse einer Funktion zugewiesen ist, die ein int
und ein nimmt und ein char *
zurückgibtint
Von oben könnten wir also eine Quellzeile haben, in der die Adresse der Funktion func()
der Funktionszeigervariablen pFunc
wie in zugewiesen wird pFunc = func;
.
Beachten Sie die Syntax, die mit einer Funktionszeigerdeklaration / -definition verwendet wird, in der Klammern verwendet werden, um die Vorrangregeln für natürliche Operatoren zu überwinden.
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
Verschiedene Verwendungsbeispiele
Einige Beispiele für die Verwendung eines Funktionszeigers:
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
Sie können Parameterlisten variabler Länge bei der Definition eines Funktionszeigers verwenden.
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
Oder Sie können überhaupt keine Parameterliste angeben. Dies kann nützlich sein, verhindert jedoch, dass der C-Compiler die bereitgestellte Argumentliste überprüfen kann.
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);
Casts im C-Stil
Sie können Casts im C-Stil mit Funktionszeigern verwenden. Beachten Sie jedoch, dass ein C-Compiler bei Überprüfungen möglicherweise nachlässig ist oder eher Warnungen als Fehler bereitstellt.
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.
Funktionszeiger mit Gleichheit vergleichen
Sie können mithilfe einer if
Anweisung überprüfen, ob ein Funktionszeiger einer bestimmten Funktionsadresse entspricht, obwohl ich nicht sicher bin, wie nützlich dies wäre. Andere Vergleichsoperatoren scheinen noch weniger nützlich zu sein.
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;
}
Ein Array von Funktionszeigern
Und wenn Sie ein Array von Funktionszeigern haben möchten, von denen jedes der Elemente in der Argumentliste Unterschiede aufweist, können Sie einen Funktionszeiger definieren, dessen Argumentliste nicht angegeben ist ( void
was nicht bedeutet, dass keine Argumente, sondern nur nicht angegeben sind) Möglicherweise werden Warnungen vom C-Compiler angezeigt. Dies funktioniert auch für einen Funktionszeigerparameter auf eine Funktion:
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.
}
C-Stil namespace
Verwenden von Global struct
mit Funktionszeigern
Mit dem static
Schlüsselwort können Sie eine Funktion angeben, deren Name der Dateibereich ist, und diese dann einer globalen Variablen zuweisen, um etwas Ähnliches wie die namespace
Funktionalität von C ++ bereitzustellen.
Definieren Sie in einer Header-Datei eine Struktur, die unser Namespace ist, zusammen mit einer globalen Variablen, die sie verwendet.
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;
Dann in der C-Quelldatei:
#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};
Dies wird dann verwendet, indem der vollständige Name der globalen Strukturvariablen und der Mitgliedsname angegeben werden, um auf die Funktion zuzugreifen. Der const
Modifikator wird global verwendet, damit er nicht versehentlich geändert werden kann.
int abcd = FuncThingsGlobal.func1 (a, b);
Anwendungsbereiche von Funktionszeigern
Eine DLL-Bibliothekskomponente könnte etwas Ähnliches wie der C-Stil- namespace
Ansatz tun, bei dem eine bestimmte Bibliotheksschnittstelle von einer Factory-Methode in einer Bibliotheksschnittstelle angefordert wird, die die Erstellung von struct
enthaltenen Funktionszeigern unterstützt. Diese Bibliotheksschnittstelle lädt die angeforderte DLL-Version und erstellt sie eine Struktur mit den erforderlichen Funktionszeigern und gibt die Struktur dann zur Verwendung an den anfordernden Aufrufer zurück.
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;
}
und dies könnte wie folgt verwendet werden:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
Der gleiche Ansatz kann verwendet werden, um eine abstrakte Hardwareschicht für Code zu definieren, der ein bestimmtes Modell der zugrunde liegenden Hardware verwendet. Funktionszeiger werden von einer Fabrik mit hardwarespezifischen Funktionen gefüllt, um die hardwarespezifische Funktionalität bereitzustellen, die die im abstrakten Hardwaremodell angegebenen Funktionen implementiert. Dies kann verwendet werden, um eine abstrakte Hardwareschicht bereitzustellen, die von Software verwendet wird, die eine Factory-Funktion aufruft, um die spezifische Hardwarefunktionsschnittstelle abzurufen, und dann die bereitgestellten Funktionszeiger verwendet, um Aktionen für die zugrunde liegende Hardware auszuführen, ohne Implementierungsdetails über das spezifische Ziel zu kennen .
Funktionszeiger zum Erstellen von Delegaten, Handlern und Rückrufen
Sie können Funktionszeiger verwenden, um bestimmte Aufgaben oder Funktionen zu delegieren. Das klassische Beispiel in C ist der Vergleichsdelegat-Funktionszeiger, der mit den Standard-C-Bibliotheksfunktionen verwendet wird qsort()
und bsearch()
die Sortierreihenfolge zum Sortieren einer Liste von Elementen oder zum Durchführen einer binären Suche über eine sortierte Liste von Elementen bereitstellt. Der Vergleichsfunktionsdelegierte gibt den Kollatierungsalgorithmus an, der bei der Sortierung oder der binären Suche verwendet wird.
Eine andere Verwendung ähnelt der Anwendung eines Algorithmus auf einen C ++ Standard Template Library-Container.
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);
Ein anderes Beispiel ist der GUI-Quellcode, in dem ein Handler für ein bestimmtes Ereignis registriert wird, indem ein Funktionszeiger bereitgestellt wird, der tatsächlich aufgerufen wird, wenn das Ereignis eintritt. Das Microsoft MFC-Framework mit seinen Nachrichtenzuordnungen verwendet etwas Ähnliches, um Windows-Nachrichten zu verarbeiten, die an ein Fenster oder einen Thread gesendet werden.
Asynchrone Funktionen, die einen Rückruf erfordern, ähneln einem Ereignishandler. Der Benutzer der asynchronen Funktion ruft die asynchrone Funktion auf, um eine Aktion zu starten, und stellt einen Funktionszeiger bereit, den die asynchrone Funktion nach Abschluss der Aktion aufruft. In diesem Fall ist das Ereignis die asynchrone Funktion, die ihre Aufgabe erfüllt.