Skip to Content

1. 什么是Lambda表达式?

简单来说,Lambda表达式是一种在代码中就地定义的匿名函数对象

我们来拆解这个定义:

  • 就地定义 (Inline):你可以在需要一个函数的地方直接写出它的实现,而无需在别处单独定义一个完整的函数或函数对象类。
  • 匿名 (Anonymous):它没有正式的函数名。
  • 函数对象 (Function Object / Functor):在底层,编译器会为每个Lambda表达式生成一个唯一的、未命名的类,并重载其operator()。所以,一个Lambda表达式的实例就是一个可调用对象,就像一个函数指针或函数对象一样。

2. 为什么需要Lambda?

想象一下在C++11之前,如果你想给std::sort传递一个自定义的比较逻辑,你需要:

  • 方法一:定义一个独立的函数

    bool compareMyObjects(const MyObject& a, const MyObject& b) { return a.value < b.value; } std::sort(vec.begin(), vec.end(), compareMyObjects);

    缺点:逻辑和调用点分离,如果这个比较逻辑只用一次,会造成全局命名空间的污染。

  • 方法二:定义一个函数对象(Functor)

    struct CompareMyObjects { bool operator()(const MyObject& a, const MyObject& b) const { return a.value < b.value; } }; std::sort(vec.begin(), vec.end(), CompareMyObjects());

    缺点:非常冗长,为了一个简单的比较写一个完整的结构体,代码显得笨重。

使用Lambda表达式,代码变得极其简洁和清晰:

std::sort(vec.begin(), vec.end(), [](const MyObject& a, const MyObject& b) { return a.value < b.value; });

所有的逻辑都集中在调用点,一目了然。

3. Lambda表达式的完整语法

其完整语法结构如下:

[capture-clause](parameters) specifiers -> return-type { // 函数体 function-body };

我们来逐一解析每个部分:

A. [capture-clause] (捕获列表)

这是Lambda最强大和最核心的部分。它定义了Lambda函数体内部如何访问其外部(定义Lambda的作用域)的变量。

  • []:不捕获任何外部变量。
  • [=]按值捕获所有外部变量。在Lambda函数体内,这些变量是只读的副本(除非使用mutable关键字)。
  • [&]按引用捕-获所有外部变量。在Lambda函数体内可以修改这些变量,并且会影响到原始变量。
  • [x, &y]:显式捕获。x按值捕获,y按引用捕获。这是推荐的最佳实践,因为它明确了Lambda的依赖。
  • [this]:按值捕获当前对象的this指针。这使得你可以在Lambda内部访问类的成员变量和成员函数。
  • [=, &y]:默认按值捕获,但y显式按引用捕获。
  • [&, x]:默认按引用捕获,但x显式按值捕获。
  • C++14+ 初始化捕获 (Generalized Capture):允许在捕获列表中创建并初始化新的变量。
    int x = 10; auto my_lambda = [y = x + 5, z = std::move(some_string)] { // 在这里,y的值是15,z拥有了some_string的内容 // Lambda内部无法直接访问x };

B. (parameters) (参数列表)

与普通函数的参数列表完全一样。如果Lambda不需要参数,()可以省略(但如果后面有mutable-> return-type则不能省略)。

  • C++14+ 泛型Lambda (Generic Lambda):可以在参数中使用auto关键字,使得Lambda可以接受任意类型的参数,效果类似于模板。
    auto add = [](auto a, auto b) { return a + b; }; int i = add(3, 4); // i = 7 double d = add(3.5, 4.5); // d = 8.0

C. specifiers (可选说明符)

  • mutable:允许在Lambda函数体内修改按值捕获的变量。注意,这修改的是Lambda内部的副本,不会影响外部的原始变量。
    int counter = 0; auto increment = [counter]() mutable { counter++; // 如果没有mutable,这里会编译错误 return counter; }; increment(); // 返回 1 increment(); // 返回 2 // 此时外部的 counter 仍然是 0
  • noexcept:指定该Lambda不抛出任何异常。
  • constexpr (C++17+):表示该Lambda可以在编译期求值。

D. -> return-type (返回类型)

指定Lambda的返回类型。在大多数情况下,这是可选的,因为编译器可以根据函数体中的return语句自动推断出返回类型。

何时必须显式指定?

  1. 函数体中有多个return语句,且它们返回的类型不一致(这通常会导致编译错误)。
  2. 你想强制转换返回类型。
  3. 函数体中没有return语句(推断为void),但你想让它返回void以外的类型。

E. { function-body } (函数体)

与普通函数的函数体一样,包含了Lambda要执行的代码。

4. 实践示例

  1. 查找大于特定值的第一个元素 (std::find_if)

    std::vector<int> v = {1, 5, 10, 15, 20}; int limit = 12; // 使用捕获列表来访问外部变量 limit auto it = std::find_if(v.begin(), v.end(), [limit](int n) { return n > limit; }); // it 指向 15
  2. 计算总和(按引用捕获)

    int sum = 0; // 使用 &sum 来修改外部的 sum std::for_each(v.begin(), v.end(), [&sum](int n) { sum += n; }); // sum 现在是 51
  3. 存储和调用Lambda Lambda可以被存储在auto变量或std::function中。

    // 使用auto,类型是编译器生成的唯一类型 auto greeter = [](const std::string& name) { std::cout << "Hello, " << name << std::endl; }; greeter("World"); // 使用std::function,更通用,但可能有轻微的性能开销 std::function<void(const std::string&)> greeter_func = greeter; greeter_func("C++");
  4. IILE (Immediately Invoked Lambda Expression) 定义一个Lambda并立即调用它,常用于复杂对象的const初始化。

    const int complex_value = [] { // ... 一些复杂的计算 ... int result = 0; for (int i = 0; i < 5; ++i) result += i; return result; }(); // 注意末尾的(),表示立即调用 // complex_value 是 10

总结

  • Lambda是现代C++的基石,它让函数式编程风格在C++中变得自然和高效。
  • 核心是[捕获列表],它解决了作用域问题,是Lambda与普通函数的最大区别。
  • 首选显式捕获 ([x, &y]) 而不是默认捕获 ([=], [&]),这让代码意图更清晰,不易出错。
  • 结合auto和STL算法使用,可以写出极其强大、简洁且易于维护的代码。

掌握Lambda表达式是精通现代C++的关键一步。

Last updated on