Mein Lieblingswerkzeug in C++ sind Lambda-Funktionen, obwohl mir gesagt wurde, dass sie beängstigend aussehen. Tatsächlich sind sie bezaubernd. Sie vereinfachen das Schreiben von Programmen erheblich und ermöglichen es Ihnen, einige ziemlich interessante Entscheidungen zu treffen.
Aber bevor Sie sich die verschiedenen Verwendungen von Lambda-Funktionen ansehen, schlage ich vor, dass Sie sich mit der grundlegenden Syntax von Lambda-Funktionen vertraut machen.
Mögliche Syntaxoptionen für Lambda-Funktionen
[ capture ] ( params ) mutable exception attribute -> ret { body } [ capture ] ( params ) -> ret { body } [ capture ] ( params ) { body } [ capture ] { body }
Die erste Option ist vollständig, aber es ist nicht verboten, abgekürzte Varianten der Schreibweise von Funktionen zu verwenden.
- capture - Liste externer erfasster Objekte, sie können sowohl durch Referenz als auch durch Kopieren erfasst werden.
- params - Liste von Parametern, die an Lambda-Funktionen übergeben werden. Dieser Teil ähnelt dem Schreiben von Argumenten für reguläre Funktionen.
- mutable - Mit mutable können Sie Kopien von Objekten ändern, die von der Kopie erfasst wurden. Normalerweise werden sie nicht geändert.
- Ausnahme – stellt eine Ausnahmespezifikation bereit, d. h. Lambda-Funktionen können genau wie normale Funktionen Ausnahmen auslösen.
- attribute - stellt eine Attributspezifikation bereit, es gibt nur zwei solcher Attribute in der C++-Spezifikation ([[noreturn]], [[carries_dependency]])
- params - Liste der an die Lambda-Funktion übergebenen Parameter
- ret - Rückgabewert der Lambda-Funktion
Der Rückgabewert kann automatisch aus dem Objekttyp abgeleitet werden, der von der return-Anweisung zurückgegeben wird. Wenn die Lambda-Funktion keine return-Anweisung enthält, ist der Rückgabewert ungültig.
Eine Lambda-Funktion erstellt ein unbenanntes temporäres Objekt eines eindeutigen, unbenannten, nicht gewerkschaftlichen, nicht aggregierten Typs, bekannt als closure type. Dank der Einführung des auto -Operators im modernen C++-Standard ist es möglich, a zu deklarieren Lambda-Funktionsobjekt ziemlich einfach, ohne eine Funktordeklaration ( * std::function * ) mit allen Parametern und Rückgabewerten zu schreiben, was den Code einfacher und lesbarer macht (natürlich für einen erfahrenen Programmierer. Natürlich Sie berücksichtigen, dass ein Anfänger schnell vermuten wird, dass etwas nicht stimmt, wenn std::function in der Lambda-Deklaration auftaucht, aber das ist Übungssache).
Hier ist ein Beispiel für die Deklaration einer einfachen Lambda-Funktion, die einen void -Typ zurückgibt, weil mindestens eine return -Anweisung fehlt.
#include <iostream> using namespace std; int main() { auto myLambda = []() { cout << "Hello World!" << endl; }; myLambda(); return 0; }
Dementsprechend wird der Programmcode nicht kompiliert, wenn es zwei oder mehr Rückgabeanweisungen in der Lambda-Funktion gibt, die Objekte unterschiedlicher Typen zurückgeben, die nicht durch eine Vererbungshierarchie verbunden sind und nicht in den Basisklassentyp umgewandelt werden können. Und selbst wenn diese Objekte eine Basisklasse haben, muss der Typ des Rückgabewerts angegeben werden, es ist nur ein Zeiger auf das Basisklassenobjekt (im allgemeinen Fall).
Hier ist ein Beispiel für Code, der nicht kompiliert werden kann.
#include <iostream> using namespace std; class A { public: A(){} }; class B : public A { public: B(){} }; class C : public A { public: C(){} }; int main() { auto myLambda = [](int type) { if (type == 0) { return new B(); } else { return new C(); } }; myLambda(0); return 0; }
Sie müssen den Rückgabetyp angeben
auto myLambda = [](int type) -> A* // Укажем тип возвращаемого значения { if (type == 0) { return new B(); } else { return new C(); } };
Außerdem tritt ein Kompilierungsfehler auf, wenn Sie den Typ des Rückgabewerts nicht angeben und gleichzeitig ein Objekt auf dem Heap innerhalb der Lambda-Funktion erstellen, aber in einigen Fällen können Sie einen Zeiger auf * nullptr. zurückgeben. * Das heißt, der folgende Code wird nicht kompiliert.
#include <iostream> using namespace std; class A { public: A(){} }; int main() { auto myLambda = [](int type) { if (type == 0) { return new A(); } else { return nullptr; } }; myLambda(0); return 0; }
Auch hier müssen Sie den Typ des Rückgabewerts angeben.
auto myLambda = [](int type) -> A* // Укажем тип возвращаемого значения { if (type == 0) { return new A(); } else { return nullptr; } };
Der Punkt ist, dass nullptr ein generischer Datentyp ist, der in gewissem Sinne kein Datentyp ist, da er nicht als Variablentyp festgelegt werden kann. Aber es kann einem Zeiger auf ein Objekt als Wert zugewiesen werden. Damit die implizite Konvertierung in diesem Fall korrekt funktioniert, müssen Sie auch den Typ des Rückgabewerts angeben.
Das obige Beispiel zeigt auch, wie man eine Lambda-Funktion aufruft und ihr Parameter übergibt. Bemerkte? In diesem Beispiel wird der Parameter int type verwendet, je nachdem, ob wir einen Zeiger auf das erstellte Objekt oder nullptr zurückgeben.
Auch in Lambda-Funktionen gibt es das Konzept der Erfassung von Variablen. Das bedeutet, dass die Lambda-Funktion nicht nur die ihr übergebenen Variablen als Parameter verwenden kann, sondern auch alle Objekte, die außerhalb der Lambda-Funktion deklariert wurden.
Eine Liste von Zeichen kann wie folgt übergeben werden:
**[a,&b]**
wobei a als Wert erfasst wird und b als Referenz erfasst wird.**[this]**
schnappt sich den**this**
-Zeiger nach Wert.**[&]**
erfasst alle Zeichen per Referenz**[=]**
erfasst alle Zeichen nach Wert**[]**
erfasst nichts
Wir werden in den folgenden Artikeln über das Erfassen von Variablen sprechen.
Aber ich werde einen interessanten Punkt anmerken, die Lambda-Funktion kann sofort dort aufgerufen werden, wo Sie sie deklariert haben, wenn Sie Klammern nach dem Hauptteil der Lambda-Funktion hinzufügen und alle erforderlichen Parameter übergeben, falls vorhanden.
Dieser Code wird beispielsweise auch kompiliert
#include <iostream> using namespace std; class A { public: A(){} }; int main() { A* myA = [](int type) -> A* { if (type == 0) { return new A(); } else { return nullptr; } }(0); return 0; }
Denken Sie also darüber nach, wird der folgende Code kompiliert?
int main(){[](){}();}