Menu
CoddyTech

仿函数与 Lambda 表达式

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 74 课,共 104 课。

许多 STL 算法都接受可调用对象,用来定制其行为。你已经见过与 std::transform 一起使用的 lambda。接下来,我们来了解 函数对象和 lambda 表达式,这两种在 C++ 中创建可调用对象的方式。

仿函数(函数对象)是一个重载了 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}
}

每个函子实例都可以通过构造时设置的成员变量携带自己的独立状态,这是普通函数调用做不到的。不过,为简单操作定义类会显得冗长。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]。

Lambda 表达式特别适合用于一次性操作,例如按照自定义条件排序:

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

挑战

简单

我们来构建一个价格计算器,演示如何使用函子和 lambda 表达式,为商品价格应用不同的折扣策略。

你需要将代码分到两个文件中:

  • Discounts.h:在这里定义折扣函子和实用函数。

    创建一个名为 PercentageDiscount 的函子,用整数保存折扣百分比。它的 operator() 应接收一个 double 类型的价格,并返回折后价格。例如,$100 打 20% 的折扣后应返回 $80。

    再创建一个名为 FixedDiscount 的函子,用 double 类型保存要减去的固定金额。它的 operator() 应接收一个价格,并返回减去固定金额后的价格(但不能低于 0)。

    创建一个名为 printPrices 的函数,接收一个 const std::vector<double>&,并将所有价格以空格分隔后输出,最后换行。将每个价格格式化为保留两位小数。

  • main.cpp:读取 5 个输入(每个输入单独占一行):
    1. 第一个商品的价格(double)
    2. 第二个商品的价格(double)
    3. 第三个商品的价格(double)
    4. 要应用的百分比折扣(整数,例如,20 表示 20%)
    5. 固定折扣金额(double)

    创建一个包含这三个价格的向量,并演示这两种方法:

    1. 输出 Original prices:,后接这些价格
    2. 使用 std::transform 和 PercentageDiscount 函子创建一个新的折后价格向量。输出 After percentage discount:,后接计算结果
    3. 使用 std::transform 和 FixedDiscount 函子处理原始价格,创建另一个向量。输出 After fixed discount:,后接计算结果
    4. 使用 std::transform 和一个 lambda 表达式,将每个原始价格加倍。输出 Premium prices (doubled):,后接计算结果
    5. 使用 std::sort 和一个 lambda 按降序排列原始价格。输出 Sorted (high to low):,后接排序后的价格

例如,输入 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 

这个挑战让你比较函子(可以像保存折扣金额这样的状态)和 lambda(可以捕获变量,快速执行内联操作)。这两种方法都能与 std::transform 和 std::sort 等 STL 算法顺畅配合。

亲手试试

#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;
    
    // 用这三个价格创建一个 vector
    std::vector<double> prices = {price1, price2, price3};
    
    // TODO: 使用 printPrices 输出 "Original prices:",然后输出价格
    
    // TODO: 使用 std::transform 和 PercentageDiscount 函数对象
    // 创建一个新 vector 来存放结果
    // 输出 "After percentage discount:",然后输出结果
    
    // TODO: 对原始价格使用 std::transform 和 FixedDiscount 函数对象
    // 创建一个新 vector 来存放结果
    // 输出 "After fixed discount:",然后输出结果
    
    // TODO: 使用 std::transform 和一个将每个原始价格翻倍的 lambda
    // 创建一个新 vector 来存放结果
    // 输出 "Premium prices (doubled):",然后输出结果
    
    // TODO: 使用 std::sort 和一个 lambda,将原始价格按降序排序
    // 输出 "Sorted (high to low):",然后输出排序后的价格
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器