Functors e Expressões Lambda
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 74 de 104.
Muitos algoritmos da STL aceitam um objeto chamável (callable) que customiza seu comportamento. Você já viu lambdas sendo usados com std::transform. Vamos explorar tanto functors quanto expressões lambda - duas maneiras de criar objetos chamáveis em C++.
Um functor (objeto de função) é uma classe que sobrecarrega o operator(), permitindo que instâncias sejam chamadas como funções:
#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));
// resultado: {3, 6, 9, 12}
}Funtores podem manter estado entre chamadas, o que funções regulares não conseguem. No entanto, definir uma classe para operações simples é verboso. Expressões lambda fornecem uma alternativa concisa:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });A sintaxe lambda é [capture](parameters) { body }. A cláusula de captura especifica quais variáveis externas a lambda pode acessar. Use [=] para capturar tudo por valor, [&] para capturar tudo por referência, ou liste variáveis específicas como [factor] ou [&factor].
Lambdas são particularmente úteis para operações pontuais com algoritmos como ordenação com critérios personalizados:
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); });
// Ordenado pelo valor absoluto: {-1, -2, 5, 8}Desafio
FácilVamos construir uma calculadora de preços que demonstra tanto functores quanto expressões lambda para aplicar diferentes estratégias de desconto aos preços dos produtos.
Você organizará seu código em dois arquivos:
Discounts.h: Defina seus functores de desconto e funções utilitárias aqui.Crie um functor chamado
PercentageDiscountque armazena uma porcentagem de desconto (como um inteiro). Seuoperator()deve receber um preçodoublee retornar o preço com desconto. Por exemplo, um desconto de 20% sobre $100 deve retornar $80.Crie outro functor chamado
FixedDiscountque armazena um valor fixo a ser subtraído (como umdouble). Seuoperator()deve receber um preço e retornar o preço menos o valor fixo (mas nunca abaixo de 0).Crie uma função chamada
printPricesque recebe umconst std::vector<double>&e imprime todos os preços separados por espaços, seguidos por uma nova linha. Formate cada preço com duas casas decimais.main.cpp: Leia cinco entradas (cada uma em uma linha separada):- Preço do primeiro produto (double)
- Preço do segundo produto (double)
- Preço do terceiro produto (double)
- Porcentagem de desconto a aplicar (inteiro, ex: 20 para 20%)
- Valor do desconto fixo (double)
Crie um vetor com os três preços e demonstre ambas as abordagens:
- Imprima
Original prices:seguido pelos preços - Use
std::transformcom seu functorPercentageDiscountpara criar um novo vetor de preços com desconto. ImprimaAfter percentage discount:seguido pelos resultados - Use
std::transformcom seu functorFixedDiscountnos preços originais para criar outro vetor. ImprimaAfter fixed discount:seguido pelos resultados - Use
std::transformcom uma expressão lambda que dobra cada preço original. ImprimaPremium prices (doubled):seguido pelos resultados - Use
std::sortcom uma lambda para ordenar os preços originais em ordem decrescente. ImprimaSorted (high to low):seguido pelos preços ordenados
Por exemplo, com as entradas 100.00, 50.00, 75.00, 20, e 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 Este desafio permite comparar functores (que mantêm estado, como o valor do desconto) com lambdas (que capturam variáveis para operações rápidas e inline). Ambas as abordagens funcionam perfeitamente com algoritmos da STL como std::transform e std::sort.
Folha de consulta
Algoritmos da STL aceitam objetos chamáveis para personalizar seu comportamento. Duas abordagens comuns são functors e expressões lambda.
Um functor é uma classe que sobrecarrega o operator(), permitindo que instâncias sejam chamadas como funções:
struct MultiplyBy {
int factor;
MultiplyBy(int f) : factor(f) {}
int operator()(int x) const {
return x * factor;
}
};
std::transform(nums.begin(), nums.end(), result.begin(), MultiplyBy(3));Functors podem manter estado entre chamadas, mas exigem definições de classe verbosas.
Expressões lambda fornecem uma alternativa concisa com a sintaxe [capture](parameters) { body }:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });A cláusula de captura especifica quais variáveis externas a lambda pode acessar:
[=]- capturar tudo por valor[&]- capturar tudo por referência[factor]- capturar variável específica por valor[&factor]- capturar variável específica por referência
Lambdas funcionam bem com algoritmos como std::sort para critérios de ordenação personalizados:
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return std::abs(a) < std::abs(b); });Experimente você mesmo
#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"
int main() {
// Ler as entradas
double price1, price2, price3;
int percentageDiscount;
double fixedDiscount;
std::cin >> price1;
std::cin >> price2;
std::cin >> price3;
std::cin >> percentageDiscount;
std::cin >> fixedDiscount;
// Criar um vector com os três preços
std::vector<double> prices = {price1, price2, price3};
// TODO: Imprimir "Original prices:" seguido pelos preços usando printPrices
// TODO: Usar std::transform com o functor PercentageDiscount
// Criar um novo vector para os resultados
// Imprimir "After percentage discount:" seguido pelos resultados
// TODO: Usar std::transform com o functor FixedDiscount nos preços originais
// Criar um novo vector para os resultados
// Imprimir "After fixed discount:" seguido pelos resultados
// TODO: Usar std::transform com uma lambda que dobra cada preço original
// Criar um novo vector para os resultados
// Imprimir "Premium prices (doubled):" seguido pelos resultados
// TODO: Usar std::sort com uma lambda para ordenar os preços originais em ordem decrescente
// Imprimir "Sorted (high to low):" seguido pelos preços ordenados
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários10Visão Geral da STL
Visão Geral e Filosofia da STLContainers da STLIteradoresAlgoritmos da STLFunctors e Expressões LambdaRecapitulação - Frequência de Palavras2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico