Was ist der Sinn von functionszeigern?

Ich habe Schwierigkeiten, den Nutzen von functionszeigern zu sehen. Ich denke, dass es in einigen Fällen nützlich sein kann (sie existieren schließlich), aber ich kann mir keinen Fall vorstellen, in dem es besser oder unvermeidlich ist, einen functionszeiger zu verwenden.

Können Sie ein Beispiel für eine gute Verwendung von functionszeigern (in C oder C ++) geben?

Solutions Collecting From Web of "Was ist der Sinn von functionszeigern?"

Die meisten Beispiele gehen auf Callbacks zurück : Sie rufen eine function f() die die Adresse einer anderen function g() f() übergibt, und f() ruft g() für eine bestimmte Aufgabe auf. Wenn Sie stattdessen f() die Adresse von h() , ruft f() h() stattdessen h() .

Im Grunde ist dies eine Möglichkeit, eine function zu parametrisieren : Ein Teil ihres Verhaltens ist nicht fest in f() kodiert, sondern in die Callback-function. Anrufer können f() sich anders verhalten, indem sie verschiedene Callback-functionen übergeben. Ein Klassiker ist qsort() aus der C-Standardbibliothek, die ihr Sortierkriterium als pointers auf eine Vergleichsfunktion verwendet.

In C ++ geschieht dies oft mit functionsobjekten (auch Funktoren genannt). Dies sind Objekte, die den functionsaufrufoperator überlasten, sodass Sie sie so aufrufen können, als wären sie eine function. Beispiel:

 class functor { public: void operator()(int i) {std::cout < < "the answer is: " << i << '\n';} }; functor f; f(42); 

Die Idee dahinter ist, dass ein functionsobjekt im Gegensatz zu einem functionszeiger nicht nur einen Algorithmus, sondern auch Daten enthalten kann:

 class functor { public: functor(const std::string& prompt) : prompt_(prompt) {} void operator()(int i) {std::cout < < prompt_ << i << '\n';} private: std::string prompt_; }; functor f("the answer is: "); f(42); 

Ein weiterer Vorteil ist, dass es manchmal einfacher ist, Aufrufe an functionsobjekte als Aufrufe über functionszeiger zu verknüpfen. Dies ist ein Grund, warum das Sortieren in C ++ manchmal schneller ist als das Sortieren in C.

Nun, ich benutze sie (professionell) in Sprungtabellen (siehe auch diese StackOverflow-Frage ).

Sprungtabellen werden üblicherweise (aber nicht ausschließlich) in endlichen Automaten verwendet , um sie datengesteuert zu machen. Anstelle von verschachtelten Switch / Case

  switch (state) case A: switch (event): case e1: .... case e2: .... case B: switch (event): case e3: .... case e1: .... 

Sie können ein 2D-Array oder functionszeiger machen und einfach handleEvent[state][event] aufrufen

Beispiele:

  1. Benutzerdefinierte Sortierung / Suche
  2. Verschiedene Muster (wie Strategie, Beobachter)
  3. callbacke

Das “klassische” Beispiel für die Nützlichkeit von functionszeigern ist die C-Bibliothek-function qsort() , die eine Schnellsortierung implementiert. Um universell für alle Datenstrukturen zu sein, die der Benutzer hervorbringen kann, benötigt er ein paar Leerzeiger auf sortierbare Daten und einen pointers auf eine function, die weiß, wie zwei Elemente dieser Datenstrukturen zu vergleichen sind. Dies ermöglicht uns, die für den Job gewählte function zu definieren und sogar die Auswahl der Vergleichsfunktion zur Laufzeit zu ermöglichen, z. B. zum Sortieren aufsteigend oder absteigend.

In C ist die klassische Verwendung die qsort-function , wobei der vierte Parameter ein pointers auf eine function ist, die verwendet wird, um die Sortierung innerhalb der Sortierung durchzuführen. In C ++ würde man eher Funktoren (Objekte, die wie functionen aussehen) für diese Art von Dingen verwenden.

Stimmen Sie mit allen oben genannten Punkten überein, plus …. Wenn Sie eine DLL dynamisch zur Laufzeit laden, benötigen Sie functionszeiger, um die functionen aufzurufen.

Ich werde hier gegen den Strom gehen.

In C sind functionszeiger die einzige Möglichkeit, die Anpassung zu implementieren, da kein OO vorhanden ist.

In C ++ können Sie für dasselbe Ergebnis entweder functionszeiger oder Funktoren (functionsobjekte) verwenden.

Die Funktoren haben aufgrund ihrer Objekteigenschaften eine Reihe von Vorteilen gegenüber Raw-functionszeigern, insbesondere:

  • Sie können mehrere Überlastungen des operator()
  • Sie können einen Status / Verweis auf vorhandene Variablen haben
  • Sie können vor Ort gebaut werden ( lambda und bind )

Ich persönlich bevorzuge Funktoren, um pointers (trotz des Boilerplate-Codes) zu funktionieren, hauptsächlich weil die Syntax für functionszeiger leicht haarig werden kann (aus dem Function Pointer Tutorial ):

 typedef float(*pt2Func)(float, float); // defines a symbol pt2Func, pointer to a (float, float) -> float function typedef int (TMyClass::*pt2Member)(float, char, char); // defines a symbol pt2Member, pointer to a (float, char, char) -> int function // belonging to the class TMyClass 

Das einzige Mal, dass ich functionszeiger gesehen habe, die wo Funktoren verwendet wurden, war Boost.Spirit. Sie haben die Syntax völlig missbraucht, um eine beliebige Anzahl von Parametern als einen einzigen Template-Parameter zu übergeben.

  typedef SpecialClass class_type; 

Aber da variadic Templates und Lambdas um die Ecke sind, bin ich mir nicht sicher, ob wir functionszeiger im reinen C ++ – Code schon lange verwenden werden.

Ich habe kürzlich functionszeiger verwendet, um eine Abstraktionsschicht zu erstellen.

Ich habe ein Programm in reinem C geschrieben, das auf eingebetteten Systemen läuft. Es unterstützt mehrere Hardware-Varianten. Abhängig von der Hardware, auf der ich arbeite, muss es verschiedene Versionen einiger functionen aufrufen.

Zur Initialisierungszeit ermittelt das Programm, auf welcher Hardware es läuft, und füllt die functionszeiger auf. Alle übergeordneten Routinen im Programm rufen nur die functionen auf, auf die pointers verweisen. Ich kann Unterstützung für neue Hardware-Varianten hinzufügen, ohne die übergeordneten Routinen zu berühren.

Früher habe ich switch / case-statementen verwendet, um die richtigen functionsversionen auszuwählen, aber das wurde unpraktisch, als das Programm wuchs, um mehr und mehr Hardware-Varianten zu unterstützen. Ich musste überall Fallberichte hinzufügen.

Ich habe auch Zwischenfunktionsschichten ausprobiert, um herauszufinden, welche function zu verwenden ist, aber sie haben nicht viel geholfen. Ich musste immer noch Fallanweisungen an mehreren Stellen aktualisieren, wenn wir eine neue Variante hinzufügten. Mit den functionszeigern muss ich nur die Initialisierungsfunktion ändern.

functionszeiger können in C verwendet werden, um eine Schnittstelle für die Programmierung zu erstellen. Abhängig von der spezifischen functionalität, die zur Laufzeit benötigt wird, kann dem functionszeiger eine andere Implementierung zugewiesen werden.

Meine Hauptverwendung von ihnen war CALLBACKS: wenn Sie Informationen über eine function speichern müssen, um später anzurufen .

Sagen Sie, Sie schreiben Bomberman. 5 Sekunden nachdem die Person die Bombe fallen lässt, sollte sie explodieren (rufen Sie die function explode() ).

Jetzt gibt es 2 Möglichkeiten, es zu tun. Ein Weg besteht darin, alle Bomben auf dem Bildschirm zu “sondieren”, um zu sehen, ob sie bereit sind, in der Hauptschleife zu explodieren.

 foreach bomb in game if bomb.boomtime() bomb.explode() 

Eine andere Möglichkeit besteht darin, einen callback an Ihr Uhrensystem anzuhängen. Wenn eine Bombe platziert wird, fügen Sie einen Callback hinzu, um bomb.explode () aufzurufen, wenn die Zeit reif ist .

 // user placed a bomb Bomb* bomb = new Bomb() make callback( function=bomb.explode, time=5 seconds ) ; // IN the main loop: foreach callback in callbacks if callback.timeToRun callback.function() 

Hier kann callback.function() irgendeine function sein , weil es ein functionszeiger ist.

Wie Rich oben sagte, ist es sehr üblich, dass functionszeiger in Windows auf eine Adresse verweisen, die die function speichert.

Wenn Sie in C language auf Windows-Plattform programmieren, laden Sie im Grunde eine DLL-Datei im primären Speicher (mit LoadLibrary ), und um die in DLL gespeicherten functionen zu verwenden, müssen Sie functionszeiger erstellen und auf diese Adresse verweisen (mit GetProcAddress ).

Verweise:

  • LoadLibrary

  • GetProcAddress

Verwendung des functionszeigers

Dynamischer Aufruf der function basierend auf Benutzereingaben In diesem Fall erstellen Sie eine Zuordnung von String und functionszeiger.

 #include #include using namespace std; //typedef map funMap; #define funMap map funMap objFunMap; int Add(int x, int y) { return x+y; } int Sub(int x, int y) { return xy; } int Multi(int x, int y) { return x*y; } void initializeFunc() { objFunMap["Add"]=Add; objFunMap["Sub"]=Sub; objFunMap["Multi"]=Multi; } int main() { initializeFunc(); while(1) { string func; cout< <"Enter your choice( 1. Add 2. Sub 3. Multi) : "; int no, a, b; cin>>no; if(no==1) func = "Add"; else if(no==2) func = "Sub"; else if(no==3) func = "Multi"; else break; cout< <"\nEnter 2 no :"; cin>>a>>b; //function is called using function pointer based on user input //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)" int ret = objFunMap[func](a, b); cout<  

Auf diese Weise haben wir den functionszeiger in unserem tatsächlichen Buchungskreis verwendet. Sie können 'n' Nummer der function schreiben und sie mit dieser Methode aufrufen.

AUSGABE:

     Geben Sie Ihre Auswahl ein (1. Add 2. Sub 3. Multi): 1
     Geben Sie 2 ein: 2 4
     6
     Geben Sie Ihre Auswahl ein (1. Add 2. Sub 3. Multi): 2
     Geben Sie 2 ein: 10 3
     7
     Geben Sie Ihre Auswahl ein (1. Add 2. Sub 3. Multi): 3
     Geben Sie 2 ein: 3 6
     18

Ich verwende functionszeiger ausgiebig, um Mikroprozessoren zu emulieren, die 1-Byte-Opcodes haben. Ein Array von 256 functionszeigern ist die natürliche Art dies zu implementieren.

Eine Verwendung des functionszeigers könnte darin bestehen, dass wir den Code, an dem die function aufgerufen wird, nicht ändern wollen (was bedeutet, dass der Aufruf dadurch bedingt sein kann und unter anderen Bedingungen unterschiedliche Verarbeitungen durchgeführt werden müssen). Hier sind die functionszeiger sehr praktisch, da wir den Code nicht an der Stelle ändern müssen, an der die function aufgerufen wird. Wir rufen die function einfach über den functionszeiger mit entsprechenden Argumenten auf. Der functionszeiger kann bedingt auf unterschiedliche functionen zeigen. (Dies kann während der Initialisierungsphase geschehen). Außerdem ist das obige Modell sehr hilfreich, wenn wir nicht in der Lage sind, den Code zu ändern, an dem es aufgerufen wird (angenommen, es handelt sich um eine Bibliotheks-API, die wir nicht modifizieren können). Die API verwendet einen functionszeiger zum Aufrufen der entsprechenden benutzerdefinierten function.

Für OO-Sprachen, polymorphe Aufrufe hinter den Kulissen (das gilt auch für C bis zu einem gewissen Punkt, denke ich).

Darüber hinaus sind sie sehr nützlich, um einer anderen function (foo) zur Laufzeit ein anderes Verhalten zu injizieren. Das macht die function höherrangig. Neben seiner Flexibilität macht es den foo-Code lesbarer, da man damit die zusätzliche Logik von “if-else” herausziehen kann.

Es ermöglicht viele andere nützliche Dinge in Python wie Generatoren, Schließungen usw.

Eine andere Perspektive, neben anderen guten Antworten hier:

In C gibt es nur functionszeiger, es gibt keine functionen.

Ich meine, Sie schreiben functionen, aber Sie können functionen nicht manipulieren . Es gibt keine Laufzeitdarstellung einer function als solche. Sie können nicht einmal “eine function” aufrufen. Wenn du schreibst:

 my_function(my_arg); 

was Sie tatsächlich sagen, ist “führen Sie einen Aufruf an die my_function pointers mit dem angegebenen Argument”. Sie rufen über einen functionszeiger auf. Dieser Zero-to-Function-pointers bedeutet, dass die folgenden Befehle dem vorherigen functionsaufruf entsprechen:

 (&my_function)(my_arg); (*my_function)(my_arg); (**my_function)(my_arg); (&**my_function)(my_arg); (***my_function)(my_arg); 

und so weiter (danke @LuuVinhPhuc).

Sie verwenden also functionszeiger bereits als Werte . Offensichtlich würden Sie Variablen für diese Werte haben wollen – und hier ist, wo alle anderen Elemente kommen: Polymorphie / Anpassung (wie in qsort), Callbacks, Sprungtabellen usw.

In C ++ sind die Dinge ein wenig komplizierter, da wir lambdas und Objekte mit operator() und sogar eine std::function class haben, aber das Prinzip ist immer noch das gleiche.