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

Заключение

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

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

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

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
24 сентября 2018 г. 17:42
edorofeeva

C++ - Тест 001. Первая программа и типы данных

  • Результат 100баллов,
  • Очки рейтинга10
24 сентября 2018 г. 17:37
edorofeeva

C++ - Тест 001. Первая программа и типы данных

  • Результат 66баллов,
  • Очки рейтинга-1
23 сентября 2018 г. 14:38
No Names

C++ - Тест 001. Первая программа и типы данных

  • Результат 60баллов,
  • Очки рейтинга-1
Последние комментарии
24 сентября 2018 г. 15:09
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

А вот здесь у меня есть пример использования supervisor. https://evileg.com/ru/post/3/ Вся статья вам там не интересна, интересен только шаг с настройкой supervisor. Он получается ...
24 сентября 2018 г. 15:00
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Не могли бы дать ссылку на пример? Какое-то рабочее использование. Т.е. у меня есть Qt Gui App, которое я бы хотел запускать при старте системы и в случае, если оно грохнется. Если о чем Вы го...
24 сентября 2018 г. 14:55
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Если честно, то я не уверен, что это вообще можно реализовать через *.desktop файл. Я сделал предположение на основе того, что вы сказали про *.desktop и рестарт. Все варианты, котор...
24 сентября 2018 г. 14:47
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Просто сейчас правлю сам файл example.desktop. Пытаюсь понять какую пару key=value мне нужно дописать.
24 сентября 2018 г. 14:42
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Ну я имел ввиду, что дописать в коде вот сюда то, о чём вы говорили про рестарт QString autorunContent("[Desktop Entry]\n" "Type=Application\n" ...
Сейчас обсуждают на форуме
24 сентября 2018 г. 16:47
Евгений_Канусовский@1981

Чтение файлов в python

Добрый вечер Евгений и форумчане! Столкнулся с проблемой чтения файлов в python: файлы с обычным текстом в формате las и txt читаются, например: ~Version information VERS.          ...
24 сентября 2018 г. 13:29
Евгений Легоцкой

Трансляция видео с помощью VLC по RTP

Добрый день! Я не сталкивался, но предположу, что нужно настроить Input Codec в VLC. В настройках есть секция Input Codec, возможно, что там установлено низкое разрешение. ...
21 сентября 2018 г. 8:25
Евгений Легоцкой

Прокси-модель, содержащая на 1 столбец больше, чем модель-источник.

Попробуйте ещё PySide 2 - это официально поддерживаемый пакет привязок Python к Qt, возможно, что там не будет таких проблем.
20 сентября 2018 г. 20:06
Евгений Легоцкой

Qt Installer Framework

Добрый день. Зачем собирать Qt Installer Framework-то из исходников? Я ещё понимаю Qt собирают из исходников статически (хотя тоже считаю по большей части бесполезной тратой времени),...
Присоединяйтесь к нам в социальных сетях