Практический курс по C/C++: https://stepik.org/course/193691
В стандарте
компилятора С++11 появились, так называемые, лямбда-функции (еще говорят
лямбда-выражения или анонимные функции). Что это за функции и зачем они нужны? Если
очень кратко, то лямбда-выражение позволяет создавать простой объект-функцию в
любом допустимом месте программы. И одно из таких допустимых мест – аргумент обычной
функции. Давайте я все детально поясню на конкретных примерах. А начнем,
конечно же, со способа объявления и вызова таких лямбда-функций.
Общий синтаксис
их определения следующий:
[] ([параметры]) { <операторы тела
функции>}
Обратите
внимание, что здесь отсутствует имя функции. Поэтому их иногда и называют
анонимными. Как мы далее увидим, это имя здесь и не нужно.
В самом простом
варианте лямбда-выражение можно записать в виде:
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
int main()
{
[](int a) {
cout << "Lambda-function: " << a << endl;
};
return 0;
}
Здесь в круглых
скобках указан один параметр, а в фигурных – один оператор с выводом строки в
выходной поток.
Если сейчас
выполнить программу, то она успешно скомпилируется, но в консоль ничего
выведено не будет. Почему так произошло? Да, по той причине, что мы объявили
функцию, но не вызвали ее. Но как вызывать функцию, у которой нет имени? В
самом простом варианте можно добавить вызов сразу после ее определения,
например, так:
[](int a) {
cout << "Lambda-function: " << a << endl;
} (10);
Но смысла от
такой операции большого не будет. Поэтому на практике чаще всего созданный
объект-функцию присваивают какой-либо переменной. Мы сделаем это следующим
образом:
auto r = [](int a) {
cout << "Lambda-function: " << a << endl;
};
r(10);
Смотрите, как
удобно здесь использовать вычисляемый тип для переменной r, вместо явного
указания типа. В результате, переменная r представляет
собой лямбда-выражение. Обратите внимание, r не ссылается на
лямбда-функцию, а само является этой функцией, точнее, объектом-функцией. Мало
того, далее, мы можем сделать и так:
При этом s будет уже
другим объектом-функцией. В момент инициализации происходит копирование одного
объекта в другой.
Или, эту же
инициализацию можно прописать в виде:
Или же, явно
прописать еще одно лямбда-выражение:
auto s { [](const char* msg, double& x) {
cout << msg << endl;
x++;
} };
double b = 0;
s("increment", b);
Конечно,
лямбда-выражение может возвращать произвольные данные с помощью оператора return, как это делают
обычные функции. Например, ту же самую функцию s мы можем
записать в виде:
auto s { [](const char* msg, double x) {
cout << msg << endl;
return ++x;
} };
double b = s("increment", 4);
В результате,
переменная b примет значение
5.
Или, более
простой пример:
auto sum2 { [](int a, int b) {return a+b;} };
Получили
объект-функцию для сложения двух целых чисел. Если мы посмотрим на
сформированный тип для переменной sum2, то увидим:
При
необходимости, возвращаемый тип можно указывать явно. Для этого, после круглых
скобок ставится оператор -> с указанием нужного типа. Например:
auto sum2 { [](int a, int b) -> double {return a+b;} };
Получим
вычисленный тип вида:
double sum2(int a, int b)
Начиная со
стандарта С++14 в лямбда-выражениях можно использовать вычисляемые типы у
параметров. Например:
auto sum2 { [](auto a, auto b) -> double {return a+b;} };
и,
соответственно, вызывать функцию, например, так:
double res_1 = sum2(3, 5);
double res_2 = sum2(3.4, 5.3);
cout << res_1 << " " << res_2 << endl;
Можно пойти еще
дальше у всех типов прописать ключевое слово auto, получим:
auto sum2 { [](auto a, auto b) -> auto {return a+b;} };
В результате
объект sum2 можно
прописывать для сложения любых допустимых данных. Например:
std::string res_3 = sum2(std::string("hello, "), std::string("world!"));
cout << res_3 << endl;
Лямбда-выражения в аргументах функций
Теперь, когда мы
в целом познакомились с идеей лямбда-выражений, ответим на следующий вопрос,
зачем все это нужно? Давайте, я приведу простой пример, где лямбда-функции
могут быть удобнее обычных функций.
Объявим функцию
с именем show_ar, которая
выводит целочисленные значения переданного ей массива ar длиной length:
void show_ar(const int* ar, size_t length, bool (*filter_func)(int) = nullptr)
{
for(int i = 0;i < length; ++i) {
if(filter_func != nullptr) {
if(filter_func(ar[i]))
cout << ar[i] << " ";
}
else
cout << ar[i] << " ";
}
}
Последний
параметр представляет собой указатель на функцию со значением по умолчанию nullptr. В этом случае в
консоль выводится весь массив от начала до конца. Например, так:
int main()
{
int data[] {1, 2, 3, 4, 5, 6, 7, 8};
show_ar(data, sizeof(data)/sizeof(*data));
return 0;
}
Но третьим
аргументом можно передать функцию, которая позволит нам фильтровать данные и
отображать только нужные значения массива, например, только четные. Для этого
воспользуемся лямбда-выражением и пропишем его следующим образом прямо в момент
вызова функции show_ar():
show_ar(data, sizeof(data)/sizeof(*data), [](int x) {return x % 2 == 0;});
Анонимная
функция возвращает истину для четных значений и ложь для нечетных. В итоге
видим в консоли только четные значения элементов массива.
Видите, как
удобно определять произвольные критерии выбора числовых значений с помощью
лямбда-функций? В результате, функция show_ar() стала в
некотором смысле универсальной. Ее можно вызвать для отображения массива целых
чисел с любым критерием отбора. Например, всех чисел кратных трем:
show_ar(data, sizeof(data)/sizeof(*data), [](int x) {return x % 3 == 0;});
Преимущество
лямбда-выражения в этом примере перед обычной функцией еще и в том, что это
выражение для функции существует лишь при вызове show_ar() и автоматически
пропадает после вызова. То есть, имеем экономию памяти и не нагромождаем
программу лишними объявлениями функций.
На следующем
занятии продолжим эту тему и увидим, зачем нужны квадратные скобки при
определении лямбда-выражений.
Практический курс по C/C++: https://stepik.org/course/193691