Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Лямбда-функции в C++
Определение и назначение
Лямбда (lambda) — это безымянная функция (функтор), объявленная и определённая в точке использования. Введены в C++11 и стали одной из ключевых фишек современного C++.
Лямбды решают проблему "одноразовых" функций: зачем создавать отдельную функцию или функтор, если функция нужна только в одном месте?
Синтаксис лямбды
[capture](parameters) -> ReturnType { body }
Компоненты:
- [capture] — список переменных, захватываемые из внешней области
- (parameters) — параметры функции (может быть пусто)
- -> ReturnType — тип возврата (опционально, выводится автоматически)
- { body } — тело лямбды
Пример базовой лямбды
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// Простая лямбда без параметров и захватов
auto greet = []() { std::cout << "Hello, Lambda!\\n"; };
greet(); // Вызов: Hello, Lambda!
// Лямбда с параметрами
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 5) << std::endl; // 8
// Лямбда с явным типом возврата
auto divide = [](double a, double b) -> double { return a / b; };
std::cout << divide(10.0, 3.0) << std::endl; // 3.33...
return 0;
}
Захват переменных (capture)
Это самая мощная часть лямбд — управление доступом к переменным из окружающей области:
1. Захват по значению [=]
int x = 10, y = 20;
auto lambda = [=]() { return x + y; }; // Копируем x и y
std::cout << lambda() << std::endl; // 30
Изменение x и y снаружи не влияет на лямбду.
2. Захват по ссылке [&]
int x = 10, y = 20;
auto lambda = [&]() { return x + y; }; // Ссылка на x и y
x = 100; // Изменяем x
std::cout << lambda() << std::endl; // 120
Лямбда видит новое значение x. Опасно, если лямбда живёт дольше переменной!
3. Смешанный захват [=, &x]
int x = 10, y = 20, z = 30;
auto lambda = [=, &x]() { return x + y + z; };
// Захватили y и z по значению, x по ссылке
x = 100;
std::cout << lambda() << std::endl; // 100 + 20 + 30 = 150
4. Явный захват [x, y]
int x = 10, y = 20, z = 30;
auto lambda = [x, y]() { return x + y; }; // Только x и y
// z недоступен, это ошибка:
// auto bad = [x, y]() { return x + y + z; }; // ERROR!
5. Инициализирующий захват (C++14) [x = 42, &y]
int original_x = 10;
auto lambda = [x = 100, &y]() { return x + y; };
// x захвачена со значением 100 (не 10!)
Полезно для копирования и переименования переменных.
Использование в алгоритмах STL
Лямбды очень удобны с функциями стандартной библиотеки:
#include <algorithm>
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// sort с кастомным компаратором
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; }); // По убыванию
// find_if с условием
auto it = std::find_if(nums.begin(), nums.end(),
[](int x) { return x > 3; });
// for_each с побочным эффектом
int sum = 0;
std::for_each(nums.begin(), nums.end(),
[&sum](int x) { sum += x; }); // Захват sum по ссылке
std::cout << "Sum: " << sum << std::endl;
return 0;
}
Лямбда как тип функции
Каждая лямбда имеет уникальный анонимный тип. Вы не можете (легко) узнать этот тип:
auto lambda1 = []() {};
auto lambda2 = []() {};
// lambda1 и lambda2 имеют РАЗНЫЕ типы, даже если выглядят одинаково!
Для хранения лямбд разных типов используйте std::function:
#include <functional>
std::function<int(int, int)> operation;
operation = [](int a, int b) { return a + b; };
std::cout << operation(5, 3) << std::endl; // 8
operation = [](int a, int b) { return a * b; };
std::cout << operation(5, 3) << std::endl; // 15
Стационарные лямбды (mutable, C++11)
По умолчанию лямбда захватывает переменные по значению как const — вы не можете менять копию внутри лямбды:
int x = 10;
auto lambda = [x]() { x++; }; // ERROR: x захвачена как const
Решение — добавить mutable:
int x = 10;
auto lambda = [x]() mutable { x++; return x; };
std::cout << lambda() << std::endl; // 11
std::cout << x << std::endl; // 10 (оригинал не изменился)
Лямбда с состоянием (C++14+)
Инициализирующий захват позволяет создавать лямбды со своим состоянием:
auto counter = [count = 0]() mutable { return ++count; };
std::cout << counter() << std::endl; // 1
std::cout << counter() << std::endl; // 2
std::cout << counter() << std::endl; // 3
Обобщённые лямбды (C++14)
Используйте auto в параметрах лямбды:
auto add = [](auto a, auto b) { return a + b; };
std::cout << add(3, 5) << std::endl; // 8 (int)
std::cout << add(3.5, 2.7) << std::endl; // 6.2 (double)
std::cout << add(std::string("Hello"), " World") << std::endl; // Конкатенация
Лямбды в C++20: concepts и constraints
template<typename T>
concept Addable = requires(T a, T b) { a + b; };
auto safe_add = []<Addable T>(T a, T b) { return a + b; };
Когда использовать лямбды
Используйте лямбды:
- Для callback функций в алгоритмах STL
- Для одноразовых операций
- Когда функция нужна только локально
- В параллельном коде (std::thread, async)
Избегайте лямбд:
- Если логика сложная и повторяется
- Если лямбда становится больше 5-10 строк
- Для публичных API (используйте явные функции)
Производительность
Лямбды без захватов (или с пустым захватом) компилируются в обычные указатели на функции с нулевых оверхедом. Лямбды с захватом превращаются в функторы и также очень эффективны — компилятор их инлайнит.
Итоговые рекомендации
Лямбда — это мощный инструмент для написания чистого, компактного кода. Она:
- Позволяет определить функцию рядом с местом использования
- Автоматически захватывает переменные из окружающей области
- Идеальна для STL алгоритмов
- С C++11+ стала стандартом для коллбэков и функциональных операций
- Имеет нулевой оверхед на производительность благодаря инлайнированию
Мастерство в использовании лямбд — признак современного C++ программиста!