4-Sprachen-Vergleich des Abschlusses (Python, JavaScript, Java, C ++)

Überblick

Closure ist wie JavaScript, aber ich hatte die Möglichkeit, Closure in Python zu verwenden, also werde ich es in verschiedenen Sprachen vergleichen.

Wenn Sie einen Fehler machen, teilen Sie uns dies bitte in den Kommentaren mit! Natürlich werde ich versuchen, keine Fehler zu machen.

Für JavaScript

function outer(){
    let a = 0;
    let inner = () => {
      console.log(a++);
    };
    return inner;
}

let fn = outer();

>>> fn();
0
>>> fn();
1
>>> fn();
2

Die Variable a bleibt vom GC nicht wiederhergestellt, da die innere einen Verweis auf a enthält. Einfach und leicht zu verstehen. Beachten Sie jedoch, dass a auch dann beibehalten wird, wenn a wie unten gezeigt als Argument von Outer übergeben wird.

function outer(a){
    let inner = () => {
      console.log(a++);
    };
    return inner;
}

let fn = outer(0);

>>> fn();
0
>>> fn();
1
>>> fn();
2

Versuchen Sie, den Verschluss an einem anderen Ort als dem, an dem er definiert wurde, auszuführen

Aus der Schlussfolgerung können Sie sich natürlich auch an den Variablen zum Zeitpunkt der Definition auf die Variablen beziehen, die sich von denen zum Zeitpunkt der Definition unterscheiden.

Erstellen Sie eine .mjs-Datei, um die Importanweisung auf dem Knoten auszuführen, und verwenden Sie den Befehl node --experimental-modules.

module.mjs


// export let a = 1;
//nicht exportieren a
let a = 1;

export function outer(){
  let b = 1000;
  let inner1 = ()=>{
    console.log(b++);
  }
  return inner1;
}

//Keine funktionsunfähige Funktion
export function inner2(){
  console.log(a++)
}

closure.mjs


import * as m from "./module.mjs";

let fn = m.outer();

fn();
fn();
fn();
m.inner2();
m.inner2();
m.inner2();
console.log(a)

Ausgabe:


$ node --experimental-modules closure.mjs
(node:12980) ExperimentalWarning: The ESM module loader is experimental.
1000
1001
1002
1
2
3
file:///***********/closure.mjs:11
console.log(a)
            ^

ReferenceError: a is not defined

Auf diese Weise ist a nicht definiert, kann aber in inner2 erwähnt werden. Dies bedeutet, dass in JavaScript eine Funktion zum Abschluss wird, auch wenn es sich nicht um eine In-Function-Funktion handelt.

Führen Sie es tatsächlich mit Firefox (66.0.3) aus.

$ cp closure.mjs closure.js
$ cp module.mjs module.js

Schreiben Sie die Importanweisung von close.js als import * als m von "./module.js" um.

closure.html


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script type="module" src="closure.js"></script>
    <title>test</title>
  </head>
  <body>
  </body>
</html>

Greifen Sie mit Firefox auf schloss.html zu und überprüfen Sie das Protokoll.

1000
1001
1002
1
2
3
ReferenceError: a is not defined[Einzelheiten]

Das Ergebnis war genau das gleiche. In Python ist der Umfang globaler Variablen auf diese Datei beschränkt. Wenn Sie jedoch die Import / Export-Funktion verwenden und diese Variable nicht exportieren, ist sie in JavaScript identisch? (Es ist natürlich, weil es nicht exportiert)

Für Python

def outer():
    a = 0
    def inner():
        nonlocal a
        print(a)
        a += 1
    
    return inner

fn = outer()

>>> fn()
0
>>> fn()
1
>>> fn()
2

Python ist fast das gleiche und leicht zu verstehen, aber es gibt ein Extra. Sein Name ist nicht lokal. Erforderlich, wenn versucht wird, eine Variable im äußeren Bereich zu ändern. Übrigens, wenn global a anstelle von nicht lokal verwendet wird, bezieht sich a auf die globale Variable a.

Die Anweisung> nonlocal stellt sicher, dass die aufgelisteten Bezeichner auf die zuvor gebundene Variable im äußeren Bereich verweisen, ausgenommen global.

Mit der globalen Anweisung wird angegeben, dass die aufgelisteten Bezeichner als globale Variablen interpretiert werden sollen.

(Quelle: Python Language Reference)

a = 111
def outer():
    a = 0
    def inner1():
        #Wenn Sie nur darauf verweisen möchten, müssen Sie nicht nicht lokal a verwenden
        print(a)
        #nicht lokal a oder a+=Ich erhalte eine Fehlermeldung mit 1
        # a += 1
    def inner2():
        #global a bedeutet a=Siehe die Definition von 111
        global a
        print(a)
    
    return (inner1, inner2)

inner1, inner2 = outer()

>>> inner1()
0
>>> inner2()
111

#Natürlich, aber von der Schließung
#A innerhalb der referenzierten äußeren Funktion=0 ist von außen nicht zugänglich
#In diesem Fall a ist dies eine globale Variable=111 wird gedruckt
>>> print(a) 
111

Versuchen Sie, es an einem anderen Ort als dem, an dem es definiert wurde, auszuführen

module.py


a = 1

def outer():
    b = 1000
    def inner1():
        nonlocal b
        print(b)
        b += 1
    return inner1

#inner2 ist keine funktionsunfähige Funktion
def inner2():
    #Ich erhalte eine Fehlermeldung, wenn ich nicht global a mache
    global a
    print(a)
    a += 1

closure.py


from module import *

inner1 = outer()

inner1()
inner1()
inner1()

inner2()
inner2()
inner2()

Ausgabe:

$ python closure.py
1000
1001
1002
1
2
3

Es ist wie bei JavaScript!

(Ich habe geschrieben, dass es sich von JavaScript unterscheidet, weil ich einen Fehler erhalte, ohne global a in inner2 zu sagen, aber ich habe ihn korrigiert, indem ich im Kommentar darauf hingewiesen habe.)

Für Java

Es scheint, dass Java (7 oder früher) keinen Abschluss hat. Sie können etwas Ähnliches (aber mit Einschränkungen) tun, indem Sie eine anonyme Klasse in Ihrer Funktion verwenden. Der Lambda-Ausdruck wurde aus Java8 eingeführt, aber es scheint auch kein Abschluss zu sein. Die folgenden Links, einschließlich ihres Hintergrunds, sind detailliert und sehr einfach zu lesen und werden empfohlen.

Ich habe Teil 2 noch nicht gelesen, aber ich werde ihn veröffentlichen.

Das Folgende ist eine grobe Erklärung. Versuchen wir zunächst ein Beispiel für die Verwendung einer anonymen Klasse. In Java sind Funktionen keine erstklassigen Objekte. Was ist also, wenn wir ein anonymes Klassenobjekt zurückgeben, das stattdessen nur eine Funktion als Mitglied von der äußeren Funktion hat?

interface Inner {
    public void print();
}

public class ClosureTest {

    public Inner outer() {
        //Fehler, wenn final hier nicht verwendet wird
        final int a = 0;
        
        return new Inner() {
            public void print() {
                System.out.println(a);
                
                //Weil es endgültig ist, a++Kann nicht
            }
        }
    }
    
    public static void main(String[] args) {
		ClosureTest ct = new ClosureTest();
		
		Inner inner = ct.outer();

		inner.print(); 

	}

}

Wie im obigen Beispiel müssen Sie final hinzufügen, damit Sie dies nicht wie JavaScript oder Python tun können. Da final jedoch nur als Referenz final ist, ist es möglich, den Wert des Elements für jede Ausführung zu ändern, indem die Variable zu einem Array oder einer ArrayList gemacht wird. Mit anderen Worten, das Gleiche kann erreicht werden.

Als nächstes kommt der Lambda-Ausdruck. Im Fall eines Lambda-Ausdrucks muss diese Variable beim Verweisen auf eine Variable außerhalb des Gültigkeitsbereichs nicht endgültig sein. Beim Ändern des Werts wird jedoch eine Fehlermeldung angezeigt.

public class Closure {
	public static void main(String... args) {
            //Im Gegensatz zur anonymen Klasse muss es nicht endgültig sein!
            int a = 0;
            //Aber a++Ich bekomme einen Fehler im Teil
            Runnable r = () -> System.out.println(a++);
	    r.run();
	  } 
}

Die Details des Fehlers sind wie folgt.

Exception in thread "main" java.lang.Error: Unresolved compilation problem: Local variable a defined in an enclosing scope must be final or effectively final

Dies bedeutet, dass die Variable a endgültig oder praktisch endgültig sein muss. Im Wesentlichen endgültig bedeutet, keine Änderungen wie im obigen Beispiel vorzunehmen.

Mit anderen Worten, die Behandlung beim Verweisen auf Variablen außerhalb des Gültigkeitsbereichs ist für anonyme Klassen und Lambda-Ausdrücke (fast) dieselbe.

Als ich zum ersten Mal etwas über anonyme Klassen, funktionale Schnittstellen, Lambda-Notation usw. erfuhr, fragte ich mich, was das war, aber aus der Perspektive des Abschlusses habe ich das Gefühl, dass ich es verstehen kann.

Für C ++

Es scheint einfach zu sein, mit C ++ 11 Lambda-Ausdrücken zu schreiben.

Erstens ist dies der Lambda-Ausdruck.

[](inta,intb) -> int { return a + b; }

Verwenden Sie wie folgt.

auto fn = [](inta,intb) -> int { return a + b; }
int c = fn();

Der Teil "-> int" gibt den Rückgabetyp dieser Funktion an. Es kann wie folgt weggelassen werden.

[](inta,intb) { return a + b; }

[] Wird später beschrieben.

Und

Dieser Lambda-Ausdruck definiert ein Funktionsobjekt im laufenden Betrieb:

struct F {
 auto operator()(inta,intb) const -> decltype(a + b)
 {
    return a + b;
 }
};

(Quelle: cpprefjp --C ++ Japanese Reference)

Es ist interessant, () zu überladen, um ein Funktionsobjekt zu realisieren. Der Grund, warum es zwei Rückgabewerte gibt, auto und decltype (a + b), ist siehe hier.

[] Bedeutet Erfassung.

Lambda-Ausdrücke verfügen über eine Funktion namens "Capture", mit der Sie auf automatische Variablen außerhalb des Lambda-Ausdrucks innerhalb des Lambda-Ausdrucks verweisen können. Die Erfassung wird im Block [] am Anfang des Lambda-Ausdrucks angegeben, der als Lambda-Einführer bezeichnet wird.

Die Erfassung umfasst die Kopier- und Referenzerfassung. Sie können angeben, welche Methode standardmäßig erfasst und welche Methode einzelne Variablen erfasst werden soll.

(Quelle: cpprefjp --C ++ Japanese Reference)

Unten finden Sie ein Beispiel für die Erfassung

#include <iostream>

using namespace std;

int main(){
  int a = 1;
  int b = 2;
  
  //Kopieraufnahme a
  auto fn1 = [a]() { cout << a << endl; };
  
  //Referenzaufnahme a
  auto fn2 = [&a]() { cout << a << endl; };
  
  //Kopieren Sie die Erfassung von a und b
  auto fn3 = [=]() { cout << a + b << endl; };
  
  //Referenzaufnahme von a und b
  auto fn4 = [&]() { cout << a + b << endl; };
  
  a = 1000;
  b = 2000;
  
  fn1();
  fn2();
  fn3();
  fn4();
}

Ausgabe:

1
1000
3
3000

Wird es zum Zeitpunkt der Kopiererfassung wie folgt sein? Bitte lassen Sie mich wissen, wenn Sie Details haben.

Dies ist imaginärer Code
struct F {
  //Ist der Variablenname nicht a und b?
  int a = 1;
  int b = 2;
  auto operator()() const -> decltype(a + b)
  {
     cout << a + b << endl;
  }
};

Und der Abschluss kann so geschrieben werden.

#include <iostream>
#include <functional>

std::function<int()> outer()
{
    int a = 0;
    //Kopieraufnahme a
    auto inner = [a]() mutable -> int {
        return a++;
    };
    return inner;
}

int main()
{
    auto inner = outer()

    std::cout << inner() << std::endl;
    std::cout << inner() << std::endl;
    std::cout << inner() << std::endl;
    return 0;
}

In Bezug auf veränderlich,

Die erfasste Variable wird als Elementvariable des Abschlussobjekts betrachtet, und der Funktionsaufrufoperator des Abschlussobjekts ist standardmäßig const-qualifiziert. Daher kann die kopiererfasste Variable nicht in den Lambda-Ausdruck umgeschrieben werden.

Wenn Sie die kopiererfasste Variable neu schreiben möchten, schreiben Sie veränderlich nach der Liste der Lambda-Ausdrucksparameter.

(Quelle: cpprefjp --C ++ Japanese Reference) Und das.

Kopieren Sie in diesem Beispiel a in den externen Bereich und speichern Sie es als Elementvariable im Lambda-Ausdruck (Funktionsobjekt). Die Variable, die wie Java kopiert wird, ist const (final), kann aber durch die veränderbare Phrase geändert werden.

Natürlich im obigen Beispiel

auto inner = [&a]() mutable -> int {}

Wenn Sie eine Referenzaufnahme wie diese durchführen, wird das Referenzziel am Ende der äußeren () Ausführung freigegeben, sodass es sich um eine Kopienerfassung handeln muss.

Bonus

Sie können interessante Verschlüsse in Python verwenden. Es gibt eine Bibliothek namens http.server, und Sie können einen einfachen Webserver einrichten. Es wird wie folgt verwendet, aber das hd des zweiten Arguments von HTTPServer () muss ein Klassenobjekt sein. Aber hd funktioniert gut mit Verschlüssen.

server = HTTPServer(('', int(port)), hd)
server.serve_forever()

Wenn hd ein Verschluss ist:

def handler_wrapper():
    counter = [0]
    def handler(*args):
        counter[0] += 1
        return HandleServer(counter, *args)

    return handler

hd = handler_wrapper()

Wenn ich Zeit habe, einschließlich der Gründe, warum ich dies tun sollte, möchte ich es als separaten Artikel schreiben.

Zusammenfassung

Die Schließung ist schwierig.

Recommended Posts

4-Sprachen-Vergleich des Abschlusses (Python, JavaScript, Java, C ++)
Geschwindigkeitsvergleich von Python, Java, C ++
Grundlegender Grammatikvergleich in fünf Sprachen (C #, Java, Python, Ruby, Kotlin)
Vergleich der grundlegenden Grammatik zwischen Java und Python
Einführung in Protobuf-c (C-Sprache ⇔ Python)
Rufen Sie die c-Sprache von Python aus auf (python.h)
Vergleich der Deserialisierungsleistung von msgpack (C ++ / Python / Ruby)
Express Python-Ertrag in JavaScript oder Java
Python lernen! Vergleich mit Java (Grundfunktion)
Socket-Kommunikation in C-Sprache und Python
Generieren Sie mit Python eine C-Sprache aus dem S-Ausdruck
[C, C ++, Python, JavaScript] L Chika mit Edison
Schreiben Sie eine C-Sprach-Linkliste in einem objektorientierten Stil (mit Codevergleich zwischen Python und Java).
Erster Python 3 ~ Erster Vergleich ~
62-ary <-> dezimale Konvertierungsskripte nach Sprache (R, Python, Java)
Python-Abschlussbeispiel
Python> Funktion> Schließen
Schreiben von Protokollen in eine CSV-Datei (Python, C-Sprache)
Python C ++ Notizen
Python, openFrameworks (c ++)
paiza POH ec-Kampagne (C # / Java / Python / Ruby) # paizahack_01
Versuchen Sie, ein Python-Modul in C-Sprache zu erstellen
C-Sprache, Java, Python-Benchmarks mit Primfaktorisierung
AtCoder Anfängerwettbewerb 176 C Problem "Schritt" Erklärung (Python3, C ++, Java)
Die Geschichte der automatischen Sprachkonvertierung von TypeScript / JavaScript / Python
Schreiben wir jeweils Python, Ruby, PHP, Java und JavaScript
Vergleich von CoffeeScript mit JavaScript-, Python- und Ruby-Grammatik
Vergleich der Ausführungszeit von Python SDP
Python: Verarbeitung natürlicher Sprache
Python C / C ++ - Erweiterungsmusterzeiger
C-Sprache ALDS1_3_B Warteschlange
Einführung in die Python-Sprache
Weiter Python in C-Sprache
Python Package Manager-Vergleich
[C-Sprachalgorithmus] Endianness
C-API in Python 3
ABC147 C --HonestOrUnkind2 [Python]
Rufen Sie C-Sprachfunktionen von Python auf, um mehrdimensionale Arrays auszutauschen
AtCoder Anfängerwettbewerb 174 B Problem "Entfernung" Erklärung (C ++, Python, Java)
Lassen Sie uns das Ausführungsergebnis des Programms mit C ++, Java, Python messen.
AtCoder Anfängerwettbewerb 167 Ein Problem "Registrierung" Erklärung (Python3, C ++, Java)
AtCoder ABC151 Problem D Geschwindigkeitsvergleich in C ++ / Python / PyPy
Entfernen Sie führende und nachfolgende Leerzeichen in Python, JavaScript oder Java
2020 Arduino / Raspberry Pi / Python / Microcomputer C Sprachen lernen ~ Empfohlenes Buch ~
AtCoder-Anfängerwettbewerb 169 B Problem "Multiplikation 2" Erläuterung (Python3, C ++, Java)
Verwenden Sie eine Skriptsprache für ein komfortables C ++ - Leben - OpenCV-Port Python zu C ++ -
Verhalten von Teilungsoperatoren zwischen ganzen Zahlen (C-Sprache, C ++, Scala, Java, Rust, Go-Sprache, PHP, JavaScript, Perl, Python, Ruby)