Funtores y expresiones lambda
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 74 de 104.
Muchos algoritmos de la STL aceptan un objeto llamable (callable) que personaliza su comportamiento. Ya has visto lambdas utilizadas con std::transform. Exploremos tanto los functors como las expresiones lambda: dos formas de crear objetos llamables en C++.
Un functor (objeto de función) es una clase que sobrecarga el operator(), permitiendo que las instancias sean llamadas como funciones:
#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}
}Los functores pueden mantener el estado entre llamadas, algo que las funciones normales no pueden hacer. Sin embargo, definir una clase para operaciones simples resulta verboso. Las expresiones lambda proporcionan una alternativa concisa:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });La sintaxis de lambda es [capture](parameters) { body }. La cláusula de captura especifica a qué variables externas puede acceder la lambda. Utiliza [=] para capturar todo por valor, [&] para capturar todo por referencia, o enumera variables específicas como [factor] o [&factor].
Las lambdas son particularmente útiles para operaciones puntuales con algoritmos como la ordenación con criterios 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 por valor absoluto: {-1, -2, 5, 8}Desafío
FácilVamos a construir una calculadora de precios que demuestre tanto los functores como las expresiones lambda para aplicar diferentes estrategias de descuento a los precios de los productos.
Organizarás tu código en dos archivos:
Discounts.h: Define aquí tus functores de descuento y funciones de utilidad.Crea un functor llamado
PercentageDiscountque almacene un porcentaje de descuento (como un entero). Suoperator()debe recibir un preciodoubley devolver el precio con el descuento aplicado. Por ejemplo, un descuento del 20% sobre $100 debería devolver $80.Crea otro functor llamado
FixedDiscountque almacene una cantidad fija a restar (como undouble). Suoperator()debe recibir un precio y devolver el precio menos la cantidad fija (pero nunca por debajo de 0).Crea una función llamada
printPricesque reciba unconst std::vector<double>&e imprima todos los precios separados por espacios, seguidos de un salto de línea. Formatea cada precio con dos decimales.main.cpp: Lee cinco entradas (cada una en una línea separada):- Precio del primer producto (double)
- Precio del segundo producto (double)
- Precio del tercer producto (double)
- Porcentaje de descuento a aplicar (entero, por ejemplo, 20 para 20%)
- Cantidad de descuento fijo (double)
Crea un vector con los tres precios y demuestra ambos enfoques:
- Imprime
"Original prices: "seguido de los precios - Usa
std::transformcon tu functorPercentageDiscountpara crear un nuevo vector de precios con descuento. Imprime"After percentage discount: "seguido de los resultados - Usa
std::transformcon tu functorFixedDiscountsobre los precios originales para crear otro vector. Imprime"After fixed discount: "seguido de los resultados - Usa
std::transformcon una expresión lambda que duplique cada precio original. Imprime"Premium prices (doubled): "seguido de los resultados - Usa
std::sortcon una lambda para ordenar los precios originales en orden descendente. Imprime"Sorted (high to low): "seguido de los precios ordenados
Por ejemplo, con las entradas 100.00, 50.00, 75.00, 20, y 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 desafío te permite comparar functores (que mantienen un estado como la cantidad de descuento) con lambdas (que capturan variables para operaciones rápidas en línea). Ambos enfoques funcionan perfectamente con algoritmos de la STL como std::transform y std::sort.
Hoja de referencia
Los algoritmos de la STL aceptan objetos invocables para personalizar su comportamiento. Dos enfoques comunes son los functors y las expresiones lambda.
Un functor es una clase que sobrecarga el operator(), permitiendo que las instancias sean llamadas como funciones:
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));Los functors pueden mantener el estado entre llamadas, pero requieren definiciones de clase verbosas.
Las expresiones lambda proporcionan una alternativa concisa con la sintaxis [capture](parameters) { body }:
int factor = 3;
std::transform(nums.begin(), nums.end(), result.begin(),
[factor](int x) { return x * factor; });La cláusula de captura especifica a qué variables externas puede acceder la lambda:
[=]- capturar todo por valor[&]- capturar todo por referencia[factor]- capturar una variable específica por valor[&factor]- capturar una variable específica por referencia
Las lambdas funcionan bien con algoritmos como std::sort para criterios de ordenación personalizados:
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return std::abs(a) < std::abs(b); });Pruébalo tú mismo
#include <iostream>
#include <vector>
#include <algorithm>
#include "Discounts.h"
int main() {
// Leer las entradas
double price1, price2, price3;
int percentageDiscount;
double fixedDiscount;
std::cin >> price1;
std::cin >> price2;
std::cin >> price3;
std::cin >> percentageDiscount;
std::cin >> fixedDiscount;
// Crear un vector con los tres precios
std::vector<double> prices = {price1, price2, price3};
// TODO: Imprimir "Original prices:" seguido de los precios usando printPrices
// TODO: Usar std::transform con el functor PercentageDiscount
// Crear un nuevo vector para los resultados
// Imprimir "After percentage discount:" seguido de los resultados
// TODO: Usar std::transform con el functor FixedDiscount sobre los precios originales
// Crear un nuevo vector para los resultados
// Imprimir "After fixed discount:" seguido de los resultados
// TODO: Usar std::transform con una lambda que duplique cada precio original
// Crear un nuevo vector para los resultados
// Imprimir "Premium prices (doubled):" seguido de los resultados
// TODO: Usar std::sort con una lambda para ordenar los precios originales en orden descendente
// Imprimir "Sorted (high to low):" seguido de los precios ordenados
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados10Visión general de la STL
Visión general y filosofía de la STLContenedores de la STLIteradoresAlgoritmos de la STLFuntores y expresiones lambdaResumen - Frecuencia de palabras2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico