Этот сайт использует cookies. Продолжение работы с сайтом означает, что Вы согласны!
Лямбда-выражения
Начиная со стандарта C++11, помимо обычных и шаблонных функций язык C++ позволяет использовать анонимные функции, которые называются лямбда-выражениями или лямбда-функциями. Лямбда-выражение описывается по следующей схеме:
[ [Захват] ]( [Параметры через запятую] ) [mutable] [noexcept]
[ -> Тип_результата] {
Тело_лямбда-выражения
[return Возвращаемое_значение;]
}
Как видно из схемы, у лямбда-выражения нет имени, по этой причине их и называют анонимными функциями. В качестве значения лямбда-выражение возвращает указатель, который мы можем сохранить в переменной или передать в качестве параметра в функцию. Типом переменной может быть:
- указатель на функцию. Давайте создадим лямбда-выражение, выводящее сообщение в окно консоли, сохраним указатель на него в переменной, а затем вызовем:
void (*lambda)() = []{ std::cout << "OK" << std::endl; };
lambda(); // Вызов лямбда-выражения
- объект класса
function
. Прежде чем использовать класс, следует подключить заголовочный файлfunctional
:
#include <functional>
Создание объекта осуществляется по следующим схемам:
std::function<Сигнатура_функции> lambda1(Лямбда-выражение);
std::function<Сигнатура_функции> lambda2 = Лямбда-выражение;
std::function<Сигнатура_функции> lambda3;
lambda3 = Лямбда-выражение;
std::function<Сигнатура_функции> lambda4 = nullptr;
lambda4 = Лямбда-выражение;
Внутри угловых скобок указывается сигнатура функции, состоящая из типа возвращаемого значения и типов параметров через запятую внутри круглых скобок, например, void()
или int(int,int)
:
std::function<void()> lambda =
[]{ std::cout << "OK" << std::endl; };
lambda(); // Вызов лямбда-выражения
- ключевое слово
auto
:
auto lambda = []{ std::cout << "OK" << std::endl; };
lambda(); // Вызов лямбда-выражения
Описание лямбда-выражения начинается с квадратных скобок, а затем внутри фигурных скобок выводится сообщение. Лямбда-выражение в нашем примере не принимает параметров, поэтому круглые скобки можно не указывать. Хотя можно указать явным образом, если для вас так более привычно:
auto lambda = [](){ std::cout << "OK" << std::endl; };
Лямбда-выражение в нашем примере ничего не возвращает, а только выводит сообщение, поэтому тип возвращаемого значения не указывается. Если внутри фигурных скобок нет оператора return
, то компилятор по умолчанию выберет ключевое слово void
.
Вызов лямбда-выражения на исполнение осуществляется с помощью круглых скобок, внутри которых можно передать значения, указав их через запятую. Если лямбда-выражение не принимает параметры, то указываются пустые круглые скобки. Можно не сохранять указатель в переменной, а сразу вызвать лямбда-выражение:
[](){ std::cout << "OK" << std::endl; }(); // OK
Создадим лямбда-выражение с одним параметром:
auto lambda = [](const char *s){
std::cout << s << std::endl;
};
lambda("message"); // message
Тип возвращаемого значения указывается после оператора ->
. Напишем лямбда-выражение для сложения двух целых чисел:
std::function<int(int,int)> lambda = nullptr;
lambda = [](int x, int y) -> int { return x + y; };
std::cout << lambda(10, 20) << std::endl; // 30
Компилятор может самостоятельно определить тип возвращаемого значения по выражению в операторе return
, поэтому тип возвращаемого значения в этом примере можно не указывать:
std::function<int(int,int)> lambda;
lambda = [](int x, int y) { return x + y; };
std::cout << lambda(10, 20) << std::endl; // 30
Начиная со стандарта C++14, вместо реального типа параметра можно указать ключевое слово auto
, что позволяет при вызове передавать данные произвольных типов (как при использовании шаблонных функций):
auto lambda = [](auto x, auto y) { return x + y; };
std::cout << lambda(10, 20) << std::endl; // 30
std::cout << lambda(1.5, 2.2) << std::endl; // 3.7
std::cout << lambda(1.5, 2) << std::endl; // 3.5
std::cout << lambda(1, 2.2) << std::endl; // 3.2
Как и в обычных функциях, некоторые параметры лямбда-выражения могут быть необязательными. Для этого параметрам присваивается значение по умолчанию:
auto lambda = [](int x, int y=5) { return x + y; };
std::cout << lambda(10, 20) << std::endl; // 30
std::cout << lambda(10) << std::endl; // 15
Наиболее часто не сохраняют адрес лямбда-выражения в переменной, а сразу передают в качестве параметра в другую функцию. Например, функция qsort()
позволяет передать указатель на пользовательскую функцию сравнения в последнем параметре. Давайте отсортируем массив по возрастанию, передав в последнем параметре функции qsort()
лямбда-выражение (листинг 12.30).
Листинг 12.30. Передача лямбда-выражения в функцию
#include <iostream>
#include <cstdlib>
int main() {
const int ARR_SIZE = 5;
int arr[ARR_SIZE] = {10, 5, 6, 1, 3};
std::qsort(arr, ARR_SIZE, sizeof(int),
[](const void *arg1, const void *arg2) -> int {
if (*(int *)arg1 < *(int *)arg2) return -1;
if (*(int *)arg1 > *(int *)arg2) return 1;
return 0;
});
for (int i = 0; i < ARR_SIZE; ++i) {
std::cout << arr[i] << std::endl;
}
return 0;
}
Если указываются пустые квадратные скобки, то внутри тела лямбда-выражения мы имеем доступ только к глобальным переменным (можно как получить значение, так и изменить его):
int global_x = 10;
// ...
int x = 11;
auto lambda = [](){
std::cout << global_x << std::endl; // 20, а не 10 !!!
global_x = 30;
// К локальной переменной x нет доступа
// std::cout << x << std::endl; // Ошибка
};
global_x = 20;
lambda(); // Вызов лямбда-выражения
std::cout << global_x << std::endl; // 30
std::cout << x << std::endl; // 11
Следует учитывать, что мы получим значение глобальной переменной на момент вызова, а не значение на момент создания лямбда-выражения. Чтобы получить копию значения глобальной переменной на момент создания можно воспользоваться, например, следующим кодом:
auto lambda = [](int x) {
return [x]() {
std::cout << x << std::endl; // 10, а не 20
};
}(global_x);
global_x = 20;
lambda(); // Вызов лямбда-выражения
Мы создали лямбда-выражение с одним параметром, возвращающее вложенное лямбда-выражение. Далее мы сразу вызываем первое лямбда-выражение с помощью круглых скобок и передаем значение глобальной переменной global_x
. Чтобы значение параметра x
было доступно во вложенном лямбда-выражении, мы указали название локальной переменной внутри квадратных скобок вложенного лямбда-выражения. Такая операция называется захватом переменной из внешней области видимости или замыканием.
По умолчанию к локальным переменным из внешней области видимости внутри тела лямбда-выражения доступа нет. Чтобы получить доступ, следует внутри квадратных скобок указать следующие конструкции:
[=]
— захват всех локальных переменных из внешней области видимости по значению. Значения сохраняются на момент создания лямбда-выражения, а не на момент вызова. По умолчанию изменять значения внутри лямбда-выражения нельзя. Чтобы иметь такую возможность, нужно дополнительно указать ключевое словоmutable
. Следует учитывать, что изменение значения внутри лямбда-выражения не затронет значения одноименной локальной переменной из внешней области видимости:
int x = 10;
auto lambda = [=]() mutable {
// Значение на момент создания лямбда-выражения
std::cout << x << std::endl; // 10, а не 20
// Изменение значения не затронет значения
// локальной переменной x из внешней области видимости
x = 30; // Если mutable не указано, то будет ошибка
};
x = 20;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl; // 20, а не 30
[&]
— захват всех локальных переменных из внешней области видимости по ссылке. Значения доступны на момент вызова, а не на момент создания лямбда-выражения. Внутри лямбда-выражения можно как получить значение локальной переменной из внешней области видимости, так и изменить его:
int x = 10;
auto lambda = [&]() {
// Значение на момент вызова лямбда-выражения
std::cout << x << std::endl; // 20, а не 10
// Можно изменить значение
x = 30;
};
x = 20;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl; // 30, а не 20
[x, y]
— захват только переменныхx
иy
по значению:
int x = 10, y = 11, z = 22;
auto lambda = [x, y]() mutable {
// Значения на момент создания лямбда-выражения
std::cout << x << std::endl; // 10
std::cout << y << std::endl; // 11
// std::cout << z << std::endl; // Ошибка, нет доступа
// Изменение значений не затронет значений
// локальных переменных x и y из внешней области видимости
x = 30; y = 40;
};
x = 20;
y = 12;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl; // 20
std::cout << y << std::endl; // 12
std::cout << z << std::endl; // 22
[&x, &y]
— захват только переменныхx
иy
по ссылке:
int x = 10, y = 11, z = 22;
auto lambda = [&x, &y]() {
// Значения на момент вызова лямбда-выражения
std::cout << x << std::endl; // 20
std::cout << y << std::endl; // 12
// std::cout << z << std::endl; // Ошибка, нет доступа
x = 30; y = 40;
};
x = 20;
y = 12;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl; // 30
std::cout << y << std::endl; // 40
std::cout << z << std::endl; // 22
[x, &y]
— захват только переменныхx
(по значению) иy
(по ссылке);[=, &x, &y]
— захват всех локальных переменных из внешней области видимости по значению, кроме переменныхx
иy
, которые захватываются по ссылке:
int x = 10, y = 11, z = 22;
auto lambda = [=, &x, &y]() mutable {
// Значения на момент вызова лямбда-выражения
std::cout << x << std::endl; // 20
std::cout << y << std::endl; // 12
// Значение на момент создания лямбда-выражения
std::cout << z << std::endl; // 22
x = 30;
y = 40;
z = 33; // Не изменит значение из внешней области
};
x = 20;
y = 12;
z = 55;
lambda(); // Вызов лямбда-выражения
std::cout << x << std::endl; // 30
std::cout << y << std::endl; // 40
std::cout << z << std::endl; // 55, а не 33
[&, x, y]
— захват всех локальных переменных из внешней области видимости по ссылке, кроме переменныхx
иy
, которые захватываются по значению;[this]
— захват указателяthis
внутри класса:
class A {
public:
A(int x) : x_(x) {}
void func() {
[this]() { std::cout << x_ << std::endl; }();
}
private:
int x_;
};
Классы мы подробно рассмотрим в следующей главе, а сейчас просто создадим экземпляр класса A
и вызовем метод func()
:
A obj(10);
obj.func(); // 10
Помощь сайту
ЮMoney (Yandex-деньги): 410011140483022
ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов