Медленно, но верно закрываем еще одну тему из нашей roadmap.

Начнем с простого примера. Допустим у нас есть массив v и нам понадобилось вывести все четные числа, т.е. если остаток от деления равен нулю.

int main(int argc, char *argv[])
{
    std::vector<int> v {1,2,3,4,5,6,7,9};
 
    for (int i = 0; i < v.size(); i++)
    {
        if (v[i] % 2 == 0)
        {
            std::cout << v[i] << endl;
        }
    }
    return 0;
}

Результат выполнения такой программы: 2, 4, 6

Можем ли мы как то улучшить данный код? Как вариант можно воспользоваться функцией из стандартной библиотеки std::all_of, которая перебирает все элементы, которые возвращают true. Утрировано синтаксис у нее такой:

std::all_of(первый элемент, последний элемент, что выполнить)

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

Итого у нас вырисовывается прототип функции, принимает один параметр на каждом шаге, в нашем случае это int и возвращает bool, в нашем случае это true. Перепишем код с учетом этих знаний.

bool is_even(int x)
{
    if (x % 2 == 0)
    {
        std::cout << x << endl;
    }
    return true;
}
 
int main(int argc, char *argv[])
{
    std::vector<int> v {1,2,3,4,5,6,7,9};
    std::all_of(begin(v), end(v), is_even);
 
    return 0;
}

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

Чтобы проще было понять, перепишем тот же код, но с помощью лямбда выражения.

int main(int argc, char *argv[])
{
    std::vector<int> v {1,2,3,4,5,6,7,9};
 
    auto is_even = [](int x) {
        if(x % 2 == 0)
        {
            std::cout << x << endl;
        }
        return true;
    };
 
    std::all_of(begin(v), end(v), is_even);
 
    return 0;
}

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

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

int main(int argc, char *argv[])
{
    std::vector<int> v {1,2,3,4,5,6,7,9};
    std::all_of(begin(v), end(v), [](int x) {
        if(x % 2 == 0) 
            std::cout << x << endl; 
        return true;
    });
    return 0;
}

Далее поговорим об еще нескольких деталях. Допустим наша задача, взять тот же вектор и умножить его на некоторое число. Для этого мы объявим его int val = 5. И тут важно осознавать, что лямбда функция ведет себя как отдельная, т.е. val это локальная переменная main и для функции она не может быть видимой. Чтобы исправить это мы указываем в квадратных скобках, что val требуется захватить, т.е. чтобы внутри lambda она стала видимой. С учетом этих знаний перепишем код, чтобы умножить каждый элемент массива на число.

int main(int argc, char *argv[])
{
    std::vector<int> v {1,2,3,4,5,6,7,9};
    int val = 5;
 
    std::all_of(begin(v), end(v), [val](int x) {
        x *= val;
        std::cout << x << endl;
        return true;
    });
    return 0;
}

Так как в функцию мы передаем int x, т.е. по значению то на экран выведутся числа умноженные на 5, но сам исходный массив не изменится. Если мы хотели бы изменить исходный массив, то должны были бы передавать значения по ссылке int &x.

И последнее что можно упомянуть, что захват можно делать не только по конкретному значению val, а можно захватить все переменные в области видимости, т.е. для захвата всех переменных по ссылке предыдущий код можно было бы переписать так:

std::all_of(begin(v), end(v), [&](int x) {
    x *= val;
    std::cout << x << endl;
    return true;
});

В этом случае внутри лямбды можно было бы изменять переменную val, если бы это потребовалось.

Для захвата по значению указываем [=], т.е. изменяя внутри функции val снаружи оно не изменится.

std::all_of(begin(v), end(v), [=](int x) {
    x *= val;
    std::cout << x << endl;
    return true;
});

Итого, могу сказать про себя лично, долгое время я отвергал использование лямбд, в первую очередь потому что код с ними читается чаще хуже.

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика