Ich habe Probleme, die Nützlichkeit von Funktionszeigern zu erkennen. Ich denke, es kann in einigen Fällen nützlich sein (sie existieren schließlich), aber ich kann mir keinen Fall vorstellen, in dem es besser oder unvermeidlich ist, einen Funktionszeiger zu verwenden.
Können Sie ein Beispiel für die gute Verwendung von Funktionszeigern (in C oder C ++) geben?
Antworten:
Die meisten Beispiele laufen auf Rückrufe : Sie rufen eine Funktion
f()
die Adresse einer anderen Funktion übergebeng()
, undf()
Anrufeg()
für einige bestimmte Aufgabe. Wenn Sie stattdessenf()
die Adresse von übergebenh()
,f()
wirdh()
stattdessen zurückgerufen.Grundsätzlich ist dies eine Möglichkeit, eine Funktion zu parametrisieren : Ein Teil ihres Verhaltens ist nicht fest codiert
f()
, sondern in die Rückruffunktion. Anrufer könnenf()
sich anders verhalten, indem sie verschiedene Rückruffunktionen übergeben. Ein Klassiker stammtqsort()
aus der C-Standardbibliothek, die sein Sortierkriterium als Zeiger auf eine Vergleichsfunktion verwendet.In C ++ werden dazu häufig Funktionsobjekte (auch Funktoren genannt) verwendet. Dies sind Objekte, die den Funktionsaufrufoperator überladen, sodass Sie sie aufrufen können, als wären sie eine Funktion. Beispiel:
Die Idee dahinter ist, dass ein Funktionsobjekt im Gegensatz zu einem Funktionszeiger nicht nur einen Algorithmus, sondern auch Daten enthalten kann:
Ein weiterer Vorteil ist, dass es manchmal einfacher ist, Aufrufe an Funktionsobjekte zu inline als Aufrufe über Funktionszeiger. Dies ist ein Grund, warum das Sortieren in C ++ manchmal schneller ist als das Sortieren in C.
quelle
Nun, ich benutze sie im Allgemeinen (professionell) in Sprungtabellen (siehe auch diese StackOverflow-Frage ).
Sprungtabellen werden häufig (aber nicht ausschließlich) in Finite-State-Maschinen verwendet , um sie datengesteuert zu machen. Anstelle von verschachteltem Schalter / Fall
Sie können ein 2D-Array von Funktionszeigern erstellen und einfach aufrufen
handleEvent[state][event]
quelle
Beispiele:
quelle
std::sort
'scomp
Parameter als Strategie beschreibenDas "klassische" Beispiel für die Nützlichkeit von Funktionszeigern ist die C-Bibliotheksfunktion
qsort()
, die eine schnelle Sortierung implementiert. Um für alle Datenstrukturen, die der Benutzer möglicherweise erstellt, universell zu sein, sind einige leere Zeiger auf sortierbare Daten und ein Zeiger auf eine Funktion erforderlich, die zwei Elemente dieser Datenstrukturen vergleichen kann. Dies ermöglicht es uns, die Funktion unserer Wahl für den Job zu erstellen und sogar die Vergleichsfunktion zur Laufzeit auszuwählen, z. B. zum Sortieren aufsteigend oder absteigend.quelle
Stimmen Sie allen oben genannten Punkten zu, plus ... Wenn Sie eine DLL zur Laufzeit dynamisch laden, benötigen Sie Funktionszeiger, um die Funktionen aufzurufen.
quelle
Ich werde hier gegen den Strom gehen.
In C sind Funktionszeiger die einzige Möglichkeit, die Anpassung zu implementieren, da kein OO vorhanden ist.
In C ++ können Sie entweder Funktionszeiger oder Funktoren (Funktionsobjekte) für dasselbe Ergebnis verwenden.
Die Funktoren haben aufgrund ihrer Objektnatur eine Reihe von Vorteilen gegenüber Rohfunktionszeigern, insbesondere:
operator()
lambda
undbind
)Ich persönlich bevorzuge Funktoren gegenüber Funktionszeigern (trotz des Boilerplate-Codes), hauptsächlich weil die Syntax für Funktionszeiger leicht haarig werden kann (aus dem Funktionszeiger-Tutorial ):
Das einzige Mal, dass ich Funktionszeiger gesehen habe, bei denen Funktoren nicht verwendet werden konnten, war in Boost.Spirit. Sie haben die Syntax völlig missbraucht, um eine beliebige Anzahl von Parametern als einzelnen Vorlagenparameter zu übergeben.
Da jedoch verschiedene Vorlagen und Lambdas vor der Tür stehen, bin ich mir nicht sicher, ob wir Funktionszeiger in reinem C ++ - Code schon lange verwenden werden.
quelle
bind
oder Funktionszeiger verwenden (es sei denn, der Compiler kann sie wegoptimieren)function
. Es ist so, als würden wir in C ++ keine Zeiger verwenden, weil wir intelligente Zeiger verwenden. Wie auch immer, ich picke nicht.In C ist die klassische Verwendung die qsort-Funktion , wobei der vierte Parameter ein Zeiger auf eine Funktion ist, mit der die Reihenfolge innerhalb der Sortierung ausgeführt wird. In C ++ würde man dazu neigen, Funktoren (Objekte, die wie Funktionen aussehen) für solche Dinge zu verwenden.
quelle
Ich habe kürzlich Funktionszeiger verwendet, um eine Abstraktionsschicht zu erstellen.
Ich habe ein Programm in reinem C geschrieben, das auf eingebetteten Systemen läuft. Es unterstützt mehrere Hardwarevarianten. Abhängig von der Hardware, auf der ich ausgeführt werde, müssen verschiedene Versionen einiger Funktionen aufgerufen werden.
Bei der Initialisierung ermittelt das Programm, auf welcher Hardware es ausgeführt wird, und füllt die Funktionszeiger. Alle übergeordneten Routinen im Programm rufen nur die Funktionen auf, auf die durch Zeiger verwiesen wird. Ich kann Unterstützung für neue Hardwarevarianten hinzufügen, ohne die übergeordneten Routinen zu berühren.
Früher habe ich switch / case-Anweisungen verwendet, um die richtigen Funktionsversionen auszuwählen, aber dies wurde unpraktisch, als das Programm immer mehr Hardwarevarianten unterstützte. Ich musste überall Fallaussagen hinzufügen.
Ich habe auch Zwischenfunktionsebenen ausprobiert, um herauszufinden, welche Funktion verwendet werden soll, aber sie haben nicht viel geholfen. Ich musste immer noch Fallanweisungen an mehreren Stellen aktualisieren, wenn wir eine neue Variante hinzufügten. Mit den Funktionszeigern muss ich nur die Initialisierungsfunktion ändern.
quelle
Wie Rich oben sagte, ist es in Windows sehr üblich, dass Funktionszeiger auf eine Adresse verweisen, in der Funktionen gespeichert sind.
Wenn Sie
C language
auf einer Windows-Plattform programmieren, laden Sie im Grunde genommen eine DLL-Datei in den Primärspeicher (usingLoadLibrary
). Um die in DLL gespeicherten Funktionen zu verwenden, müssen Sie Funktionszeiger erstellen und auf diese Adresse verweisen (usingGetProcAddress
).Verweise:
LoadLibrary
GetProcAddress
quelle
Funktionszeiger können in C verwendet werden, um eine Schnittstelle zum Programmieren zu erstellen. Abhängig von der spezifischen Funktionalität, die zur Laufzeit benötigt wird, kann dem Funktionszeiger eine andere Implementierung zugewiesen werden.
quelle
Meine Hauptanwendung waren CALLBACKS: Wenn Sie Informationen zu einer Funktion speichern müssen, um sie später aufzurufen .
Angenommen, Sie schreiben Bomberman. 5 Sekunden nachdem die Person die Bombe fallen gelassen hat, sollte sie explodieren (
explode()
Funktion aufrufen ).Jetzt gibt es zwei Möglichkeiten. Eine Möglichkeit besteht darin, alle Bomben auf dem Bildschirm zu "untersuchen", um festzustellen, ob sie in der Hauptschleife explodieren können.
Eine andere Möglichkeit besteht darin, einen Rückruf an Ihr Uhrensystem anzuschließen. Wenn eine Bombe gepflanzt wird, fügen Sie einen Rückruf hinzu, damit sie zur richtigen Zeit bomb.explode () aufruft .
Hier
callback.function()
kann jede Funktion sein , da es sich um einen Funktionszeiger handelt.quelle
Verwendung des Funktionszeigers
Um Anruffunktion dynamisch basierend auf Benutzereingaben. In diesem Fall erstellen Sie eine Zuordnung von Zeichenfolge und Funktionszeiger.
Auf diese Weise haben wir den Funktionszeiger in unserem tatsächlichen Buchungskreis verwendet. Sie können 'n' Anzahl von Funktionen schreiben und sie mit dieser Methode aufrufen.
AUSGABE:
quelle
Eine andere Perspektive, zusätzlich zu anderen guten Antworten hier:
In C verwenden Sie nur Funktionszeiger, keine (direkten) Funktionen.
Ich meine, Sie schreiben Funktionen, aber Sie können Funktionen nicht manipulieren . Es gibt keine Laufzeitdarstellung einer Funktion als solche, die Sie verwenden können. Sie können nicht einmal "eine Funktion" aufrufen. Wenn Sie schreiben:
Was Sie tatsächlich sagen, ist "Rufen Sie den
my_function
Zeiger mit dem angegebenen Argument auf". Sie rufen über einen Funktionszeiger auf. Dieser Zerfall zum Funktionszeiger bedeutet, dass die folgenden Befehle dem vorherigen Funktionsaufruf entsprechen:und so weiter (danke @LuuVinhPhuc).
Sie verwenden also bereits Funktionszeiger als Werte . Natürlich möchten Sie Variablen für diese Werte haben - und hier kommen alle Verwendungszwecke anderer Metionen ins Spiel: Polymorphismus / Anpassung (wie in qsort), Rückrufe, Sprungtabellen usw.
In C ++ sind die Dinge etwas komplizierter, da wir Lambdas und Objekte mit
operator()
und sogar einestd::function
Klasse haben, aber das Prinzip ist meistens immer noch dasselbe.quelle
(&my_function)(my_arg)
,(*my_function)(my_arg)
,(**my_function)(my_arg)
,(&**my_function)(my_arg)
,(***my_function)(my_arg)
... weil Funktionen abklingen zu FunktionszeigerFür OO-Sprachen, um polymorphe Aufrufe hinter den Kulissen durchzuführen (dies gilt auch für C bis zu einem gewissen Punkt, denke ich).
Darüber hinaus sind sie sehr nützlich, um einer anderen Funktion (foo) zur Laufzeit ein anderes Verhalten zu verleihen. Das macht die Funktion zu einer Funktion höherer Ordnung. Abgesehen von seiner Flexibilität macht dies den foo-Code besser lesbar, da Sie damit die zusätzliche Logik des "Wenn-Sonst" herausholen können.
Es ermöglicht viele andere nützliche Dinge in Python wie Generatoren, Verschlüsse usw.
quelle
Ich verwende häufig Funktionszeiger, um Mikroprozessoren mit 1-Byte-Opcodes zu emulieren. Ein Array von 256 Funktionszeigern ist der natürliche Weg, dies zu implementieren.
quelle
Eine Verwendung des Funktionszeigers könnte darin bestehen, dass wir den Code, in dem die Funktion aufgerufen wird, möglicherweise nicht ändern möchten (was bedeutet, dass der Aufruf möglicherweise bedingt ist und wir unter verschiedenen Bedingungen unterschiedliche Verarbeitungsarten ausführen müssen). Hier sind die Funktionszeiger sehr praktisch, da wir den Code an der Stelle, an der die Funktion aufgerufen wird, nicht ändern müssen. Wir rufen die Funktion einfach mit dem Funktionszeiger mit entsprechenden Argumenten auf. Der Funktionszeiger kann so eingestellt werden, dass er bedingt auf verschiedene Funktionen zeigt. (Dies kann irgendwo während der Initialisierungsphase erfolgen). Darüber hinaus ist das obige Modell sehr hilfreich, wenn wir nicht in der Lage sind, den Code dort zu ändern, wo er aufgerufen wird (angenommen, es handelt sich um eine Bibliotheks-API, die wir nicht ändern können). Die API verwendet einen Funktionszeiger zum Aufrufen der entsprechenden benutzerdefinierten Funktion.
quelle
Ich werde versuchen, hier eine etwas umfassende Liste zu geben:
Rückrufe : Passen Sie einige (Bibliotheks-) Funktionen mit vom Benutzer bereitgestelltem Code an. Das beste Beispiel ist
qsort()
, aber auch nützlich, um Ereignisse zu behandeln (wie eine Schaltfläche, die einen Rückruf aufruft, wenn darauf geklickt wird) oder um einen Thread zu starten (pthread_create()
).Polymorphismus : Die vtable in einer C ++ - Klasse ist nichts anderes als eine Tabelle mit Funktionszeigern. Ein C-Programm kann auch eine vtable für einige seiner Objekte bereitstellen:
Der Konstruktor von
Derived
würde dann seinevtable
Mitgliedsvariable mit den Implementierungen vondestruct
und der Klasse der abgeleiteten Klasse auf ein globales Objekt setzenfrobnicate
, und Code, der zum Zerstören eines Destruktors benötigt wird,struct Base*
würde einfachbase->vtable->destruct(base)
die richtige Version des Destruktors aufrufen, unabhängig davon, auf welche abgeleitete Klassebase
tatsächlich verweist .Ohne Funktionszeiger müsste der Polymorphismus mit einer Armee von Schalterkonstrukten wie codiert werden
Dies wird ziemlich schnell ziemlich unhandlich.
Dynamisch geladener Code : Wenn ein Programm ein Modul in den Speicher lädt und versucht, seinen Code aufzurufen, muss es einen Funktionszeiger durchlaufen.
Alle Verwendungen von Funktionszeigern, die ich gesehen habe, fallen direkt in eine dieser drei breiten Klassen.
quelle