Funktionszeiger sind leicht zu deklarieren, wenn man die grundlegenden Deklaratoren kennt:
- id:
ID
: ID ist eine
- Zeiger:
*D
: D Zeiger auf
- Funktion:
D(<parameters>)
: D Funktionsaufnahme <
Parameter >
zurückkehrend
Während D ein weiterer Deklarator ist, der nach denselben Regeln aufgebaut ist. Am Ende, irgendwo, endet es mit ID
(siehe unten für ein Beispiel), der der Name der deklarierten Entität ist. Versuchen wir, eine Funktion zu bauen, die einen Zeiger auf eine Funktion nimmt, die nichts nimmt und int zurückgibt, und einen Zeiger auf eine Funktion zurückgibt, die ein char nimmt und int zurückgibt. Mit type-defs sieht das folgendermaßen aus
typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);
Wie Sie sehen, ist es ziemlich einfach, sie mit Hilfe von Typedefs aufzubauen. Ohne Typendefinitionen ist es auch nicht schwer, mit den oben genannten Deklarationsregeln, die konsequent angewendet werden. Wie Sie sehen, habe ich den Teil ausgelassen, auf den der Zeiger zeigt, und das, was die Funktion zurückgibt. Das ist das, was ganz links in der Deklaration steht und nicht von Interesse ist: Es wird am Ende hinzugefügt, wenn man den Deklarator bereits aufgebaut hat. Lass uns das tun. Konsequent aufbauen, zuerst wortreich - die Struktur zeigen mit [
y ]
:
function taking
[pointer to [function taking [void] returning [int]]]
returning
[pointer to [function taking [char] returning [int]]]
Wie Sie sehen, kann man einen Typ vollständig beschreiben, indem man Deklaratoren aneinanderhängt. Die Konstruktion kann auf zwei Arten erfolgen. Die eine ist von unten nach oben, wobei man mit dem ganz rechten Ding (Blätter) beginnt und sich bis zum Bezeichner durcharbeitet. Der andere Weg ist von oben nach unten, wobei man mit dem Bezeichner beginnt und sich bis zu den Blättern vorarbeitet. Ich werde beide Wege aufzeigen.
Von unten nach oben
Die Konstruktion beginnt mit dem Ding rechts: Das, was zurückgegeben wird, also die Funktion, die char nimmt. Um die Deklaratoren zu unterscheiden, werde ich sie nummerieren:
D1(char);
Der Parameter char wurde direkt eingefügt, da er trivial ist. Hinzufügen eines Zeigers auf den Deklarator durch Ersetzen von D1
von *D2
. Beachten Sie, dass wir Klammern um Folgendes setzen müssen *D2
. Dies lässt sich feststellen, indem man die Rangfolge der *-operator
und der Funktionsaufruf-Operator ()
. Ohne unsere Klammern würde der Compiler dies als *(D2(char p))
. Aber das wäre keine einfache Ersetzung von D1 durch *D2
mehr, natürlich. Klammern sind um Deklaratoren herum immer erlaubt. Man macht also eigentlich nichts falsch, wenn man zu viele davon hinzufügt.
(*D2)(char);
Rückgabeart ist vollständig! Ersetzen wir nun D2
durch den Funktionsdeklarator Funktionsaufnahme <parameters>
zurückkehrend das ist D3(<parameters>)
in der wir uns jetzt befinden.
(*D3(<parameters>))(char)
Beachten Sie, dass keine Klammern erforderlich sind, da wir wollen D3
diesmal ein Funktionsdeklarator und kein Zeigerdeklarator zu sein. Großartig, jetzt fehlen nur noch die Parameter dafür. Die Parameter werden genau so wie der Rückgabetyp deklariert, nur mit char
ersetzt durch void
. Ich werde es also kopieren:
(*D3( (*ID1)(void)))(char)
Ich habe ersetzt D2
von ID1
, da wir mit diesem Parameter fertig sind (er ist bereits ein Zeiger auf eine Funktion - kein weiterer Deklarator erforderlich). ID1
wird der Name des Parameters sein. Nun, ich habe oben gesagt, dass man am Ende den Typ hinzufügt, den all diese Deklaratoren modifizieren - denjenigen, der ganz links in jeder Deklaration erscheint. Bei Funktionen wird das der Rückgabetyp. Bei Zeigern der Typ, auf den gezeigt wird usw... Interessant ist, dass der Typ in der umgekehrten Reihenfolge erscheint, nämlich ganz rechts :) Wie auch immer, wenn man ihn ersetzt, erhält man die vollständige Deklaration. Beide Male int
Natürlich.
int (*ID0(int (*ID1)(void)))(char)
Ich habe den Bezeichner der Funktion aufgerufen ID0
in diesem Beispiel.
Von oben nach unten
Dies beginnt mit dem Bezeichner ganz links in der Beschreibung des Typs und umschließt den Deklarator, während wir uns nach rechts vorarbeiten. Beginnen Sie mit Funktionsaufnahme <
Parameter >
zurückkehrend
ID0(<parameters>)
Der nächste Punkt in der Beschreibung (nach "Rückkehr") war Zeiger auf . Lassen Sie es uns einbeziehen:
*ID0(<parameters>)
Die nächste Sache war dann Funktionstüchtigkeit <
Parameter >
zurückkehrend . Der Parameter ist ein einfaches Zeichen, also fügen wir ihn gleich wieder ein, da er wirklich trivial ist.
(*ID0(<parameters>))(char)
Beachten Sie die Klammern, die wir hinzugefügt haben, da wir wieder wollen, dass die *
bindet zuerst, und dann die (char)
. Sonst würde es heißen Funktionsaufnahme <
Parameter >
Funktion zurückgeben ... . Nein, Funktionen, die Funktionen zurückgeben, sind nicht einmal erlaubt.
Jetzt müssen wir nur noch die <
Parameter >
. Ich werde eine kurze Version der Ableitung zeigen, da ich denke, dass Sie bereits eine Vorstellung davon haben, wie man es macht.
pointer to: *ID1
... function taking void returning: (*ID1)(void)
Setzen Sie einfach int
vor den Deklaratoren, wie wir es bei Bottom-up gemacht haben, und wir sind fertig
int (*ID0(int (*ID1)(void)))(char)
Das Schöne daran
Ist Bottom-up oder Top-down besser? Ich bin an Bottom-up gewöhnt, aber manche Leute mögen sich mit Top-down wohler fühlen. Ich denke, das ist Geschmackssache. Übrigens, wenn Sie alle Operatoren in dieser Deklaration anwenden, erhalten Sie am Ende einen int:
int v = (*ID0(some_function_pointer))(some_char);
Das ist eine schöne Eigenschaft von Deklarationen in C: Die Deklaration besagt, dass, wenn diese Operatoren in einem Ausdruck mit dem Bezeichner verwendet werden, sie den Typ ganz links ergibt. Das ist auch bei Arrays der Fall.
Ich hoffe, dieses kleine Tutorial hat euch gefallen! Jetzt können wir darauf verweisen, wenn sich Leute über die seltsame Deklarationssyntax von Funktionen wundern. Ich habe versucht, so wenig C-Interna wie möglich einzubauen. Fühlen Sie sich frei, Dinge darin zu bearbeiten/zu korrigieren.
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