Evgenii Legotckoi
Evgenii LegotckoiШілде 6, 2018, 4:26 Т.Ж.

C++ тілінде ламбда функцияларын дайындау - 2 бөлім - Факторлық мысалды пайдалану арқылы рекурсивті ламбда функциялары

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

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

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

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

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

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

Заключение

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

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

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

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
m
  • molni99
  • Қаз. 26, 2024, 1:37 Т.Ж.

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

  • Нәтиже:80ұпай,
  • Бағалау ұпайлары4
m
  • molni99
  • Қаз. 26, 2024, 1:29 Т.Ж.

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

  • Нәтиже:20ұпай,
  • Бағалау ұпайлары-10

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

  • Нәтиже:42ұпай,
  • Бағалау ұпайлары-8
Соңғы пікірлер
i
innorwallҚар. 8, 2024, 3:29 Т.Қ.
EVILEG - 4 жыл онлайн buy priligy Pelleted food this way of administration is less stressful for the mice, and a few vendors offer a commercially available chow
i
innorwallҚар. 8, 2024, 8:51 Т.Ж.
Джанго - Сабақ 036. Әлеуметтік желілер арқылы аутентификацияны қалай қосуға болады. Байланыста buy priligy online usa In addition, it might not be effective at the doses recommended, because of your previous tolerance to a similar type of drug
i
innorwallҚар. 8, 2024, 7:40 Т.Ж.
Qt/C++ - 039-сабақ. QSqlTableModel жүйесінде жолды бағандағы мән бойынша бояу әдісі priligy results This slowing of eGFR decline was observed in patients with and without low eGFR and in those with and without type 2 diabetes
i
innorwallҚар. 8, 2024, 2:45 Т.Ж.
QML - Урок 002. QML Android жүйесіндегі пайдаланушы түймесі 2007; 14 2 270 83 priligy dapoxetine 60mg Testicular imaging is sort of a unique niche right now, Гў
Енді форумда талқылаңыз
i
innorwallҚар. 8, 2024, 7:08 Т.Ж.
добавить qlineseries в функции School of Nursing, Long Island University, Brooklyn Campus, Brooklyn, NY, USA priligy dapoxetine 30mg
9
9AnonimҚаз. 25, 2024, 9:10 Т.Ж.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Бізді әлеуметтік желілерде бақылаңыз