Ich hatte in letzter Zeit einige Erfahrungen mit Funktionszeigern in C.
Ausgehend von der Tradition, Ihre eigenen Fragen zu beantworten, habe ich beschlossen, eine kleine Zusammenfassung der Grundlagen für diejenigen zu erstellen, die sich schnell mit dem Thema befassen möchten.
c
function-pointers
Yuval Adam
quelle
quelle
Antworten:
Funktionszeiger in C.
Beginnen wir mit einer Grundfunktion, auf die wir verweisen werden :
Als erstes definieren wir einen Zeiger auf eine Funktion, die 2
int
s empfängt und Folgendes zurückgibtint
:Jetzt können wir sicher auf unsere Funktion verweisen:
Nachdem wir nun einen Zeiger auf die Funktion haben, verwenden wir ihn:
Das Übergeben des Zeigers an eine andere Funktion ist im Grunde dasselbe:
Wir können Funktionszeiger auch in Rückgabewerten verwenden (versuchen Sie mitzuhalten, es wird chaotisch):
Aber es ist viel schöner, ein
typedef
:quelle
pshufb
, aber langsam ist, sodass die frühere Implementierung immer noch schneller ist. x264 / x265 nutzen dies ausgiebig und sind Open Source.Funktionszeiger in C können verwendet werden, um eine objektorientierte Programmierung in C durchzuführen.
Die folgenden Zeilen sind beispielsweise in C geschrieben:
Ja, das
->
und das Fehlen einesnew
Operators ist ein totes Geschenk, aber es scheint sicher zu implizieren, dass wir den Text einerString
Klasse so einstellen"hello"
.Durch die Verwendung von Funktionszeigern ist es möglich, Methoden in C zu emulieren .
Wie wird das erreicht?
Die
String
Klasse besteht eigentlich aus einerstruct
Reihe von Funktionszeigern, mit denen Methoden simuliert werden können. Das Folgende ist eine Teildeklaration derString
Klasse:Wie zu sehen ist, sind die Methoden der
String
Klasse tatsächlich Funktionszeiger auf die deklarierte Funktion. Bei der Vorbereitung der Instanz vonString
wird dienewString
Funktion aufgerufen, um die Funktionszeiger auf ihre jeweiligen Funktionen einzurichten:Die
getString
Funktion, die durch Aufrufen derget
Methode aufgerufen wird , ist beispielsweise wie folgt definiert:Eine Sache, die bemerkt werden kann, ist, dass es kein Konzept einer Instanz eines Objekts gibt und Methoden hat, die tatsächlich Teil eines Objekts sind, so dass bei jedem Aufruf ein "Selbstobjekt" übergeben werden muss. (Und das
internal
ist nur ein verstecktesstruct
Element, das zuvor in der Codeliste weggelassen wurde - es ist eine Möglichkeit, Informationen auszublenden, aber das ist für Funktionszeiger nicht relevant.)Anstatt dies tun zu können
s1->set("hello");
, muss man das Objekt übergeben, um die Aktion auszuführens1->set(s1, "hello")
.Da diese kleine Erklärung einen Verweis auf sich selbst aus dem Weg räumen muss, fahren wir mit dem nächsten Teil fort, der die Vererbung in C ist .
Nehmen wir an, wir möchten eine Unterklasse bilden
String
, sagen wir eineImmutableString
. Um die Zeichenfolge unveränderlich zu machen, kann auf dieset
Methode nicht zugegriffen werden, während der Zugriff aufget
und beibehalten wirdlength
, und der "Konstruktor" wird gezwungen, Folgendes zu akzeptierenchar*
:Grundsätzlich sind die verfügbaren Methoden für alle Unterklassen wieder Funktionszeiger. Dieses Mal ist die Deklaration für die
set
Methode nicht vorhanden, daher kann sie nicht in a aufgerufen werdenImmutableString
.Für die Implementierung von
ImmutableString
ist der einzige relevante Code die "Konstruktor" -Funktion, dienewImmutableString
:Beim Instanziieren der verweisen
ImmutableString
die Funktionszeiger auf die Methodenget
undlength
tatsächlich auf die MethodeString.get
undString.length
, indem sie diebase
Variable durchlaufen , die ein intern gespeichertesString
Objekt ist.Durch die Verwendung eines Funktionszeigers kann die Vererbung einer Methode von einer Oberklasse erreicht werden.
Wir können den Polymorphismus in C weiter fortsetzen .
Wenn wir zum Beispiel das Verhalten der
length
Methode ändern wollten, um aus irgendeinem Grund die ganze0
Zeit in derImmutableString
Klasse zurückzukehren, müssten wir nur Folgendes tun:length
Methode dienen soll.length
Methode.Das Hinzufügen einer überschreibenden
length
Methode inImmutableString
kann durch Hinzufügen eineslengthOverrideMethod
:Anschließend wird der Funktionszeiger für die
length
Methode im Konstruktor an Folgendes angeschlossenlengthOverrideMethod
:Anstatt ein identisches Verhalten für die
length
Methode in derImmutableString
Klasse wie dieString
Klasse zu haben,length
verweist die Methode jetzt auf das in derlengthOverrideMethod
Funktion definierte Verhalten .Ich muss einen Haftungsausschluss hinzufügen, dass ich noch lerne, wie man mit einem objektorientierten Programmierstil in C schreibt. Daher gibt es wahrscheinlich Punkte, die ich nicht gut erklärt habe oder die in Bezug auf die beste Implementierung von OOP möglicherweise falsch sind in C. Aber mein Ziel war es, eine von vielen Verwendungen von Funktionszeigern zu veranschaulichen.
Weitere Informationen zum Ausführen der objektorientierten Programmierung in C finden Sie in den folgenden Fragen:
quelle
ClassName_methodName
Funktionsnamenskonvention. Nur dann erhalten Sie die gleichen Laufzeit- und Speicherkosten wie in C ++ und Pascal.Die Anleitung zum Abfeuern: So missbrauchen Sie Funktionszeiger in GCC auf x86-Computern, indem Sie Ihren Code von Hand kompilieren:
Diese Zeichenfolgenliterale sind Bytes mit 32-Bit-x86-Maschinencode.
0xC3
ist eine x86-ret
Anweisung .Normalerweise würden Sie diese nicht von Hand schreiben
nasm
, sondern in Assemblersprache schreiben und dann einen Assembler verwenden , um sie zu einer flachen Binärdatei zusammenzusetzen, die Sie in ein C-String-Literal hexdumpen.Gibt den aktuellen Wert im EAX-Register zurück
Schreiben Sie eine Swap-Funktion
Schreiben Sie einen For-Loop-Zähler auf 1000 und rufen Sie jedes Mal eine Funktion auf
Sie können sogar eine rekursive Funktion schreiben, die bis 100 zählt
Beachten Sie, dass Compiler Zeichenfolgenliterale in den
.rodata
Abschnitt (oder.rdata
unter Windows) einfügen, der als Teil des Textsegments verknüpft ist (zusammen mit Code für Funktionen).Das Textsegment hat Read + Exec Erlaubnis, Stringliterale zu Funktionszeiger funktioniert , ohne dass so Casting
mprotect()
oderVirtualProtect()
Systemaufrufe wie Sie für dynamisch zugewiesenen Speicher benötigen würde. (Odergcc -z execstack
verknüpft das Programm mit Stack + Datensegment + ausführbarer Heap-Datei als schnellen Hack.)Um diese zu zerlegen, können Sie dies kompilieren, um den Bytes eine Bezeichnung zu geben, und einen Disassembler verwenden.
Beim Kompilieren
gcc -c -m32 foo.c
und Zerlegen mitobjdump -D -rwC -Mintel
können wir die Assembly abrufen und feststellen, dass dieser Code den ABI verletzt, indem er EBX (ein aufruferhaltenes Register) blockiert und im Allgemeinen ineffizient ist.Dieser Maschinencode funktioniert (wahrscheinlich) in 32-Bit-Code unter Windows, Linux, OS X usw.: Die Standardaufrufkonventionen für alle diese Betriebssysteme übergeben Argumente auf dem Stapel, anstatt effizienter in Registern. EBX bleibt jedoch in allen normalen Anrufkonventionen aufruffähig. Wenn Sie es also als Arbeitsregister verwenden, ohne es zu speichern oder wiederherzustellen, kann der Anrufer leicht abstürzen.
quelle
Eine meiner Lieblingsanwendungen für Funktionszeiger sind billige und einfache Iteratoren -
quelle
int (*cb)(void *arg, ...)
. Mit dem Rückgabewert des Iterators kann ich auch vorzeitig anhalten (wenn nicht Null).Funktionszeiger können einfach deklariert werden, sobald Sie die grundlegenden Deklaratoren haben:
ID
: ID ist ein*D
: D Zeiger aufD(<parameters>)
: D Funktion<
Aufnahmeparameter>
RückkehrWährend D ein anderer Deklarator ist, der nach denselben Regeln erstellt wurde. Am Ende endet es irgendwo mit
ID
(siehe unten für ein Beispiel), dem Namen der deklarierten Entität. Versuchen wir, eine Funktion zu erstellen, die einen Zeiger auf eine Funktion nimmt, die nichts nimmt und int zurückgibt, und einen Zeiger auf eine Funktion zurückgibt, die ein Zeichen nimmt und int zurückgibt. Bei Typ-Defs ist das soWie Sie sehen, ist es ziemlich einfach, es mit typedefs aufzubauen. Ohne typedefs ist es auch mit den oben genannten Deklaratorregeln, die konsistent angewendet werden, nicht schwer. Wie Sie sehen, habe ich den Teil verpasst, auf den der Zeiger zeigt, und das, was die Funktion zurückgibt. Das steht ganz links in der Deklaration und ist nicht von Interesse: Es wird am Ende hinzugefügt, wenn man den Deklarator bereits aufgebaut hat. Lass uns das tun. Bauen Sie es konsequent auf, zuerst wortreich - zeigen Sie die Struktur mit
[
und]
:Wie Sie sehen, kann man einen Typ vollständig beschreiben, indem man Deklaratoren nacheinander anfügt. Der Bau kann auf zwei Arten erfolgen. Eine ist von unten nach oben, angefangen mit der richtigen Sache (Blätter) bis hin zur Kennung. Der andere Weg ist von oben nach unten, beginnend bei der Kennung, bis hinunter zu den Blättern. Ich werde beide Wege zeigen.
Prost
Der Bau beginnt mit dem Ding auf der rechten Seite: Das Ding, das zurückgegeben wird, ist die Funktion, die char übernimmt. Um die Deklaratoren getrennt zu halten, werde ich sie nummerieren:
Der char-Parameter wurde direkt eingefügt, da er trivial ist. Hinzufügen eines Zeigers zum Deklarator durch Ersetzen
D1
durch*D2
. Beachten Sie, dass wir Klammern umschließen müssen*D2
. Dies kann durch Nachschlagen der Priorität des*-operator
und des Funktionsaufrufoperators festgestellt werden()
. Ohne unsere Klammern würde der Compiler es als lesen*(D2(char p))
. Aber das wäre natürlich kein einfacher Ersatz für D1*D2
mehr. Um Deklaratoren herum sind immer Klammern zulässig. Sie machen also nichts falsch, wenn Sie tatsächlich zu viel davon hinzufügen.Rückgabetyp ist vollständig! Lassen Sie uns nun
D2
durch die Funktionsdeklaratorfunktion ersetzen, die die<parameters>
Rückgabe übernimmt , undD3(<parameters>)
die wir gerade sind.Beachten Sie, dass keine Klammern erforderlich sind, da wir diesmal ein Funktionsdeklarator und kein Zeigerdeklarator sein möchten
D3
. Großartig, nur noch die Parameter dafür. Der Parameter wird genauso ausgeführt wie der Rückgabetyp, nur durchchar
ersetzt durchvoid
. Also werde ich es kopieren:Ich habe ersetzt
D2
durchID1
, da wir mit diesem Parameter fertig sind (es ist bereits ein Zeiger auf eine Funktion - kein weiterer Deklarator erforderlich).ID1
wird der Name des Parameters sein. Nun, ich habe oben am Ende gesagt, dass man den Typ hinzufügt, den alle diese Deklaratoren ändern - den, der ganz links von jeder Deklaration erscheint. Für Funktionen wird dies zum Rückgabetyp. Für Zeiger, auf die der Typ zeigt usw. Es ist interessant, wenn der Typ aufgeschrieben wird, er wird in der entgegengesetzten Reihenfolge ganz rechts angezeigt :) Wenn Sie ihn ersetzen, erhalten Sie die vollständige Deklaration. Beide Maleint
natürlich.Ich habe
ID0
in diesem Beispiel die Kennung der Funktion aufgerufen .Von oben nach unten
Dies beginnt bei der Kennung ganz links in der Beschreibung des Typs und umschließt diesen Deklarator, während wir uns durch die rechte Seite bewegen. Beginnen Sie mit Funktion
<
Aufnahmeparameter>
RückkehrDas nächste in der Beschreibung (nach "Zurückkehren") war der Zeiger auf . Lassen Sie es uns aufnehmen:
Dann war das nächste, was funktionierte, indem
<
Parameter>
zurückgegeben wurden . Der Parameter ist ein einfaches Zeichen, daher setzen wir ihn sofort wieder ein, da er wirklich trivial ist.Beachten Sie die Klammern, die wir hinzugefügt haben, da wir wieder möchten, dass zuerst die
*
Bindungen und dann die(char)
. Sonst wäre es lesen Funktion unter<
Parameter>
Rückkehr Funktion ... . Nein, Funktionen, die Funktionen zurückgeben, sind nicht einmal erlaubt.Jetzt müssen wir nur noch
<
Parameter eingeben>
. Ich werde eine kurze Version der Ableitung zeigen, da ich denke, dass Sie bereits jetzt die Idee haben, wie es geht.Stellen Sie es einfach
int
vor die Deklaratoren, wie wir es mit Bottom-up getan haben, und wir sind fertigDas Schöne
Ist Bottom-Up oder Top-Down besser? Ich bin es gewohnt, von unten nach oben zu gehen, aber manche Leute fühlen sich mit Top-Down wohler. Es ist Geschmackssache, denke ich. Wenn Sie übrigens alle Operatoren in dieser Deklaration anwenden, erhalten Sie am Ende ein int:
Das ist eine nette Eigenschaft von Deklarationen in C: Die Deklaration besagt, dass, wenn diese Operatoren in einem Ausdruck unter Verwendung des Bezeichners verwendet werden, der Typ ganz links ausgegeben wird. Das ist auch für Arrays so.
Hoffe dir hat dieses kleine Tutorial gefallen! Jetzt können wir darauf verweisen, wenn sich die Leute über die seltsame Deklarationssyntax von Funktionen wundern. Ich habe versucht, so wenig C-Interna wie möglich zu verwenden. Fühlen Sie sich frei, Dinge darin zu bearbeiten / zu reparieren.
quelle
Eine weitere gute Verwendung für Funktionszeiger:
Schmerzloses Wechseln zwischen Versionen
Sie sind sehr praktisch, wenn Sie unterschiedliche Funktionen zu unterschiedlichen Zeiten oder in unterschiedlichen Entwicklungsphasen wünschen. Zum Beispiel entwickle ich eine Anwendung auf einem Host-Computer mit einer Konsole, aber die endgültige Version der Software wird auf einem Avnet ZedBoard (das über Ports für Displays und Konsolen verfügt, aber für die nicht benötigt / gewünscht wird) bereitgestellt endgültige Veröffentlichung). Während der Entwicklung werde ich also
printf
Status- und Fehlermeldungen anzeigen, aber wenn ich fertig bin, möchte ich nicht, dass etwas gedruckt wird. Folgendes habe ich getan:version.h
In
version.c
Ich werde die 2 Funktionsprototypen definieren, die in vorhanden sindversion.h
version.c
Beachten Sie, wie die Funktionszeiger wird prototypisiert in
version.h
alsvoid (* zprintf)(const char *, ...);
Wenn in der Anwendung darauf verwiesen wird, wird die Ausführung überall dort ausgeführt, wo sie zeigt, was noch definiert werden muss.
In
version.c
, Bekanntmachung in derboard_init()
Funktion , wozprintf
eine eindeutige Funktion zugeordnet (dessen Funktion Unterschrift Ursachen) in Abhängigkeit von der Version , die in definiert ist ,version.h
zprintf = &printf;
zprintf ruft printf zu Debugging-Zwecken aufoder
zprintf = &noprint;
zprintf kehrt nur zurück und führt keinen unnötigen Code ausDas Ausführen des Codes sieht folgendermaßen aus:
mainProg.c
Der obige Code wird
printf
im Debug-Modus verwendet oder im Release-Modus nicht ausgeführt. Dies ist viel einfacher, als das gesamte Projekt durchzugehen und Code zu kommentieren oder zu löschen. Alles was ich tun muss, ist die Version zu ändernversion.h
und der Code erledigt den Rest!quelle
Der Funktionszeiger wird normalerweise durch definiert
typedef
und als Parameter- und Rückgabewert verwendet.Die obigen Antworten haben bereits viel erklärt, ich gebe nur ein vollständiges Beispiel:
quelle
Eine der Hauptanwendungen für Funktionszeiger in C ist das Aufrufen einer zur Laufzeit ausgewählten Funktion. Zum Beispiel hat die C - Laufzeitbibliothek zwei Routinen,
qsort
undbsearch
, die einen Zeiger auf eine Funktion übernehmen , die zwei Elemente genannt wird zu vergleichen , sortiert werden; Auf diese Weise können Sie alles nach beliebigen Kriterien sortieren bzw. suchen.Ein sehr einfaches Beispiel: Wenn es eine aufgerufene Funktion gibt, für
print(int x, int y)
die möglicherweise eine Funktion aufgerufen werden muss (entwederadd()
odersub()
vom selben Typ), fügen wir derprint()
Funktion ein Funktionszeigerargument hinzu, wie unten gezeigt ::Die Ausgabe ist:
quelle
Die Funktion "Von Grund auf neu starten" verfügt über eine Speicheradresse, von der aus sie ausgeführt werden. In der Assemblersprache werden sie als aufgerufen (rufen Sie die Speicheradresse der Funktion auf). Kehren Sie nun zu C zurück. Wenn die Funktion eine Speicheradresse hat, können sie durch Zeiger in C.So nach den Regeln von C bearbeitet werden
1. Zuerst müssen Sie einen Zeiger auf die Funktion deklarieren. 2. Übergeben Sie die Adresse der gewünschten Funktion
**** Hinweis-> Die Funktionen sollten vom gleichen Typ sein ****
Dieses einfache Programm wird alles veranschaulichen.
Danach können wir sehen, wie die Maschine sie versteht. Die Maschinenanweisung des obigen Programms in der 32-Bit-Architektur ist begrenzt.
Der rote Markierungsbereich zeigt an, wie die Adresse ausgetauscht und in eax gespeichert wird. Dann gibt es eine Anrufanweisung für eax. eax enthält die gewünschte Adresse der Funktion.
quelle
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:
Die ersten beiden Erklärungen sind insofern etwas ähnlich:
func
ist eine Funktion, die einint
und ein nimmt und einchar *
zurückgibtint
pFunc
ist ein Funktionszeiger, dem die Adresse einer Funktion zugewiesen ist, die einint
und ein nimmt und einchar *
zurückgibtint
Von oben könnten wir also eine Quellzeile haben, in der die Adresse der Funktion
func()
der FunktionszeigervariablenpFunc
wie in zugewiesen wirdpFunc = 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.
Verschiedene Verwendungsbeispiele
Einige Beispiele für die Verwendung eines Funktionszeigers:
Sie können Parameterlisten variabler Länge bei der Definition eines Funktionszeigers verwenden.
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.
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.
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.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:C-Stil
namespace
Verwenden von Globalstruct
mit FunktionszeigernMit 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 dienamespace
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.
Dann in der C-Quelldatei:
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.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 vonstruct
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.und dies könnte wie folgt verwendet werden:
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()
undbsearch()
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.
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.
quelle
Da Funktionszeiger häufig Rückrufe eingegeben werden, möchten Sie vielleicht einen Blick auf haben Typ sicher Rückrufe . Gleiches gilt für Einstiegspunkte usw. von Funktionen, die keine Rückrufe sind.
C ist ziemlich launisch und verzeihend zugleich :)
quelle