Im vorherigen Artikel haben wir uns mit der Struktur von Lambda-Funktionen vertraut gemacht, und jetzt werden wir mit Lambdas spielen, die Fakultät berechnen und überlegen, wie eine Lambda-Funktion sein kann dafür verwendet.
Betrachten wir zunächst die übliche Version der Fakultätsrechnung und klären auch, was eine rekursive Funktion ist.
Rekursive Funktion
Eine rekursive Funktion ist eine, die sich selbst aufruft. Das bedeutet, dass es innerhalb der Funktion einen Aufruf an sich selbst gibt und eine unendliche Rekursion stattfinden kann, wenn der Funktionscode keine Bedingungen zum Verlassen der Rekursion enthält.
Hier ist ein Beispiel für eine solche unendlich rekursive Funktion, deren Programm aufgrund eines Aufrufstapelüberlaufs abnormal beendet wird.
#include <iostream> using namespace std; void infiniteRecursiveFunction() { cout << "Hello World!" << endl; infiniteRecursiveFunction(); } int main() { infiniteRecursiveFunction(); return 0; }
Um dies zu verhindern, müssen Sie eine Bedingung zum Beenden der rekursiven Funktion hinzufügen, z. B. das Erreichen des Zählers für rekursive Aufrufe auf 100.
#include <iostream> using namespace std; void infiniteRecursiveFunction(int counter = 0) { cout << "Hello World!" << endl; if (counter == 100) { return; } infiniteRecursiveFunction(counter + 1); } int main() { infiniteRecursiveFunction(); return 0; }
Jetzt wird die rekursive Funktion dank des Zählers korrekt abgeschlossen, der Aufrufstapel wird nicht überlaufen und das Programm wird ohne Fehler beendet.
Fakultät
Lassen Sie uns nun definieren, was eine Fakultät ist.
Fakultät
ist das Produkt natürlicher Zahlen von 1 bis zur Zahl selbst (einschließlich der gegebenen Zahl).
Die Fakultät wird durch ein Ausrufezeichen „!“ gekennzeichnet.
Beispiele:
- 4! = 1 · 2 · 3 · 4 = 24
- 5! = 1 · 2 · 3 · 4 · 5 = 120
Lassen Sie uns nun eine Funktion schreiben, um die Fakultät zu berechnen
long double fact(int N) { if(N < 0) // если пользователь ввел отрицательное число { return 0; // возвращаем ноль } else if (N == 0) // если пользователь ввел ноль, { return 1; // возвращаем факториал от нуля, который равен 1 } else // во всех остальных случаях { return N * fact(N - 1); // выполняем рекурсивный вызовы функции } }
In diesem Fall wird die Funktion so geschrieben, dass die Fakultätsrechnung von der größten Zahl aus durchgeführt wird und bei Null endet. Dann wird der korrekte Ausstieg aus der Rekursion durchgeführt und die Funktion gibt den Wert der Fakultät zurück.
Als Ergebnis sieht der Programmcode zur Berechnung der Fakultät wie folgt aus
#include <iostream> using namespace std; long double fact(int N) { if(N < 0) // если пользователь ввел отрицательное число { return 0; // возвращаем ноль } else if (N == 0) // если пользователь ввел ноль, { return 1; // возвращаем факториал от нуля, который равен 1 } else // во всех остальных случаях { return N * fact(N - 1); // выполняем рекурсивный вызовы функции } } int main() { int N {0}; cout << "Input number for factorial" << endl; cin >> N; cout << "Factorial for number " << N << " = " << fact(N) << endl; // fact(N) - функция для вычисления факториала. return 0; }
Anwenden einer rekursiven Lambda-Funktion
Und jetzt verwenden wir eine rekursive Lambda-Funktion, um die Fakultät zu berechnen.
In modernen C++-Standards gibt es zwei Möglichkeiten, rekursive Funktionen zu schreiben:
- Mit std::function
- Ohne Verwendung von std::function
Verwenden von std::function
In diesem Fall muss die Lambda-Funktion, um Rekursion zu verwenden, ihre eigene Struktur kennen, um sich selbst per Referenz erfassen zu können, aber die Lambda-Funktion ist eine anonyme Objektdeklaration, die irgendwie explizit gemacht werden muss. Dabei hilft uns die std::function, die dabei hilft, die Signatur der Lambda-Funktion zu bestimmen.
#include <iostream> #include <functional> // Подключаем библиотеку для использования std::function using namespace std; int main() { int N {0}; // Объявление сигнатуры через std::function -> std::function<int(int)> // Сигнатура функции int (int) // [&fact] - Захват лямбды самой себя std::function<int(int)> fact = [&fact](int N) { if(N < 0) // если пользователь ввел отрицательное число { return 0; // возвращаем ноль } else if (N == 0) // если пользователь ввел ноль, { return 1; // возвращаем факториал от нуля, который равен 1 } else // во всех остальных случаях { return N * fact(N - 1); // выполняем рекурсивный вызовы функции } }; cout << "Input number for factorial" << endl; cin >> N; cout << "Factorial for number " << N << " = " << fact(N) << endl; // fact(N) - функция для вычисления факториала. return 0; }
Die Einschränkung rekursiver Lambdas besteht darin, dass wir eine Lambda-Funktion erst erfassen können, wenn ihre Signatur bekannt ist. Das heißt, die sofortige Verwendung von auto funktioniert nicht, da auto die Lambda-Struktur zur Kompilierzeit ableitet und wenn das Lambda-Objekt nicht gebildet wurde, können wir das Lambda nicht erfassen, und da es erfasst wird sich selbst, aber noch nicht kompiliert wurde, dann weiß die Struktur nichts und kann sich daher nicht selbst erfassen.
Dann kommt std::function zur Rettung, mit der Sie die Signatur einer Lambda-Funktion vordefinieren, von dieser Lambda-Funktion initialisiert und von derselben Lambda-Funktion als erfasste Referenz verwendet werden können.
Ohne Verwendung von std::function
Das heißt aber nicht, dass Sie bei rekursiven Funktionen auf die explizite Verwendung von std::function nicht verzichten können. Im C++14 -Standard wurde es möglich, die Argumente von Lambda-Funktionen als auto zu definieren, wodurch die Lambda-Funktion per Referenz als Argument an sich selbst übergeben werden kann. Sie erhalten dieselbe rekursive Lambda-Funktion, verwenden jedoch nur die Tools der Programmiersprache C++.
#include <iostream> using namespace std; int main() { int N {0}; // Объявление лямбды через auto // Сигнатура лямбды int (auto&, int) // auto& self - в данный аргументы будет передаваться лямбды функция для выполнения самой себя auto fact = [](auto& self, int N) { if(N < 0) // если пользователь ввел отрицательное число { return 0; // возвращаем ноль } else if (N == 0) // если пользователь ввел ноль, { return 1; // возвращаем факториал от нуля, который равен 1 } else // во всех остальных случаях { // Вызываем лямбду передавая в качестве аргумента лмябду по ссылке дальше в саму себя return N * self(self, N - 1); // выполняем рекурсивный вызовы функции } }; cout << "Input number for factorial" << endl; cin >> N; // При первом вызове лямбды также нужно передать в качестве аргумента лямбду в саму себя // fact(fact, N) cout << "Factorial for number " << N << " = " << fact(fact, N) << endl; // fact(N) - функция для вычисления факториала. return 0; }
Fazit
Zur Frage nach der Zweckmäßigkeit der Verwendung rekursiver Lambda-Funktionen.
Es macht keinen Sinn, eine Funktion oder Methode in einer Klasse zu deklarieren, wenn diese Funktion an einer einzigen Stelle im Code verwendet wird. Dies wird zumindest die Klassenschnittstelle verkomplizieren, wenn wir für jedes Niesen neue Methoden schreiben. Es ist viel besser, das Lambda in einer anderen Methode zu deklarieren, wo es ausgeführt werden soll. Dies gilt für alle Lambda-Funktionen, sowohl regulär als auch rekursiv.
Es ist nicht mehr möglich, eine Lambda-Funktion zu deklarieren und sofort auszuführen. Wenn eine Lambda-Funktion deklariert und sofort aufgerufen wird, ist der Rückgabewert das Ergebnis der Ausführung der Lambda-Funktion und nicht das Lambda-Funktionsobjekt selbst, was bedeutet, dass es nicht funktioniert, das Objekt der Lambda-Funktion zu erfassen, was bedeutet, dass Beide der oben genannten Arten, rekursive Lambda-Funktionen zu schreiben, sind nicht geeignet.