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

Заключение

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

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

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

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
15 июля 2018 г. 20:20
igorpodoba

C++ - Тест 003. Условия и циклы

  • Результат 92баллов,
  • Очки рейтинга8
15 июля 2018 г. 20:17
igorpodoba

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

  • Результат 91баллов,
  • Очки рейтинга8
14 июля 2018 г. 7:47
igorpodoba

C++ - Тест 003. Условия и циклы

  • Результат 71баллов,
  • Очки рейтинга1
Последние комментарии
14 июля 2018 г. 18:49
Евгений Легоцкой

Qt/C++ - Урок 049. QTranslator - динамический перевод мультиязычного приложения на Qt

У меня на руках есть один проект, где какие-то потуги с переводами и подключением этого добра в CMAKE делались.Но там файл перевода добавляется прямо в ресурсы проекта. То есть бинарных qm файл...
14 июля 2018 г. 18:35
Евгений Легоцкой

Django - Урок 036. Как добавить аутентификацию через социальные сети. ВКонтакте

Не, не будет, в данной батарейке уже есть необходимый функционал по разрулированию этой проблемы. Аутентификации из разных социальных сетей будут сливаться на один аккаунт. Так что всё нормаль...
14 июля 2018 г. 4:17
Gerych

Django - Урок 036. Как добавить аутентификацию через социальные сети. ВКонтакте

Мне интересно что будет если в обеих сетях в авторизации одинаковый еmail. Не выведет ли ошибку ?
13 июля 2018 г. 11:55
Arrow

Qt/C++ - Урок 049. QTranslator - динамический перевод мультиязычного приложения на Qt

Хорошая статья. Только один вопрос как это сделать для CMake? Интересует именно запись в CMakeList TRANSLATIONS += QtLanguage_ru.ts CODECFORSRC = UTF-8 П...
Сейчас обсуждают на форуме
14 июля 2018 г. 18:56
Евгений Легоцкой

Как сделать пустое поле в QDateEdit

Слишком много возни, чтобы подробно объяснить, что нужно сделать.... тем более, что у вас ещё зависимость на базу данных... Для начала нужно наследоваться от QCalendarWidget, посколь...
12 июля 2018 г. 15:02
незнаток

Перенос значений таблицы в другую таблицу

void Opisanie::perevod(){ QString mil; int mf = ui->table1->rowCount(); for(int ik = 0; ik < mf; ik++) { QString tu = ui->table1->model()->data(ui...
12 июля 2018 г. 7:46
Евгений Легоцкой

OpenSSL на Windows10

Совсем забыл. Вот в этом посте есть ссылка на скачивание openssl библиотек для msvc-2015
11 июля 2018 г. 16:05
Ruslan Polupan

Наследование от QLineEdit

Из опыта разработки в нашей конторе (для программирование хобби я техподдержка): Если есть возможность переложить логику приложения на базу данных то это лутший вариант. Т.е. использовать по м...

Рекомендуемые страницы