Ein Funktionszeiger ist eine Variable, die die Adresse einer Funktion enthält. Da es sich um eine Zeigervariable handelt, die allerdings einige eingeschränkte Eigenschaften aufweist, können Sie sie so gut wie jede andere Zeigervariable in Datenstrukturen verwenden.
Die einzige Ausnahme, die mir einfällt, ist die Behandlung des Funktionszeigers als Verweis auf etwas anderes als einen einzelnen Wert. Zeigerarithmetik durch Inkrementieren oder Dekrementieren eines Funktionszeigers oder Addieren/Subtrahieren eines Offsets zu einem Funktionszeiger ist nicht wirklich von Nutzen, da ein Funktionszeiger nur auf eine einzige Sache zeigt, nämlich den Einstiegspunkt einer Funktion.
Die Größe einer Funktionszeigervariable, d.h. die Anzahl der von der Variablen belegten Bytes, kann je nach zugrunde liegender Architektur variieren, z.B. x32 oder x64 oder was auch immer.
Die Deklaration für eine Funktionszeigervariable muss dieselbe Art von Informationen enthalten wie eine Funktionsdeklaration, damit der C-Compiler die üblichen Prüfungen durchführen kann. Wenn Sie in der Deklaration/Definition des Funktionszeigers keine Parameterliste angeben, ist der C-Compiler nicht in der Lage, die Verwendung von Parametern zu überprüfen. Es gibt Fälle, in denen diese fehlende Überprüfung nützlich sein kann, aber denken Sie 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 ähnlich:
func
ist eine Funktion, die eine int
und eine char *
und gibt eine int
pFunc
ist ein Funktionszeiger, dem die Adresse einer Funktion zugeordnet ist, die eine int
und eine char *
und gibt eine int
Wir könnten also eine Quellzeile haben, in der die Adresse der Funktion func()
wird der Funktionszeigervariablen zugewiesen pFunc
wie in pFunc = func;
.
Beachten Sie die Syntax, die bei der Deklaration/Definition eines Funktionszeigers verwendet wird, bei der Klammern verwendet werden, um die natürlichen Vorrangregeln für Operatoren zu umgehen.
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 mit variabler Länge in 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, aber es eliminiert die Möglichkeit für den C-Compiler, die angegebene Argumentliste zu überprüfen.
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);
C-Stil Besetzungen
Sie können Casts im Stil von C mit Funktionszeigern verwenden. Seien Sie sich jedoch bewusst, dass ein C-Compiler bei Überprüfungen nachlässig sein oder eher Warnungen als Fehler ausgeben kann.
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 auf Gleichheit vergleichen
Sie können überprüfen, ob ein Funktionszeiger gleich einer bestimmten Funktionsadresse ist, indem Sie eine if
Anweisung, obwohl ich nicht sicher bin, wie nützlich das sein würde. 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 wollen, von denen jedes Element Unterschiede in der Argumentliste hat, dann können Sie einen Funktionszeiger mit unspezifizierter Argumentliste definieren (nicht void
was bedeutet, dass es keine Argumente gibt, sondern nur nicht spezifizierte), etwa wie folgt, obwohl Sie möglicherweise Warnungen vom C-Compiler erhalten. Dies funktioniert auch für einen Funktionszeiger-Parameter für 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
Verwendung von Global struct
mit Funktionszeigern
Sie können die static
Schlüsselwort, um eine Funktion zu spezifizieren, deren Name ein Dateibereich ist, und diese dann einer globalen Variablen zuzuweisen, um so etwas Ähnliches wie die namespace
Funktionalität von C++.
Definieren Sie in einer Header-Datei eine Struktur, die unser Namensraum sein wird, zusammen mit einer globalen Variablen, die ihn 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 würde dann durch Angabe des vollständigen Namens der globalen Strukturvariablen und des Mitgliedsnamens für den Zugriff auf die Funktion verwendet werden. Die const
Modifikator 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 tun wie die C-Stil namespace
Ansatz, bei dem eine bestimmte Bibliotheksschnittstelle von einer Fabrikmethode in einer Bibliotheksschnittstelle angefordert wird, die die Erstellung einer struct
mit Funktionszeigern.. Diese Bibliotheksschnittstelle lädt die angeforderte DLL-Version, erstellt eine Struktur mit den erforderlichen Funktionszeigern und gibt die Struktur dann an den anfordernden Aufrufer zur Verwendung 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 in 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. Auf diese Weise kann eine abstrakte Hardwareschicht bereitgestellt werden, die von Software verwendet wird, die eine Fabrikfunktion aufruft, um die spezifische Hardware-Funktionsschnittstelle zu erhalten, und dann die bereitgestellten Funktionszeiger verwendet, um Aktionen für die zugrunde liegende Hardware durchzuführen, ohne Implementierungsdetails über das spezifische Ziel kennen zu müssen.
Funktionszeiger zum Erstellen von Delegaten, Bearbeitern und Rückrufen
Sie können Funktionszeiger verwenden, um eine Aufgabe oder Funktionalität zu delegieren. Das klassische Beispiel in C ist der Funktionszeiger für Vergleichsdelegierungen, der mit den Funktionen der Standard-C-Bibliothek verwendet wird qsort()
y bsearch()
um die Sortierreihenfolge für die Sortierung einer Liste von Elementen oder die Durchführung einer binären Suche über eine sortierte Liste von Elementen anzugeben. Der Delegat der Vergleichsfunktion gibt den Sortieralgorithmus an, der bei der Sortierung oder der binären Suche verwendet wird.
Eine andere Verwendung ähnelt der Anwendung eines Algorithmus auf einen Container der C++ Standard Template Library.
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 weiteres 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 Message-Maps verwendet etwas Ähnliches, um Windows-Nachrichten zu behandeln, die an ein Fenster oder einen Thread geliefert werden.
Asynchrone Funktionen, die einen Rückruf erfordern, ähneln einem Event-Handler. Der Benutzer der asynchronen Funktion ruft die asynchrone Funktion auf, um eine Aktion zu starten, und übergibt einen Funktionszeiger, den die asynchrone Funktion aufruft, sobald die Aktion abgeschlossen ist. In diesem Fall ist das Ereignis die Beendigung der Aufgabe der asynchronen Funktion.
43 Stimmen
Auch: Eine etwas ausführlichere Analyse von C-Zeigern finden Sie unter blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge . Auch, Programmierung von Grund auf zeigt, wie sie auf Maschinenebene funktionieren. Verstehen Das "Speichermodell" von C ist sehr nützlich, um zu verstehen, wie C-Zeiger funktionieren.
10 Stimmen
Tolle Infos. Nach dem Titel zu urteilen, hätte ich eigentlich eine Erklärung erwartet, wie "Funktionszeiger funktionieren", nicht wie sie codiert sind :)
2 Stimmen
Die folgende Antwort ist kürzer und sehr viel leichter zu verstehen: stackoverflow.com/a/142809/2188550