Lambda表达式是C++11引入的匿名函数特性,它允许在代码中“就地定义”简短的函数,无需单独声明。这种“即写即用”的特性让代码更简洁、更灵活,尤其在回调、算法参数等场景中非常实用。

一、Lambda表达式的基本语法

Lambda的核心结构是:

[capture-list](parameters) mutable -> return-type { body }

各部分含义:

  1. [capture-list](捕获列表)
    定义Lambda外部的变量如何被Lambda内部访问(如按值捕获、按引用捕获)。

    • []:不捕获任何外部变量。
    • [x]:按值捕获变量x(Lambda内部是x的副本)。
    • [&x]:按引用捕获变量x(Lambda内部可修改外部x)。
    • [=]:按值捕获所有用到的外部变量。
    • [&]:按引用捕获所有用到的外部变量。
    • [=, &x]:除x按引用捕获外,其他变量按值捕获。
  2. (parameters)(参数列表)
    与普通函数的参数列表相同,如(int a, int b)

  3. mutable(可选)
    允许按值捕获的变量在Lambda内部被修改(但不会影响外部变量)。

  4. -> return-type(返回类型,可选)
    当Lambda体只有一条return语句时,编译器可自动推导返回类型,此时可省略。

  5. { body }(函数体)
    函数的具体逻辑。

基础示例:

#include <iostream>

int main() {
    // 最简单的Lambda:无参数、无返回值
    []() { 
        std::cout << "Hello, Lambda!\n"; 
    }(); // 定义后直接调用(末尾加())

    // 带参数和返回值的Lambda
    auto add = [](int a, int b) -> int { 
        return a + b; 
    };
    std::cout << add(3, 5) << "\n"; // 输出8

    // 捕获外部变量(按值捕获x)
    int x = 10;
    auto printX = [x]() { 
        std::cout << "x = " << x << "\n"; 
    };
    printX(); // 输出10

    // 按引用捕获x(可修改外部变量)
    auto modifyX = [&x]() { 
        x = 20; 
    };
    modifyX();
    std::cout << "x = " << x << "\n"; // 输出20(外部x被修改)

    return 0;
}

二、Lambda的核心价值:“就地定义”的灵活性

普通函数需要先声明后使用,而Lambda可以在需要的地方“临时定义”,尤其适合以下场景:

场景1:作为算法的参数(STL算法中广泛使用)

STL中的排序、查找等算法需要传入“比较规则”“处理逻辑”等函数,Lambda可以直接在调用处定义这些逻辑,无需单独写函数。

示例:自定义排序规则

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9};

    // 用Lambda定义排序规则:从大到小排序
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b; // 自定义比较逻辑
    });

    // 打印排序结果
    for (int num : nums) {
        std::cout << num << " "; // 输出9 5 4 3 1 1
    }
    return 0;
}

示例:筛选元素

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6};
    std::vector<int> evenNums;

    // 筛选偶数(用Lambda定义筛选规则)
    std::copy_if(nums.begin(), nums.end(), std::back_inserter(evenNums), 
                 [](int x) { return x % 2 == 0; }); 
    // evenNums = {2,4,6}
    return 0;
}

场景2:作为回调函数(简化代码)

在需要回调的场景(如按钮点击、事件触发),Lambda可以直接在注册回调的地方定义逻辑,避免单独编写回调函数。

示例:GUI按钮回调

// 假设存在一个按钮类
class Button {
public:
    using Callback = std::function<void()>; // 用std::function接收回调
    void setOnClick(Callback cb) { callback = cb; }
    void click() { if (callback) callback(); } // 触发回调
private:
    Callback callback;
};

int main() {
    Button btn;
    int clickCount = 0;

    // 用Lambda定义点击回调(捕获外部变量clickCount)
    btn.setOnClick([&clickCount]() { 
        clickCount++;
        std::cout << "点击次数:" << clickCount << "\n"; 
    });

    // 模拟点击
    btn.click(); // 点击次数:1
    btn.click(); // 点击次数:2
    return 0;
}

场景3:简化复杂算法的临时逻辑

在复杂算法中,可能需要临时定义一个小逻辑(如中间计算、条件判断),用Lambda可以避免定义多个“一次性”函数。

示例:计算数组中满足条件的元素之和

#include <vector>
#include <numeric>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};
    int threshold = 3;

    // 计算所有大于threshold的元素之和
    int sum = 0;
    std::for_each(nums.begin(), nums.end(), [&](int x) {
        if (x > threshold) {
            sum += x; // 4 + 5 = 9
        }
    });
    std::cout << sum << "\n"; // 输出9
    return 0;
}

场景4:捕获上下文变量(灵活访问外部数据)

Lambda的“捕获列表”可以灵活访问外部变量,这在回调中尤其有用——无需通过参数传递大量外部数据。

示例:带上下文的过滤逻辑

#include <vector>
#include <string>
#include <algorithm>

int main() {
    std::vector<std::string> words = {"apple", "banana", "cherry", "date"};
    int minLength = 5; // 外部变量:筛选长度≥5的单词

    // 用Lambda捕获minLength,定义筛选规则
    auto it = std::remove_if(words.begin(), words.end(), 
                            [minLength](const std::string& s) {
                                return s.size() < minLength; 
                            });

    words.erase(it, words.end()); // 剩余:apple, banana, cherry
    return 0;
}

三、Lambda的实际应用价值总结

  1. 代码更简洁
    避免在代码中充斥大量只使用一次的小函数(如排序规则、简单回调),逻辑就近编写,可读性更高。

  2. 减少命名污染
    匿名函数无需命名,避免“为了一个小功能起名字”的烦恼,尤其适合简单逻辑。

  3. 灵活捕获上下文
    无需通过参数传递外部变量,直接在Lambda中捕获,简化回调与外部数据的交互。

  4. 与STL算法无缝配合
    STL算法(如sortfor_eachfind_if)大量依赖“可调用对象”,Lambda让这些算法的使用更自然。

四、使用Lambda的注意事项

  1. 捕获列表的坑

    • 按值捕获的变量是“捕获时的副本”,后续外部变量修改不会影响Lambda内部。
    • 按引用捕获时,需确保Lambda执行时被引用的变量仍有效(避免悬垂引用)。
    int* ptr = new int(10);
    auto lambda = [ptr]() { std::cout << *ptr << "\n"; }; // 按值捕获ptr
    delete ptr; // 释放内存
    lambda(); // 危险!访问已释放的内存(未定义行为)
    
  2. 生命周期
    Lambda本身是“可调用对象”,可以存储在std::function中传递,但需注意捕获的引用变量的生命周期(如上面的悬垂引用问题)。

  3. 过度使用的问题
    复杂逻辑(多段代码、多分支)不适合用Lambda,否则会导致代码臃肿,此时应改用普通函数。

五、何时优先使用Lambda?

  • 逻辑简单(1-3行代码)且仅使用一次的场景(如排序规则、简单回调)。
  • 需要访问外部上下文变量的回调(避免通过参数传递大量数据)。
  • 与STL算法配合使用时(如sortfor_eachfind_if)。

通过以上场景可以发现,Lambda的核心价值是“在需要函数的地方就地定义函数”,它填补了“临时简单逻辑”与“正式函数”之间的空白,让C++代码更灵活、更易读。