Политика конфиденциальностиКонтактыО сайтеОтзывыGitHubDonate
© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

Готовим лямбда функции в C++ - Часть 2 - Рекурсивные лямбда функции на примере вычисления факториала

factorial, lambda, C++, лямбда функция, факториал

В предыдущей статье мы ознакомились со структурой лямбда функций, а теперь поиграем с лямбдами, вычислим факториал, и рассмотрим, как для этого можно применить лямбда функцию.

Рассмотрим для начала обычный вариант вычисления факториала, а также уточним, что такое рекурсивная функция.

Рекурсивная функция

Рекурсивной функцией является та функция, которая вызывает саму себя. Это означает, что внутри функции присутствует вызов самой себя и может случиться бесконечная рекурсия, если в коде функции отсутствуют условия по выходу из рекурсии.

Вот пример такой бесконечной рекурсивной функции, программа с которой завершается аварийно из-за переполнения стека вызовов.

#include <iostream>

using namespace std;

void infiniteRecursiveFunction()
{
    cout << "Hello World!" << endl;
    infiniteRecursiveFunction();
}

int main()
{
    infiniteRecursiveFunction();
    return 0;
}

Чтобы такого не происходило, нужно добавить условие выхода из рекурсивной функции, например, достижение счётчика рекурсивных вызовов значения 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;
}

Теперь рекурсивная функция будет завершаться корректно, благодаря счётчику стек вызовов не будет переполняться и программа завершиться без ошибок.

Факториал

А теперь определимся с тем, что такое факториал.

Факториал - это произведение натуральных чисел от 1 до самого числа (включая данное число).
Обозначается факториал восклицательным знаком «!».

Примеры:

  • 4! = 1 · 2 · 3 · 4 = 24
  • 5! = 1 · 2 · 3 · 4 · 5 = 120

Теперь напишем функцию для вычисления факториала

long double fact(int N)
{
    if(N < 0) // если пользователь ввел отрицательное число
    {
        return 0; // возвращаем ноль
    }
    else if (N == 0) // если пользователь ввел ноль,
    {
        return 1; // возвращаем факториал от нуля, который равен 1
    }
    else // во всех остальных случаях
    {
        return N * fact(N - 1); // выполняем рекурсивный вызовы функции
    }
}

В данном случае функция написана так, чтобы вычисление факториала выполнялось с наибольшего числа и заканчивалось нулём. Тогда будет выполняться корректный выход из рекурсии и функция будет возвращать значение факториала.

В результате код программы для вычисления факториала будет выглядеть так

#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;
}

Применение рекурсивной лямбда функции

А теперь применим для вычисления факториала рекурсивную лямбда функцию.

В современных стандартах C++ есть два варианта записи рекурсивных функций:

  • С применением std::function
  • Без применения std::function

С применением std::function

В данном случае для применения рекурсии лямбда функция должна знать о своей собственной структуре, чтобы быть способной захватить саму себя по ссылке, но лямбда функция - это анонимное объявление объекта, которое каким-то образом нужно сделать явным. В этом нам поможет std::function, который поможет определить сигнатуру лямбда функции.

#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;
}

Ограничение рекурсивных лямбд заключается в том, что мы не можем захватить лямбда функцию до тех пор, пока неизвестна её сигнатура. То есть применить auto сходу не получится, поскольку auto делает вывод структуры лямбды во время компиляции и если объект лямбды не был сформирован, то и захватить лямбду мы не можем, а поскольку она захватывает саму себя, но при этом ещё не была скомпилирована, то и структуре ничего не знает, а значит и захватить себя не может.

Тогда и приходит на помощь std::function, который позволяет предопределить сигнатуру лямбда функции, инициализироваться данной лямбда функцией и использоваться в качестве захватываемой ссылки этой же самой лямбда функцией.

Без применения std::function

Но это не значит, что вовсе нельзя обойтись без явного использования std::function для рекурсивных функций. В стандарте C++14 появилась возможность определять аргументы лямбда функций как auto, за счёт чего лямбда функцию можно передавать в качестве аргумента самой себе по ссылке. Получится та же самая рекурсивная лямбда функция, но с использованием исключительно средств языка программирования 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;
}

Заключение

К вопросу о целесообразности применения рекурсивных лямбда функций.

Не имеет смысла объявлять функцию или метод в классе, если эта функция используется в одном единственном месте кода. Это как минимум будет усложнять интерфейс класса, если на каждый чих будем писать новые методы. Гораздо лучше объявить лямбду внутри другого метода, где она должна выполняться. Это касается всех лямбда функций, как обычных, так и рекурсивных.

Объявить и сразу выполнить лямбда функцию уже не получится. Когда лямбда функция объявляется и сразу вызывается, то в качестве возвращаемого значения будет результат выполнения лямбда функции, а не сам объект лямбда функции, что означает, что захватить объект лямбда функцию не получится, а значит оба представленных выше способа написания рекурсивных лямбда функций не подходят.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
КА
19 февраля 2019 г. 18:32
Кристина Афанасьева

C++ - Тест 006. Перечисления

  • Результат:70баллов,
  • Очки рейтинга1
КА
19 февраля 2019 г. 18:26
Кристина Афанасьева

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60баллов,
  • Очки рейтинга-1
КА
19 февраля 2019 г. 18:00
Кристина Афанасьева

C++ - Тест 002. Константы

  • Результат:50баллов,
  • Очки рейтинга-4
Последние комментарии
21 февраля 2019 г. 12:51
Евгений Легоцкой

Иногда CMake приходится перезапускать начисто, не обновляет кэш
R
21 февраля 2019 г. 12:29
RandyGallup

Я указал данные строки, т.к. без них у меня вылетала следующая ошибка: By not providing "FindQt5Core.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configurat...
21 февраля 2019 г. 12:08
BlinCT

Вот атк выглядит мой проектник, посмотрите его. cmake_minimum_required(VERSION 3.6)project(projecttimer)set(CMAKE_CXX_STANDARD 11)set(CMAKE_AUTOMOC ON)set(CMAKE_AUTORCC ON)find_packa...
21 февраля 2019 г. 12:04
BlinCT

Смотрите, если вы используете глобально для проекта -DCMAKE_PREFIX_PATH= то вам не надо уже указывать вот эти строкиset(Qt5Core_DIR "C:/Qt/5.12.1/mingw73_64/lib/cmake/Qt5Core")set(Qt5Gui_DIR...
R
21 февраля 2019 г. 11:54
RandyGallup

Даже не запускается. main.cpp у меня точно такой же, как в статье. CMakeLists.txt пришлось немного подправить (прикрепил ниже), т.к. не находились некоторые файлы. cmake_minimum_requi...
Сейчас обсуждают на форуме
21 февраля 2019 г. 8:58
Евгений Легоцкой

Ну у меня координаты передавались в зависимости от положения курсора мыши, а в вам по сути нужно будет аналогичным способом посылать даннные из полей ввода. Так что здесь скорее интерфес...
20 февраля 2019 г. 21:55
Евгений Легоцкой

Не до конца понимаю сути вопроса, наверное, нужно увидеть программный код и попытку его применения, но к методам базового класса можно обращаться в наследованном классе через вызов по имени ба...
MU
20 февраля 2019 г. 15:06
Maciej Urmański

Yes, ok I have solution! Thank you for directing me about annotate.:) Solution is: users_in = User.objects.filter(joined_users__goal=goal, joined_users__joined=True)
20 февраля 2019 г. 14:40
Евгений Легоцкой

Думаю, что ещё можно переопределить mouseReleaseEvent(QMouseEvent* event) у QTableView, который содержит модель и немного поиграться с индексом. Если это индекс, который соответству...
20 февраля 2019 г. 10:34
Евгений Легоцкой

Да, так тоже можно. Единственный момент в том, что lupdate не всегда понимает, к какому контексту это дело относится, и может запихать в левый контекст. В небольшом проекте это не критич...
Присоединяйтесь к нам в социальных сетях

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы