Lambda-Ausdrücke in C++

Lambda Expressions C



Warum Lambda-Ausdruck?

Betrachten Sie die folgende Aussage:

intmyInt= 52;

Hier ist myInt ein Bezeichner, ein lvalue. 52 ist ein Literal, ein Prvalue. Heute ist es möglich, eine Funktion speziell zu codieren und an die Position 52 zu setzen. Eine solche Funktion wird als Lambda-Ausdruck bezeichnet. Betrachten Sie auch das folgende kurze Programm:







#enthalten

mit Namensraumstd;

intfn(intdurch)

{

intAntworten=durch+ 3;

RückkehrAntworten;

}


inthauptsächlich()

{

fn(5);



Rückkehr 0;

}

Heute ist es möglich, eine Funktion speziell zu codieren und an die Position des Arguments von 5 des Funktionsaufrufs fn(5) zu setzen. Eine solche Funktion wird als Lambda-Ausdruck bezeichnet. Der Lambda-Ausdruck (Funktion) an dieser Position ist ein Prvalue.



Jedes Literal mit Ausnahme des Zeichenfolgenliterals ist ein Prvalue. Der Lambda-Ausdruck ist ein spezielles Funktionsdesign, das als Literal in Code passen würde. Es ist eine anonyme (unbenannte) Funktion. In diesem Artikel wird der neue primäre C++-Ausdruck erläutert, der als Lambda-Ausdruck bezeichnet wird. Grundkenntnisse in C++ sind Voraussetzung, um diesen Artikel zu verstehen.



Artikelinhalt

Illustration des Lambda-Ausdrucks

Im folgenden Programm wird einer Variablen eine Funktion, die ein Lambda-Ausdruck ist, zugewiesen:





#enthalten

mit Namensraumstd;

Autofn= [](inthalt)

{

intAntworten=halt+ 3;

RückkehrAntworten;

};


inthauptsächlich()

{

Autovariab=fn(2);

Kosten <<variab<< ' ';


Rückkehr 0;

}

Die Ausgabe ist:

5

Außerhalb der Funktion main() gibt es die Variable fn. Sein Typ ist automatisch. Auto bedeutet in dieser Situation, dass der tatsächliche Typ, z. B. int oder float, durch den rechten Operanden des Zuweisungsoperators (=) bestimmt wird. Rechts vom Zuweisungsoperator befindet sich ein Lambda-Ausdruck. Ein Lambda-Ausdruck ist eine Funktion ohne den vorhergehenden Rückgabetyp. Beachten Sie die Verwendung und Position der eckigen Klammern []. Die Funktion gibt 5 zurück, ein int, das den Typ für fn bestimmt.



In der Funktion main() gibt es die Anweisung:

Autovariab=fn(2);

Das bedeutet, dass fn außerhalb von main() als Bezeichner für eine Funktion endet. Seine impliziten Parameter sind die des Lambda-Ausdrucks. Der Typ für variab ist auto.

Beachten Sie, dass der Lambda-Ausdruck mit einem Semikolon endet, genau wie die Klassen- oder Strukturdefinition mit einem Semikolon endet.

Im folgenden Programm ist eine Funktion, bei der es sich um einen Lambda-Ausdruck handelt, der den Wert 5 zurückgibt, ein Argument für eine andere Funktion:

#enthalten

mit Namensraumstd;

Leereanderefn(intnein1,int (*ptr)(int))

{

intnein2= (*ptr)(2);

Kosten <<nein1<< '' <<nein2<< ' ';

}


inthauptsächlich()

{

anderefn(4,[](inthalt)

{

intAntworten=halt+ 3;

RückkehrAntworten;

});


Rückkehr 0;
}

Die Ausgabe ist:

Vier fünf

Hier gibt es zwei Funktionen, den Lambda-Ausdruck und die otherfn()-Funktion. Der Lambda-Ausdruck ist das zweite Argument von otherfn(), das in main() aufgerufen wird. Beachten Sie, dass die Lambda-Funktion (Ausdruck) in diesem Aufruf nicht mit einem Semikolon endet, da es sich hier um ein Argument (keine eigenständige Funktion) handelt.

Der Lambda-Funktionsparameter in der Definition der otherfn()-Funktion ist ein Zeiger auf eine Funktion. Der Zeiger hat den Namen ptr. Der Name ptr wird in der otherfn()-Definition verwendet, um die Lambda-Funktion aufzurufen.

Die Aussage,

intnein2= (*ptr)(2);

In der otherfn()-Definition ruft es die Lambda-Funktion mit einem Argument von 2 auf. Der Rückgabewert des Aufrufs, '(*ptr)(2)' von der Lambda-Funktion, wird no2 zugewiesen.

Das obige Programm zeigt auch, wie die Lambda-Funktion im C++-Callback-Funktionsschema verwendet werden kann.

Teile des Lambda-Ausdrucks

Die Teile einer typischen Lambda-Funktion sind wie folgt:

[] () {}
  • [] ist die Capture-Klausel. Es kann Elemente enthalten.
  • () ist für die Parameterliste.
  • {} ist für den Funktionsrumpf. Steht die Funktion allein, sollte sie mit einem Semikolon enden.

Aufnahmen

Die Lambda-Funktionsdefinition kann einer Variablen zugewiesen oder als Argument für einen anderen Funktionsaufruf verwendet werden. Die Definition für einen solchen Funktionsaufruf sollte als Parameter einen Zeiger auf eine Funktion haben, die der Lambda-Funktionsdefinition entspricht.

Die Lambda-Funktionsdefinition unterscheidet sich von der normalen Funktionsdefinition. Sie kann einer Variablen im globalen Gültigkeitsbereich zugewiesen werden; diese funktionszugeordnete-variable kann auch innerhalb einer anderen Funktion codiert werden. Wenn er einer globalen Gültigkeitsbereichsvariablen zugewiesen wird, kann sein Hauptteil andere Variablen im globalen Gültigkeitsbereich sehen. Wenn sie einer Variablen innerhalb einer normalen Funktionsdefinition zugewiesen wird, kann ihr Hauptteil andere Variablen im Funktionsumfang nur mit Hilfe der Capture-Klausel [] sehen.

Die Capture-Klausel [], auch als Lambda-Introducer bekannt, ermöglicht das Senden von Variablen aus dem umgebenden (Funktions-)Bereich in den Funktionsrumpf des Lambda-Ausdrucks. Der Funktionsrumpf des Lambda-Ausdrucks soll die Variable erfassen, wenn er das Objekt empfängt. Ohne die Capture-Klausel [] kann eine Variable nicht aus dem umgebenden Gültigkeitsbereich in den Funktionsrumpf des Lambda-Ausdrucks gesendet werden. Das folgende Programm veranschaulicht dies mit dem main()-Funktionsbereich als umgebenden Bereich:

#enthalten

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5;


Autofn= [Ich würde]()

{

Kosten <<Ich würde<< ' ';

};

fn();


Rückkehr 0;

}

Die Ausgabe ist 5 . Ohne den Namen id innerhalb von [] hätte der Lambda-Ausdruck die Variable id des main()-Funktionsbereichs nicht gesehen.

Aufnahme nach Referenz

Das obige Beispiel für die Verwendung der Capture-Klausel ist die Erfassung nach Wert (siehe Details unten). Bei der Erfassung durch Referenz wird die Position (Speicherung) der Variablen, z. B. id oben, des umgebenden Gültigkeitsbereichs innerhalb des Lambda-Funktionskörpers verfügbar gemacht. Wenn Sie also den Wert der Variablen innerhalb des Lambda-Funktionskörpers ändern, ändert sich der Wert derselben Variablen im umgebenden Bereich. Um dies zu erreichen, wird jeder Variablen, die in der Capture-Klausel wiederholt wird, das kaufmännische Und (&) vorangestellt. Das folgende Programm veranschaulicht dies:

#enthalten

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU';

Autofn= [&Ich würde,&ft,&CH]()

{

Ich würde= 6;ft= 3.4;CH= 'B';

};

fn();

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ' ';

Rückkehr 0;

}

Die Ausgabe ist:

6, 3.4, B

Bestätigen, dass die Variablennamen im Funktionsrumpf des Lambda-Ausdrucks für dieselben Variablen außerhalb des Lambda-Ausdrucks gelten.

Erfassung nach Wert

Beim Erfassen nach Wert wird eine Kopie der Position der Variablen des umgebenden Gültigkeitsbereichs innerhalb des Lambda-Funktionskörpers verfügbar gemacht. Obwohl die Variable im Rumpf der Lambda-Funktion eine Kopie ist, kann ihr Wert derzeit nicht innerhalb des Rumpfs geändert werden. Um eine Erfassung nach Wert zu erreichen, wird jeder Variablen, die in der Capture-Klausel wiederholt wird, nichts vorangestellt. Das folgende Programm veranschaulicht dies:

#enthalten

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU';

Autofn= [id, ft, ch]()

{

//id = 6; ft = 3,4; ch = 'B';

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ' ';

};

fn();

Ich würde= 6;ft= 3.4;CH= 'B';

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ' ';

Rückkehr 0;

}

Die Ausgabe ist:

5, 2.3, A

6, 3.4, B

Wenn der Kommentarindikator entfernt wird, wird das Programm nicht kompiliert. Der Compiler gibt eine Fehlermeldung aus, dass die Variablen in der Definition des Lambda-Ausdrucks des Funktionskörpers nicht geändert werden können. Obwohl die Variablen innerhalb der Lambda-Funktion nicht geändert werden können, können sie außerhalb der Lambda-Funktion geändert werden, wie die Ausgabe des obigen Programms zeigt.

Mischen von Aufnahmen

Erfassung nach Referenz und Erfassung nach Wert können gemischt werden, wie das folgende Programm zeigt:

#enthalten

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU'; boolbl= wahr;


Autofn= [id, ft,&CH,&bl]()

{

CH= 'B';bl= falsch;

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' ';

};

fn();


Rückkehr 0;

}

Die Ausgabe ist:

5, 2.3, B, 0

Wenn alle erfasst sind, sind dies als Referenz:

Wenn alle zu erfassenden Variablen per Referenz erfasst werden, reicht nur ein & in der Erfassungsklausel aus. Das folgende Programm veranschaulicht dies:

#enthalten

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU'; boolbl= wahr;


Autofn= [&]()

{

Ich würde= 6;ft= 3.4;CH= 'B';bl= falsch;

};

fn();

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' ';


Rückkehr 0;

}

Die Ausgabe ist:

6, 3.4, B, 0

Wenn einige Variablen per Referenz und andere per Wert erfasst werden sollen, stellt ein & alle Referenzen dar, und den restlichen wird jeweils nichts vorangestellt, wie das folgende Programm zeigt:

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU'; boolbl= wahr;


Autofn= [&, id, ft]()

{

CH= 'B';bl= falsch;

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' ';

};

fn();


Rückkehr 0;

}

Die Ausgabe ist:

5, 2.3, B, 0

Beachten Sie, dass & allein (d. h. & nicht gefolgt von einem Bezeichner) das erste Zeichen in der Capture-Klausel sein muss.

Wenn alle erfasst sind, sind sie nach Wert:

Sollen alle zu erfassenden Variablen nach Wert erfasst werden, genügt ein = in der Erfassungsklausel. Das folgende Programm veranschaulicht dies:

#enthalten

mit Namensraumstd;

inthauptsächlich()
{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU'; boolbl= wahr;


Autofn= [=]()

{

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' ';

};

fn();


Rückkehr 0;


}

Die Ausgabe ist:

5, 2.3, A, 1

Notiz : = ist ab sofort schreibgeschützt.

Wenn einige Variablen nach Werten und andere nach Referenzen erfasst werden sollen, stellt ein = alle schreibgeschützten kopierten Variablen dar, und der Rest hat jeweils &, wie das folgende Programm zeigt:

#enthalten

mit Namensraumstd;

inthauptsächlich()

{

intIch würde= 5; schwebenft= 2.3; verkohlenCH= 'ZU'; boolbl= wahr;


Autofn= [=,&CH,&bl]()

{

CH= 'B';bl= falsch;

Kosten <<Ich würde<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' ';

};

fn();


Rückkehr 0;

}

Die Ausgabe ist:

5, 2.3, B, 0

Beachten Sie, dass = allein das erste Zeichen in der Capture-Klausel sein muss.

Klassisches Callback-Funktionsschema mit Lambda-Ausdruck

Das folgende Programm zeigt, wie ein klassisches Callback-Funktionsschema mit dem Lambda-Ausdruck erstellt werden kann:

#enthalten

mit Namensraumstd;

verkohlen *Ausgang;


Autocba= [](verkohlenaus[])

{

Ausgang=aus;

};



LeeremainFunc(verkohlenEingang[],Leere (*zum)(verkohlen[]))

{

(*zum)(Eingang);

Kosten<<'für Hauptfunktion'<<' ';

}


Leerefn()

{

Kosten<<'Jetzt'<<' ';

}


inthauptsächlich()

{

verkohlenEingang[] = 'für Rückruffunktion';

mainFunc(Eingabe, cba);

fn();

Kosten<<Ausgang<<' ';



Rückkehr 0;

}

Die Ausgabe ist:

für Hauptfunktion

Jetzt

für Rückruffunktion

Denken Sie daran, dass, wenn eine Lambda-Ausdrucksdefinition einer Variablen im globalen Gültigkeitsbereich zugewiesen wird, deren Funktionskörper globale Variablen sehen kann, ohne die Capture-Klausel zu verwenden.

Der Trailing-Return-Typ

Der Rückgabetyp eines Lambda-Ausdrucks ist auto, d. h. der Compiler bestimmt den Rückgabetyp aus dem Rückgabeausdruck (sofern vorhanden). Wenn der Programmierer wirklich den Rückgabetyp angeben möchte, wird er dies wie im folgenden Programm tun:

#enthalten

mit Namensraumstd;

Autofn= [](inthalt) -> int

{

intAntworten=halt+ 3;

RückkehrAntworten;

};


inthauptsächlich()

{

Autovariab=fn(2);

Kosten <<variab<< ' ';


Rückkehr 0;

}

Die Ausgabe ist 5. Nach der Parameterliste wird der Pfeiloperator eingegeben. Darauf folgt der Rückgabetyp (in diesem Fall int).

Schließung

Betrachten Sie das folgende Codesegment:

strukturierenCla

{

intIch würde= 5;

verkohlenCH= 'zu';

}obj1, obj2;

Cla ist hier der Name der Strukturklasse. Obj1 und obj2 sind zwei Objekte, die aus der struct-Klasse instanziiert werden. Der Lambda-Ausdruck ist in der Implementierung ähnlich. Die Lambda-Funktionsdefinition ist eine Art Klasse. Wenn die Lambda-Funktion aufgerufen (aufgerufen) wird, wird ein Objekt aus seiner Definition instanziiert. Dieses Objekt wird als Closure bezeichnet. Es ist die Schließung, die die Arbeit erledigt, die das Lambda leisten soll.

Beim Codieren des Lambda-Ausdrucks wie in der obigen Struktur werden jedoch obj1 und obj2 durch die entsprechenden Parameterargumente ersetzt. Das folgende Programm veranschaulicht dies:

#enthalten

mit Namensraumstd;

Autofn= [](intParameter1,intparam2)

{

intAntworten=param1+param2;

RückkehrAntworten;

} (2,3);


inthauptsächlich()

{

Autowo=fn;

Kosten <<wo<< ' ';


Rückkehr 0;

}

Die Ausgabe ist 5. Die Argumente sind 2 und 3 in Klammern. Beachten Sie, dass der Funktionsaufruf des Lambda-Ausdrucks, fn, kein Argument akzeptiert, da die Argumente bereits am Ende der Lambda-Funktionsdefinition codiert wurden.

Abschluss

Der Lambda-Ausdruck ist eine anonyme Funktion. Es besteht aus zwei Teilen: Klasse und Objekt. Seine Definition ist eine Art Klasse. Beim Aufruf des Ausdrucks wird aus der Definition ein Objekt gebildet. Dieses Objekt wird als Closure bezeichnet. Es ist die Schließung, die die Arbeit erledigt, die das Lambda leisten soll.

Damit der Lambda-Ausdruck eine Variable aus einem äußeren Funktionsbereich empfängt, benötigt er eine nicht leere Capture-Klausel in seinem Funktionsrumpf.