Функторы и лямбда-выражения
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 74 из 104.
Многие алгоритмы STL принимают вызываемый объект, который настраивает их поведение. Вы уже видели лямбда-выражения, используемые с std::transform. Давайте рассмотрим functors и lambda expressions — два способа создания вызываемых объектов в C++.
functor (объект функции) — это класс, который перегружает operator(), позволяя вызывать его экземпляры как функции:
#include <algorithm>
#include <vector>
#include <iostream>
struct MultiplyBy {
int factor;
MultiplyBy(int f) : factor(f) {}
int operator()(int x) const {
return x * factor;
}
};
int main() {
std::vector<int> nums = {1, 2, 3, 4};
std::vector<int> result(nums.size());
std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));
// result: {3, 6, 9, 12}
}Каждый экземпляр functor может хранить собственное независимое состояние через переменные-члены, заданные при создании, — то, чего не может сделать обычный вызов function. Однако определение class для простых операций многословно. Выражения lambda предоставляют лаконичную альтернативу:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });Синтаксис lambda имеет вид [capture](parameters) { body }. Предложение захвата указывает, к каким внешним переменным lambda может обращаться. Используйте [=], чтобы захватить все по значению, [&], чтобы захватить все по ссылке, или перечислите конкретные переменные, например [factor] или [&factor].
Лямбда-выражения особенно полезны для разовых операций с алгоритмами, например для сортировки по пользовательским критериям:
std::vector<int> nums = {5, -2, 8, -1};
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return std::abs(a) < std::abs(b); });
// Отсортировано по абсолютному значению: {-1, -2, 5, 8}Задание
ЛегкоДавайте создадим калькулятор цен, демонстрирующий использование функторов и лямбда-выражений для применения различных стратегий discount к ценам товаров.
Вы организуете код в двух файлах:
Discounts.h: определите здесь функторы discount и вспомогательные функции.Создайте функтор с именем
PercentageDiscount, который хранит процент discount (как целое число). Егоoperator()должен принимать цену типаdoubleи возвращать цену с учетом discount. Например, discount в размере 20% от $100 должен возвращать $80.Создайте другой функтор с именем
FixedDiscount, который хранит фиксированную сумму для вычитания (какdouble). Егоoperator()должен принимать цену и возвращать цену за вычетом фиксированной суммы (но never меньше 0).Создайте function с именем
printPrices, которая принимаетconst std::vector<double>&и выводит все цены, разделенные пробелами, с последующим newline. Отформатируйте каждую цену с двумя знаками после десятичной точки.main.cpp: считайте пять inputs (каждый в отдельной строке):- Цена первого товара (double)
- Цена второго товара (double)
- Цена третьего товара (double)
- Применяемый процент discount (integer, например, 20 для 20%)
- Фиксированная сумма discount (double)
Создайте вектор с тремя ценами и продемонстрируйте оба подхода:
- Выведите
Original prices:, followed by ценами - Используйте
std::transformс вашим функторомPercentageDiscount, чтобы создать новый вектор цен с учетом discount. ВыведитеAfter percentage discount:, followed by результатами - Используйте
std::transformс вашим функторомFixedDiscountдля исходных цен, чтобы создать еще один вектор. ВыведитеAfter fixed discount:, followed by результатами - Используйте
std::transformс лямбда-выражением, которое удваивает each исходную цену. ВыведитеPremium prices (doubled):, followed by результатами - Используйте
std::sortс лямбда-выражением, чтобы отсортировать исходные цены в descending order. ВыведитеSorted (high to low):, followed by отсортированными ценами
Например, для inputs 100.00, 50.00, 75.00, 20 и 15.00:
Original prices: 100.00 50.00 75.00
After percentage discount: 80.00 40.00 60.00
After fixed discount: 85.00 35.00 60.00
Premium prices (doubled): 200.00 100.00 150.00
Sorted (high to low): 100.00 75.00 50.00 Это задание позволяет сравнить функторы (которые сохраняют состояние, например сумму discount) с лямбда-выражениями (которые захватывают переменные для быстрых встроенных операций). Оба подхода seamlessly работают с алгоритмами STL, такими как std::transform и std::sort.
Попробуйте сами
#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"
int main() {
// Считать входные данные
double price1, price2, price3;
int percentageDiscount;
double fixedDiscount;
std::cin >> price1;
std::cin >> price2;
std::cin >> price3;
std::cin >> percentageDiscount;
std::cin >> fixedDiscount;
// Создать вектор с тремя ценами
std::vector<double> prices = {price1, price2, price3};
// TODO: Print "Original prices:" followed by the prices using printPrices
// TODO: Use std::transform with PercentageDiscount functor
// Создать новый вектор для результатов
// Print "After percentage discount:" followed by the results
// TODO: Use std::transform with FixedDiscount functor on original prices
// Создать новый вектор для результатов
// Print "After fixed discount:" followed by the results
// TODO: Use std::transform with a lambda that doubles each original price
// Создать новый вектор для результатов
// Print "Premium prices (doubled):" followed by the results
// TODO: Use std::sort with a lambda to sort original prices in descending order
// Print "Sorted (high to low):" followed by the sorted prices
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++