Funktory i wyrażenia lambda
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 74 z 104.
Wiele algorytmów STL przyjmuje obiekt wywoływalny, który dostosowuje ich działanie. Widziałeś już lambdy używane z std::transform. Przyjrzyjmy się zarówno funktorom, jak i wyrażeniom lambda — dwóm sposobom tworzenia obiektów wywoływalnych w C++.
Funktor (obiekt funkcyjny) to klasa, która przeciąża operator(), umożliwiając wywoływanie jej instancji jak funkcji:
#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}
}Każda instancja funkcyjna może przechowywać własny, niezależny stan za pomocą zmiennych składowych ustawianych podczas tworzenia — czego nie może zrobić zwykłe wywołanie funkcji. Jednak definiowanie klasy dla prostych operacji jest rozwlekłe. Wyrażenia lambda stanowią zwięzłą alternatywę:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });Składnia lambdy to [capture](parameters) { body }. Klauzula przechwytywania określa, do których zmiennych zewnętrznych lambda ma dostęp. Użyj [=], aby przechwycić wszystkie przez wartość, [&], aby przechwycić wszystkie przez referencję, lub wymień konkretne zmienne, takie jak [factor] lub [&factor].
Lambdy są szczególnie przydatne do jednorazowych operacji z algorytmami, takimi jak sortowanie według niestandardowych kryteriów:
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); });
// Posortowane według wartości bezwzględnej: {-1, -2, 5, 8}Wyzwanie
ŁatwyZbudujmy kalkulator cen, który pokazuje zarówno funktory, jak i wyrażenia lambda służące do stosowania różnych strategii rabatowych do cen produktów.
Rozmieścisz swój kod w dwóch plikach:
Discounts.h: Zdefiniuj tutaj funktory rabatowe i funkcje pomocnicze.Utwórz funktor o nazwie
PercentageDiscount, który przechowuje procent rabatu (jako liczbę całkowitą). Jegooperator()powinien przyjmować cenę typudoublei zwracać cenę po rabacie. Na przykład rabat 20% od 100 USD powinien zwrócić 80 USD.Utwórz kolejny funktor o nazwie
FixedDiscount, który przechowuje stałą kwotę do odjęcia (jako wartość typudouble). Jegooperator()powinien przyjmować cenę i zwracać cenę pomniejszoną o stałą kwotę (ale nigdy mniejszą niż 0).Utwórz funkcję o nazwie
printPrices, która przyjmujeconst std::vector<double>&i wypisuje wszystkie ceny oddzielone spacjami, a na końcu znak nowej linii. Formatuj każdą cenę do dwóch miejsc po przecinku.main.cpp: Wczytaj pięć danych wejściowych (każdą w osobnym wierszu):- Cena pierwszego produktu (double)
- Cena drugiego produktu (double)
- Cena trzeciego produktu (double)
- Procent rabatu do zastosowania (liczba całkowita, np. 20 dla 20%)
- Kwota stałego rabatu (double)
Utwórz tablicę z trzema cenami i pokaż oba podejścia:
- Wypisz
Original prices:, a następnie ceny - Użyj
std::transformz funktoremPercentageDiscount, aby utworzyć nową tablicę cen po rabacie. WypiszAfter percentage discount:, a następnie wyniki - Użyj
std::transformz funktoremFixedDiscountdla cen początkowych, aby utworzyć kolejną tablicę. WypiszAfter fixed discount:, a następnie wyniki - Użyj
std::transformz wyrażeniem lambda, które podwaja każdą cenę początkową. WypiszPremium prices (doubled):, a następnie wyniki - Użyj
std::sortz lambdą, aby posortować ceny początkowe w kolejności malejącej. WypiszSorted (high to low):, a następnie posortowane ceny
Na przykład dla danych wejściowych 100.00, 50.00, 75.00, 20 i 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 To wyzwanie pozwala porównać funktory (które przechowują stan, taki jak kwota rabatu) z lambdami (które przechwytują zmienne na potrzeby szybkich operacji wykonywanych bezpośrednio w kodzie). Oba podejścia płynnie współpracują z algorytmami STL, takimi jak std::transform i std::sort.
Spróbuj swoich sił
#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"
int main() {
// Wczytaj dane wejściowe
double price1, price2, price3;
int percentageDiscount;
double fixedDiscount;
std::cin >> price1;
std::cin >> price2;
std::cin >> price3;
std::cin >> percentageDiscount;
std::cin >> fixedDiscount;
// Utwórz wektor z trzema cenami
std::vector<double> prices = {price1, price2, price3};
// TODO: Wypisz "Original prices:", a następnie ceny za pomocą printPrices
// TODO: Użyj std::transform z funktorem PercentageDiscount
// Utwórz nowy wektor na wyniki
// Wypisz "After percentage discount:", a następnie wyniki
// TODO: Użyj std::transform z funktorem FixedDiscount na oryginalnych cenach
// Utwórz nowy wektor na wyniki
// Wypisz "After fixed discount:", a następnie wyniki
// TODO: Użyj std::transform z lambdą, która podwaja każdą oryginalną cenę
// Utwórz nowy wektor na wyniki
// Wypisz "Premium prices (doubled):", a następnie wyniki
// TODO: Użyj std::sort z lambdą, aby posortować oryginalne ceny malejąco
// Wypisz "Sorted (high to low):", a następnie posortowane ceny
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników10Przegląd STL
Przegląd i filozofia STLKontenery STLIteratoryAlgorytmy STLFunktory i wyrażenia lambdaPowtórzenie – częstość występowania słów2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online