Menu
Coddy logo textTech

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}
challenge icon

Desafío

Fácil

Vamos 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 PercentageDiscount que almacene un porcentaje de descuento (como un entero). Su operator() debe recibir un precio double y devolver el precio con el descuento aplicado. Por ejemplo, un descuento del 20% sobre $100 debería devolver $80.

    Crea otro functor llamado FixedDiscount que almacene una cantidad fija a restar (como un double). Su operator() debe recibir un precio y devolver el precio menos la cantidad fija (pero nunca por debajo de 0).

    Crea una función llamada printPrices que reciba un const 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):
    1. Precio del primer producto (double)
    2. Precio del segundo producto (double)
    3. Precio del tercer producto (double)
    4. Porcentaje de descuento a aplicar (entero, por ejemplo, 20 para 20%)
    5. Cantidad de descuento fijo (double)

    Crea un vector con los tres precios y demuestra ambos enfoques:

    1. Imprime "Original prices: " seguido de los precios
    2. Usa std::transform con tu functor PercentageDiscount para crear un nuevo vector de precios con descuento. Imprime "After percentage discount: " seguido de los resultados
    3. Usa std::transform con tu functor FixedDiscount sobre los precios originales para crear otro vector. Imprime "After fixed discount: " seguido de los resultados
    4. Usa std::transform con una expresión lambda que duplique cada precio original. Imprime "Premium prices (doubled): " seguido de los resultados
    5. Usa std::sort con 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;
}
quiz iconPonte a prueba

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