Frage

Ich bin gerade jetzt das Lernen über Funktionszeiger und, wie ich auf das Thema der K & R Kapitel las, das erste, was mich getroffen war, „Hey, das ist ein bisschen wie ein Verschluss ist.“ Ich wusste, dass diese Annahme grundlegend falsch ist irgendwie und nach einer Online-Suche habe ich nicht wirklich eine Analyse dieses Vergleichs finden.

Warum sind C-Stil Funktionszeiger grundsätzlich verschieden von Schließungen oder Lambda-Ausdrücke? Soweit ich kann sagen, dass es mit der Tatsache zu tun hat, dass die Funktionszeiger verweisen noch auf eine definierten (benannt) Funktion in Bezug auf die Praxis im Gegensatz von der Funktion anonym zu definieren.

Warum eine Funktion auf eine Funktion als mächtiger im zweiten Fall zu sehen ist vorbei, wo er namentlich nicht ist, als die erste, wo es nur eine normale, alltägliche Funktion, die übergeben wird?

Bitte sagen Sie mir, wie und warum ich falsch bin, die beide so eng miteinander zu vergleichen.

Danke.

War es hilfreich?

Lösung

A lambda (oder Verschluss ) kapselt sowohl die Funktion Zeiger und Variablen. Aus diesem Grund ist in C # können Sie tun:

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

I verwenden es ein anonym Delegierten als Verschluss (es ist Syntax ist etwas deutlicher und näher an C als das Lambda-äquivalent), die lessThan (a Stapelvariable) in den Verschluss eingefangen. Wenn der Verschluss ausgewertet wird, lessThan (kann dessen Stapelrahmen zerstört wurde) weiterhin referenziert werden. Wenn ich lessThan ändern, dann ändere ich den Vergleich:

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

lessThanTest(99); // returns true
lessThan = 10;
lessThanTest(99); // returns false

In C, das wäre illegal:

BOOL (*lessThanTest)(int);
int lessThan = 100;

lessThanTest = &LessThan;

BOOL LessThan(int i) {
   return i < lessThan; // compile error - lessThan is not in scope
}

wenn ich könnte einen Funktionszeiger definieren, die zwei Argumente verwendet:

int lessThan = 100;
BOOL (*lessThanTest)(int, int);

lessThanTest = &LessThan;
lessThanTest(99, lessThan); // returns true
lessThan = 10;
lessThanTest(100, lessThan); // returns false

BOOL LessThan(int i, int lessThan) {
   return i < lessThan;
}

Aber jetzt muss ich die zwei Argumente übergeben, wenn ich es beurteilen. Wenn ich diese Funktion Zeiger auf eine andere Funktion zu übergeben wollte, wo lessThan war in ihrem Umfang nicht, würde ich entweder müssen sie manuell am Leben zu halten, indem es für jede Funktion in der Kette verläuft, oder indem sie sie zu einem globalen fördern.

Obwohl die meisten Mainstream-Sprachen, die Schließungen unterstützen anonyme Funktionen verwenden, gibt es keine Notwendigkeit dafür. Sie können Verschlüsse ohne anonyme Funktionen haben, und anonyme Funktionen ohne Verschlüsse.

Zusammenfassung: a. Verschluss ist eine Kombination aus Funktionszeigern + erfassten Variablen

Andere Tipps

Als jemand, der Compiler für Sprachen mit und ohne ‚echte‘ Schließungen geschrieben hat, ich respektvoll widersprechen mit einigen der oben genannten Antworten. Ein Lisp, Scheme, ML oder Haskell Schließung schafft keine neue Funktion dynamisch . Stattdessen es wieder verwendet eine vorhandene Funktion tut dies aber mit neuen freien Variablen . Die Sammlung von freien Variablen ist oft die Umwelt , zumindest von programmiersprachen Theoretiker genannt.

Eine Schließung ist nur ein Aggregat, das eine Funktion und eine Umgebung enthält. Im Standard ML von New Jersey Compiler, vertreten wir eine als Datensatz; ein Feld enthalten einen Zeiger auf den Code, und die anderen Felder enthalten sind, die Werte der freien Variablen. Der Compiler erstellt einen neuen Verschluss (nicht-Funktion) dynamisch durch einen neuen Datensatz einen Zeiger auf die gleichen Code, aber mit andere Wert Zuweisung enthält, die für die freien Variablen.

Sie können all dies in C simulieren, aber es ist ein Schmerz in den Arsch. Zwei Techniken sind sehr beliebt:

  1. Geben einen Zeigers auf die Funktion (der Code) und einen separaten Zeiger auf die freie Variable, so dass der Verschluss durch zwei Variablen C aufgeteilt wird.

  2. Führen Sie einen Zeiger auf eine Struktur, wo die Struktur, die Werte der freien Variablen enthält und auch einen Zeiger auf den Code.

Technik # 1 ist ideal, wenn Sie versuchen, eine Art zu simulieren Polymorphismus in C und Sie nicht wollen, die Art der Umwelt offenbaren --- Sie verwenden, um einen void * Zeiger auf repräsentieren die Umwelt. Für Beispiele, schauen Sie sich Dave Hanson C Schnittstellen und Realisierungen . Technik # 2, die mehr ähnelt, was passiert, in nativen Code-Compiler für funktionale Sprachen, ähnelt auch eine andere bekannte Technik ... C ++ Objekte mit virtuellen Elementfunktionen. Die Implementierungen sind fast identisch.

Diese Beobachtung führte zu einem wisecrack von Henry Baker:

  

Die Menschen in der Algol / Fortran Welt klagen seit Jahren, dass sie nicht verstehen, was mögliche Verwendung Funktion Schließungen in effizienter Programmierung der Zukunft haben würde. Dann wird die `objektorientierte Programmierung‘ Revolution geschehen ist, und jetzt Programme jeder Funktion Verschlüsse, mit der Ausnahme, dass sie noch sie sich weigern, zu nennen.

In C können Sie die Funktion inline nicht definieren, so kann man nicht wirklich einen Verschluss erstellen. Alle zu einem gewissen vordefinierten Verfahren um eine Referenz ist vorbei Sie tun. In Sprachen, die anonymen Methoden / Verschlüsse, die Definition der Methoden unterstützen, ist viel flexibler.

In der einfachsten Form, Funktionszeiger haben keinen Raum mit ihnen verbunden sind (es sei denn, Sie den globalen Bereich zählen), während Schließungen den Umfang des Verfahrens schließen, die sie ist zu definieren. Mit lambda, können Sie eine Methode schreiben, die eine Methode schreibt. Verschlüsse können Sie binden „einige Argumente an eine Funktion und eine untere arity Funktion als Ergebnis.“ (Von Thomas Kommentar genommen). Sie können das nicht in C.

EDIT: Hinzufügen ein Beispiel (ich werde Actionscript-ish Syntax Ursache verwenden, was jetzt auf meinem Kopf ist):

Angenommen, Sie eine Methode, die eine andere Methode als Argument, aber bietet keine Möglichkeit, alle Parameter zu übergeben zu dieser Methode, wenn es heißt? Wie, sagen einige Verfahren, die eine Verzögerung verursacht, bevor das Verfahren läuft man es übergeben (dumm Beispiel, aber ich will es einfach halten).

function runLater(f:Function):Void {
  sleep(100);
  f();
}

Jetzt sagen Sie Benutzer runLater wollen () eine Verarbeitung eines Objekts zu verzögern:

function objectProcessor(o:Object):Void {
  /* Do something cool with the object! */
}

function process(o:Object):Void {
  runLater(function() { objectProcessor(o); });
}

Die Funktion, die Sie Prozess vorbei sind () ist nicht einige staticly mehr definierte Funktion. Es ist dynamisch generiert und in der Lage, Verweise auf Variablen enthalten, die in ihrem Umfang waren, als das Verfahren definiert wurde. So kann es Zugriff auf ‚o‘ und ‚objectProcessor‘, auch wenn dies nicht im globalen Bereich.

Ich hoffe, dass Sinn.

Closure = Logik + Umwelt.

Zum Beispiel, betrachten Sie diese C # 3 Methode:

public Person FindPerson(IEnumerable<Person> people, string name)
{
    return people.Where(person => person.Name == name);
}

Der Lambda-Ausdruck nicht nur kapselt die Logik ( "vergleichen den Namen"), sondern auch die Umwelt, einschließlich der Parameter (das heißt lokale Variable) "name".

auf dieses mehr ist, haben Sie einen Blick auf meine Artikel über Schließungen dem Sie nimmt durch C # 1, 2 und 3, die zeigen, wie Verschlüsse Dinge einfacher zu machen.

In C, Funktionszeiger können als Argumente an Funktionen übergeben werden, und kehrten als Wert von Funktionen, aber Funktionen existieren nur auf höchster Ebene: Sie können nicht verschachtelt Funktionsdefinitionen ineinander. Denken Sie darüber nach, was es für C nehmen würde, um verschachtelte Funktionen zu unterstützen, die die Variablen der äußeren Funktion zugreifen können, während immer noch Funktionszeiger in der Lage, nach oben und unten dem Call-Stack zu senden. (Um diese Erklärung zu folgen, sollten Sie die Grundlagen der weiß, wie Funktionsaufrufe implementiert sind in C und am ähnlichsten Sprachen: Befragen Sie die Call-Stack Eintrag auf Wikipedia.)

Was für eine Art von Objekt ist ein Zeiger auf eine verschachtelte Funktion? Es kann nicht nur die Adresse des Codes sein, denn wenn man es nennen, wie funktioniert es die Variablen der äußeren Funktion zugreifen? (Denken Sie daran, dass wegen der Rekursion gibt verschiedene mehrere sein können Anrufe von der Außen Funktion aktiv auf einmal.) Das nennt man die funarg Problem , und es gibt zwei Teilprobleme. die nach unten funargs Problem und die nach oben funargs Problem

Der nach unten funargs Problem, das heißt, einen Funktionszeiger "down den Stapel" als Argument für eine Funktion Senden Sie anrufen, ist eigentlich nicht unvereinbar mit C und GCC verschachtelte Funktionen, wie unten funargs unterstützt . In GCC, wenn Sie einen Zeiger auf eine verschachtelte Funktion erstellen, erhalten Sie wirklich einen Zeiger auf ein Trampolin , ein dynamisch konstruiertes Stück Code, die einrichtet statischer Link Zeiger und ruft dann die eigentliche Funktion, die die statischen Verknüpfungszeiger verwendet, um die Variablen zugreifen die äußeren Funktion.

Die Aufwärts funargs Problem ist schwieriger. GCC verhindert nicht von einem Trampolin Zeiger lassen existieren, nachdem die äußere Funktion nicht mehr aktiv ist (hat keinen Datensatz auf dem Call-Stack) und dann die statische Link Zeiger auf Müll zeigen könnte. Die Aktivierung Datensätze können nicht mehr auf einem Stapel zugeordnet werden. Die übliche Lösung besteht darin, sie auf dem Heap zuzuteilen, und ein Funktionsobjekt lassen eine geschachtelte Funktion nur zeigen auf die Aktivierungsdatensatz der äußeren Funktion darstellt. Ein solches Objekt ist ein Schließung . Dann wird die Sprache hat in der Regel unterstützen Garbage Collection so dass die Datensätze sein können einmal befreit es weist nicht mehr Zeiger auf sie.

Lambdas ( anonyme Funktionen ) sind wirklich ein anderes Thema, aber in der Regel eine Sprache, die läßt Sie definieren anonyme Funktionen im laufenden Betrieb werden Sie auch als Funktionswert bringen sie lassen, so dass sie am Ende Schließungen zu sein.

Ein Lambda ist eine anonyme, dynamisch definiert Funktion. Sie können nur das tun, nicht in C ... wie für Verschlüsse (oder die convination des beide), das typische Lisp Beispiel etwas entlang der Linien von aussehen:

(defun get-counter (n-start +-number)
     "Returns a function that returns a number incremented
      by +-number every time it is called"
    (lambda () (setf n-start (+ +-number n-start))))

C ausgedrückt könnte man sagen, dass die lexikalische Umgebung (der Stapel) von get-counter wird durch die anonyme Funktion erfasst werden und modifiziert intern wie das folgende Beispiel zeigt:

[1]> (defun get-counter (n-start +-number)
         "Returns a function that returns a number incremented
          by +-number every time it is called"
        (lambda () (setf n-start (+ +-number n-start))))
GET-COUNTER
[2]> (defvar x (get-counter 2 3))
X
[3]> (funcall x)
5
[4]> (funcall x)
8
[5]> (funcall x)
11
[6]> (funcall x)
14
[7]> (funcall x)
17
[8]> (funcall x)
20
[9]> 

Verschlüsse implizieren eine Variable vom Punkt der Funktionsdefinition zusammen mit der Funktionslogik gebunden ist, wie die Möglichkeit, ein Mini-Objekt auf der Fliege zu erklären.

Ein wichtiges Problem bei C und Schließungen Variablen auf dem Stack zugeordnet wird, den aktuellen Bereich beim Verlassen, unabhängig davon zerstört werden, wenn ein Verschluss sie zeigt. Dies würde auf die Art von Fehlern führen Menschen bekommen, wenn sie nachlässig Zeiger auf lokale Variablen zurück. Closures impliziert grundsätzlich alle relevanten Variablen sind entweder ref gezählt oder Garbage Collection Elemente auf einem Haufen.

Ich bin nicht bequem Gleichsetzung Lambda mit Schließung, weil ich nicht sicher bin, dass Lambda-Ausdrücke in allen Sprachen Schließungen sind, manchmal denke, ich haben lambdas nur lokal anonyme Funktionen, ohne die Bindung von Variablen definiert (Python 2.1 vor?).

In GCC ist es möglich, Lambda-Funktionen mit dem folgende Makro zu simulieren:

#define lambda(l_ret_type, l_arguments, l_body)       \
({                                                    \
    l_ret_type l_anonymous_functions_name l_arguments \
    l_body                                            \
    &l_anonymous_functions_name;                      \
})

Beispiel von Quelle :

qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]),
     lambda (int, (const void *a, const void *b),
             {
               dump ();
               printf ("Comparison %d: %d and %d\n",
                       ++ comparison, *(const int *) a, *(const int *) b);
               return *(const int *) a - *(const int *) b;
             }));

Mit dieser Technik natürlich Mit beseitigt die Möglichkeit der Anwendung mit anderen Compilern zu arbeiten und ist scheinbar „undefiniert“ Verhalten so YMMV.

Die Schließung fängt die freie Variablen in einer Umwelt . Die Umwelt wird immer noch existieren, auch wenn die umgebende Code nicht mehr aktiv sein kann.

Ein Beispiel in Common Lisp, wo MAKE-ADDER einen neuen Verschluss zurück.

CL-USER 53 > (defun make-adder (start delta) (lambda () (incf start delta)))
MAKE-ADDER

CL-USER 54 > (compile *)
MAKE-ADDER
NIL
NIL

Unter Verwendung der obigen Funktion:

CL-USER 55 > (let ((adder1 (make-adder 0 10))
                   (adder2 (make-adder 17 20)))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder1))
               (print (funcall adder1))
               (describe adder1)
               (describe adder2)
               (values))

10 
20 
30 
40 
37 
57 
77 
50 
60 
#<Closure 1 subfunction of MAKE-ADDER 4060001ED4> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(60 10)
#<Closure 1 subfunction of MAKE-ADDER 4060001EFC> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(77 20)

Beachten Sie, dass die DESCRIBE Funktion zeigt, dass die Funktionsobjekte für beide Verschlüsse sind die gleichen, aber die Umwelt ist anders.

Common Lisp beide macht Schließungen und reine Funktionsobjekte (jene ohne Umwelt), die beide auf Funktionen und man kann rufen beide in der gleichen Art und Weise, hier FUNCALL verwendet wird.

Der Hauptunterschied ergibt sich aus dem Mangel an lexikalischen Scoping in C.

A Funktionszeiger ist nur, dass ein Zeiger auf einen Code-Block. Jeder nicht-Stack-Variable, die es verweist, ist global, statisch oder ähnliches.

Ein Verschluss, OTOH, hat einen eigenen Staat in Form von ‚äußeren Variablen‘ oder ‚upvalues‘. sie können als privat oder geteilt sein, wie Sie wollen, lexikalischen Scoping verwenden. Sie können viele Verschlüsse mit dem gleichen Funktionscode erstellen, aber verschiedene Variablen Instanzen.

Einige Verschlüsse können einige Variablen teilen, und so kann die Schnittstelle eines Objekts (in der OOP Sinne). zu machen, dass in C müssen Sie eine Struktur mit einer Tabelle von Funktionszeiger zuzuordnen (das ist, was C ++ tut, mit einer Klasse V-Tabelle).

kurz gesagt, eine Schließung ist ein Funktionszeiger PLUS einig Zustand. es ist ein übergeordnetes Konstrukt

Die meisten Antworten zeigen, dass Verschlüsse Funktionszeiger erfordern, was möglicherweise zu anonymen Funktionen, sondern als Mark schrieb Verschlüsse mit benannten Funktionen existieren kann. Hier ist ein Beispiel in Perl:

{
    my $count;
    sub increment { return $count++ }
}

Der Verschluss ist die Umgebung, die die $count Variable definiert. Es ist nur das increment Unterprogramm und bleibt zwischen den Anrufen.

In C eine Funktionszeiger ist ein Zeiger, der eine Funktion aufrufen, wenn Sie dereferenzieren, ein Verschluss ein Wert ist, der eine Funktion der Logik enthält und die Umwelt (Variablen und die Werte, die sie gebunden sind) und eine Lambda in der Regel bezieht sich auf ein Wert, der tatsächlich eine unbenannte Funktion ist. In C ist eine Funktion nicht ein First-Class-Wert, so dass es nicht um übergeben werden kann, so dass Sie einen Zeiger auf mich stattdessen passieren müssen, aber in funktionalen Sprachen (wie Scheme) Sie funktioniert auf die gleiche Art und Weise übergeben Sie einen anderen Wert passieren kann

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top