Gibt es Unterschiede zwischen diesen beiden Funktionsdefinitionen höherer Ordnung?

Lesezeit: 6 Minuten

Benutzeravatar von Flee
fliehen

Gibt es Unterschiede zwischen den 4 Hauptaussagen? Ich finde nur apply2(&func) sinnvoll. Alle 4 geben jedoch den gleichen Wert zurück.

int func(void) 
{
    return 1;
}

int apply1( int f1(void) )
{
    return f1();
}

int apply2( int (*f1) (void) ) 
{
    return f1();
}

int main() 
{
    apply1(func); 
    apply1(&func);
    apply2(func);
    apply2(&func);

    return 0;
}

  • Benötigt f1 keine Dereferenzierung in apply2?

    – abiesu

    5. September 2013 um 0:54 Uhr

  • Wie testen Sie den Rückgabewert? Wie wäre es mit einem anderen Rückgabewert, z. B. 197, bei dem es sich weniger um einen Zufall handelt?

    – ThomasW

    5. September 2013 um 0:54 Uhr

  • @abiessu, nein: Funktionszeiger sind genauso aufrufbar wie Funktionen.

    – zneak

    5. September 2013 um 1:07 Uhr

  • @flee Was für eine erstaunliche erste Frage.

    – 1”

    5. September 2013 um 1:38 Uhr

Benutzeravatar von zneak
zneak

Zunächst einmal sind Funktionszeiger schwierig. Zu denken, dass Sie eine Funktion als Parameter an eine andere Funktion übergeben können, erfordert eine gewisse Überwindung, ähnlich wie beim Verständnis von Rekursion. Zuerst wirst du es nicht verstehen, aber dann ist es plötzlich so, als würden sich die Schleusen des Verstehens in deinem Gehirn öffnen und du bist erleuchtet.

Aber dann müssen Sie noch die Regeln zum Übergeben von Funktionen als Parameter in C und C++ kennen. In diesen Sprachen sind Funktionen keine erstklassigen Bürger, daher gibt es viele Einschränkungen, was Sie mit ihnen tun können.

Syntax

Die Funktionszeiger-Syntax ist ein wenig hässlich. Die grundlegende Anatomie ist [return type] (*[name])([argument list]). Die Klammern um *name sind notwendig um zwischen einem Funktionszeiger und einer Funktion, die einen Zeiger zurückgibt, zu unterscheiden:

// not function pointers: * not grouped to function name
int x(); // function that returns an int
int* x(); // function that returns an int*
int *x(); // also a function that returns an int*, spaces don't matter

// function pointers: * grouped to function name
int (*x)(); // pointer to a function that returns an int
int* (*x)(); // pointer to a function that returns an int*

Verfall

In Bezug auf die Übergabe als Parameter verhalten sich Funktionen ungefähr gleich wie Arrays. Wenn sie übergeben werden, verwandeln sie sich in einen Zeiger. Vergleichen:

void Foo(int bar[4]); // equivalent to: void Foo(int* bar)
void Bar(int baz()); // equivalent to: void Bar(int (*baz)())

Dies liegt einfach daran, dass Funktionen und Arrays nicht zuweisbar und nicht kopierbar sind:

int foo[4];
int bar[4] = foo; // invalid

int foo();
int bar() = foo; // invalid

Daher besteht die einzige Möglichkeit, sie als Funktionsparameter zu übergeben, darin, ihre Adresse zu übergeben, anstatt sie zu kopieren. (Bei Arrays ist dies umstritten, aber so funktioniert es.) Die Tatsache, dass diese “Werte” bei der Übergabe als Parameter in Zeiger umgewandelt werden, wird als “Zerfall” bezeichnet.

Diese beiden Prototypen sind kompatibel (d. h. sie beziehen sich auf dieselbe Funktion, nicht auf unterschiedliche Überladungen), und daher gibt es keinen Unterschied zwischen den beiden:

int foo(void bar());
int foo(void (*bar)());

Abgesehen von der Optik gibt es absolut keinen Unterschied zwischen diesen beiden Deklarationen. Beide Funktionen akzeptieren eine Funktion Zeigerob es so aussieht oder nicht, wegen Verfall. Da Decay jedoch oft als unangenehm und verwirrend angesehen wird, werden die meisten Entwickler es vorziehen, ausdrücklich nach einem Funktionszeiger zu fragen (und viele Entwickler wissen nicht einmal, dass Funktionstypen zerfallen können).

Implizite Konvertierungen

Nun zum Übergeben von Funktionen als Parameter. Dieser ist einfach eine Folge des Zerfalls: Funktionen müssen implizit in ihren Funktionszeigertyp konvertierbar sein. Das bedeutet, dass Sie eine Funktion übergeben können, wo ein Funktionszeiger erwartet wird, und der Compiler wird seine Adresse für Sie erhalten. Zu diesem Zweck sind dies noch einmal die gleichen:

int foo();
int (*bar)() = foo; // the compiler implicitly assigns the address of foo to bar
int (*baz)() = &foo; // you explicitly assign the address of foo to baz

Kombinieren Sie diese beiden Erklärungen und Sie werden feststellen, dass Ihre vier Funktionsaufrufe alle gleich sind. apply1 und apply2 beide akzeptieren denselben Parametertyp (int (*)(void)), auch wenn es für nicht offensichtlich ist apply1; und beim Aufruf der Funktionen mit func Anstatt von &funcnimmt der Compiler implizit die Adresse für Sie und macht sie äquivalent zu &func.


Das Folgende liegt außerhalb des Rahmens der Frage, aber es geht auf den vorherigen Teil ein, und ich denke, es ist irgendwie ordentlich.

Funktionsreferenzen [C++ only]

Es ist eine wenig bekannte Tatsache, aber es ist auch möglich, zu bestehen Verweise zu Arrays und Funktionen: In diesem Fall findet kein Zerfall statt. So was:

void Foo(int (&bar)[4]); // NOT equivalent to void Foo(int* bar)
void Bar(int (&baz)()); // NOT equivalent to void Bar(int (*baz)())

In diesem Szenario dürfen Sie den address-of-Operator nicht verwenden, da es keine implizite Konvertierung zwischen Zeigertypen und Referenztypen gibt. Das Besiegen von Verfall wird im Allgemeinen als eine gute Sache angesehen, da Verfall oft verwirrend ist.

int baz();
Bar(baz); // valid
Bar(&baz); // INVALID

Funktionsreferenzen unterliegen den gleichen Regeln wie normale Referenzen: Sie können nur zum Zeitpunkt der Definition zugewiesen werden und dürfen nicht null sein.

Typedefs

Sie können Funktionszeiger weniger hässlich machen, indem Sie verwenden typedef.

typedef int (*X)();
X func; // func is a pointer to a function that returns an int

Die Dinge werden interessanter, wenn Sie das herausnehmen (*) Teil:

typedef int X();
X* func; // func is a function pointer
X& func; // func is a function reference [C++ only]
X func; // func is a function declaration (!!)

Im letzteren Fall X func; ist gleichbedeutend mit einem Erklärungsspruch int func();. Mach das nicht zu Hause, es sei denn, du willst alle verdammt verwirren.

decltype macht einen Unterschied [C++ only]

Ein weiterer interessanter Unterschied zwischen Funktionen und Funktionszeigern ergibt sich aus der Verwendung von decltype. decltype “gibt” den Typ eines Ausdrucks zurück. Für dieses Konstrukt gibt es ist ein Unterschied zwischen function und &function:

int bar();
decltype(bar); // type is int ()
decltype(&bar); // type is int (*)()

Dieser Unterschied ist besonders wichtig, wenn Sie den Typ beispielsweise als Vorlagenparameter an übergeben möchten std::unique_ptr.

std::unique_ptr<void, decltype(free)> foo; // INVALID
std::unique_ptr<void, decltype(&free)> foo; // valid

Das erste ist ungültig, da es versuchen würde, eine Funktion als Instanzfeld von zu erstellen unique_ptr.

  • Vielen Dank für Ihre Antwort. Jetzt verstehe ich, dass es keinen Unterschied zwischen apply2(func) und apply2(&func) gibt. Ich kann jedoch immer noch nicht verstehen, warum apply1 funktioniert.

    – fliehen

    5. September 2013 um 1:03 Uhr

  • @ user2748866 Warum nicht? apply1(int fn(void)) ist das gleiche wie apply1(int (*fn)(void)); und apply1(func) ist das gleiche wie apply1(&func).

    – zneak

    5. September 2013 um 1:06 Uhr

  • Es ist nicht falscher als die Deklaration apply(int foo[4]). Sie übergeben nicht wirklich ein Array, sondern einen Zeiger auf dieses Array. Das nennt man “Zerfall”: einige Typen (Funktionen und Arrays) Verfall auf ihren Zeigertyp, wenn Sie sie als Parameter übergeben.

    – zneak

    5. September 2013 um 1:13 Uhr

  • “Zeiger auf ein Array” lässt mich zusammenzucken. Ich weiß, was Sie sagen, aber seitdem ist es verwirrend p ist ein “Zeiger auf ein Array”, wenn es beispielsweise als deklariert ist int (*p)[4]. Ein Array-Name, der zerfällt zu int *p Dies wird zu einem Zeiger auf das erste Element, nicht auf das gesamte Array.

    – Benutzer541686

    5. September 2013 um 4:00 Uhr

  • Das könntest du hinzufügen decltype((bar)) wird Ihnen den Typ geben int (&)().

    – Einfach

    6. September 2013 um 10:31 Uhr

1405170cookie-checkGibt es Unterschiede zwischen diesen beiden Funktionsdefinitionen höherer Ordnung?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy