在 C++11 之前,如果你想传递一个「小功能」给函数,通常需要定义一个独立的函数或者函数对象。这种方式不仅繁琐,而且代码分散,可读性差。

Lambda 表达式(也叫匿名函数)的出现,让你可以在需要的地方直接定义「小函数」,代码更简洁、更易读。

1. 什么是 Lambda 表达式?

Lambda 表达式是一种没有名字的函数,你可以把它理解为「随用随定义的临时函数」。

1.1 基本语法

[捕获列表](参数列表) -> 返回类型 {
    函数体
}

各个部分的含义:

  • 捕获列表:指定 Lambda 可以访问外部作用域的哪些变量
  • 参数列表:和普通函数的参数一样
  • 返回类型:可以省略,编译器会自动推导
  • 函数体:函数的具体逻辑

1.2 最简单的 Lambda

#include <iostream>
using namespace std;

int main() {
    // 定义一个最简单的 Lambda
    auto sayHello = []() {
        cout << "Hello, Lambda!" << endl;
    };
    
    // 调用 Lambda
    sayHello();  // 输出:Hello, Lambda!
    
    return 0;
}

这里 [] 是捕获列表(空表示不捕获任何外部变量),() 是参数列表(空表示无参数)。

2. 带参数的 Lambda

2.1 基本参数

#include <iostream>
using namespace std;

int main() {
    // 带参数的 Lambda
    auto add = [](int a, int b) {
        return a + b;
    };
    
    cout << add(3, 5) << endl;  // 输出:8
    cout << add(10, 20) << endl;  // 输出:30
    
    return 0;
}

2.2 指定返回类型

#include <iostream>
using namespace std;

int main() {
    // 显式指定返回类型
    auto divide = [](double a, double b) -> double {
        if (b == 0) {
            return 0;
        }
        return a / b;
    };
    
    cout << divide(10, 3) << endl;  // 输出:3.33333
    cout << divide(10, 0) << endl;  // 输出:0
    
    return 0;
}

大多数情况下,返回类型可以省略,编译器会自动推导。但在以下情况需要显式指定:

  • 有多个 return 语句且返回类型不同
  • 需要返回引用
  • 返回类型无法自动推导

3. 捕获外部变量

Lambda 最强大的特性是可以捕获(访问)外部作用域的变量。

3.1 值捕获 [=]

#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int y = 20;
    
    // 值捕获:复制外部变量的值
    auto lambda = [=]() {
        cout << "x = " << x << ", y = " << y << endl;
        // x = 30;  // 错误!值捕获的变量是只读的
    };
    
    x = 30;  // 修改外部变量
    y = 40;
    
    lambda();  // 输出:x = 10, y = 20(捕获的是定义时的值)
    
    return 0;
}

注意:值捕获会复制变量的值,Lambda 内部看到的是「快照」,外部变量的后续修改不影响 Lambda。

3.2 引用捕获 [&]

#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int y = 20;
    
    // 引用捕获:捕获变量的引用
    auto lambda = [&]() {
        cout << "x = " << x << ", y = " << y << endl;
        x = 100;  // 可以修改外部变量
    };
    
    lambda();  // 输出:x = 10, y = 20
    cout << "修改后 x = " << x << endl;  // 输出:修改后 x = 100
    
    return 0;
}

引用捕获让 Lambda 可以读写外部变量,但也意味着你需要小心悬垂引用问题。

3.3 混合捕获

#include <iostream>
using namespace std;

int main() {
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    
    // 混合捕获:默认值捕获,但 b 和 d 使用引用捕获
    auto lambda = [=, &b, &d]() {
        // a, c 是值捕获(只读)
        // b, d 是引用捕获(可读写)
        b = a + c;
        d = a * c;
    };
    
    lambda();
    cout << "b = " << b << endl;  // 输出:b = 4
    cout << "d = " << d << endl;  // 输出:d = 3
    
    return 0;
}

捕获列表的组合方式:

捕获列表 含义
[] 不捕获任何变量
[=] 所有变量以值方式捕获
[&] 所有变量以引用方式捕获
[x] 只捕获 x(值方式)
[&x] 只捕获 x(引用方式)
[=, &x] 默认值捕获,x 引用捕获
[&, x] 默认引用捕获,x 值捕获
[this] 捕获当前对象的 this 指针

3.4 初始化捕获(C++14)

C++14 引入了初始化捕获,让你可以在捕获列表中创建新变量:

#include <iostream>
#include <string>
using namespace std;

int main() {
    // 初始化捕获:在捕获列表中创建新变量
    auto lambda = [name = string("Alice"), age = 25]() {
        cout << name << " is " << age << " years old" << endl;
    };
    
    lambda();  // 输出:Alice is 25 years old
    
    // 移动捕获(C++14)
    auto ptr = make_unique<int>(42);
    auto lambda2 = [p = move(ptr)]() {
        cout << *p << endl;
    };
    
    lambda2();  // 输出:42
    // cout << *ptr << endl;  // 错误!ptr 已经被移动
    
    return 0;
}

4. Lambda 的实际应用

4.1 作为排序依据

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> nums = {5, 2, 8, 1, 9, 3};
    
    // 使用 Lambda 自定义排序
    sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b;  // 降序排序
    });
    
    cout << "降序排序结果:";
    for (int num : nums) {
        cout << num << " ";
    }
    cout << endl;  // 输出:9 8 5 3 2 1
    
    // 按绝对值排序
    vector<int> nums2 = {-5, 2, -8, 1, 9, -3};
    sort(nums2.begin(), nums2.end(), [](int a, int b) {
        return abs(a) < abs(b);
    });
    
    cout << "按绝对值排序:";
    for (int num : nums2) {
        cout << num << " ";
    }
    cout << endl;  // 输出:1 2 -3 -5 -8 9
    
    return 0;
}

4.2 作为算法的参数

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 使用 Lambda 过滤偶数
    auto it = find_if(nums.begin(), nums.end(), [](int n) {
        return n % 2 == 0;
    });
    
    if (it != nums.end()) {
        cout << "第一个偶数是:" << *it << endl;  // 输出:2
    }
    
    // 使用 Lambda 计算满足条件的元素个数
    int evenCount = count_if(nums.begin(), nums.end(), [](int n) {
        return n % 2 == 0;
    });
    cout << "偶数个数:" << evenCount << endl;  // 输出:5
    
    // 使用 Lambda 转换元素
    vector<int> squares(nums.size());
    transform(nums.begin(), nums.end(), squares.begin(), [](int n) {
        return n * n;
    });
    
    cout << "平方值:";
    for (int sq : squares) {
        cout << sq << " ";
    }
    cout << endl;  // 输出:1 4 9 16 25 36 49 64 81 100
    
    return 0;
}

4.3 作为回调函数

#include <iostream>
#include <functional>
using namespace std;

// 接受回调函数的高阶函数
void executeWithRetry(int maxRetries, function<bool()> operation) {
    for (int i = 0; i < maxRetries; i++) {
        cout << "尝试第 " << (i + 1) << " 次..." << endl;
        if (operation()) {
            cout << "操作成功!" << endl;
            return;
        }
    }
    cout << "达到最大重试次数,操作失败" << endl;
}

int main() {
    int attempt = 0;
    
    // 使用 Lambda 作为回调
    executeWithRetry(3, [&attempt]() -> bool {
        attempt++;
        // 模拟第 3 次尝试成功
        return attempt >= 3;
    });
    
    return 0;
}

5. 泛型 Lambda(C++14)

C++14 允许 Lambda 使用 auto 作为参数类型,实现泛型功能:

#include <iostream>
using namespace std;

int main() {
    // 泛型 Lambda
    auto add = [](auto a, auto b) {
        return a + b;
    };
    
    cout << add(1, 2) << endl;        // 输出:3(int + int)
    cout << add(1.5, 2.5) << endl;    // 输出:4(double + double)
    cout << add(1, 2.5) << endl;      // 输出:3.5(int + double)
    
    // 泛型 Lambda 用于打印任意容器
    auto printContainer = [](const auto& container) {
        for (const auto& item : container) {
            cout << item << " ";
        }
        cout << endl;
    };
    
    vector<int> nums = {1, 2, 3, 4, 5};
    vector<string> words = {"Hello", "World", "C++"};
    
    printContainer(nums);    // 输出:1 2 3 4 5
    printContainer(words);   // 输出:Hello World C++
    
    return 0;
}

6. constexpr Lambda(C++17)

C++17 允许 Lambda 在编译期求值:

#include <iostream>
using namespace std;

int main() {
    // constexpr Lambda
    constexpr auto square = [](int x) {
        return x * x;
    };
    
    // 编译期求值
    constexpr int result = square(5);
    cout << "5 的平方是:" << result << endl;  // 输出:25
    
    // 可以用在需要常量表达式的场景
    int arr[square(3)];  // 数组大小为 9
    
    return 0;
}

7. 常见陷阱

7.1 悬垂引用

#include <iostream>
#include <functional>
using namespace std;

function<int()> createLambda() {
    int x = 42;
    return [&x]() {  // 危险!x 是局部变量
        return x;    // 函数返回后 x 已经销毁
    };
}

int main() {
    auto lambda = createLambda();
    cout << lambda() << endl;  // 未定义行为!
    
    // 正确做法:使用值捕获
    auto createLambda2 = []() {
        int x = 42;
        return [x]() {  // 值捕获,复制一份
            return x;
        };
    };
    
    auto lambda2 = createLambda2();
    cout << lambda2() << endl;  // 输出:42
    
    return 0;
}

7.2 捕获 this 指针

#include <iostream>
using namespace std;

class Calculator {
private:
    int base;
    
public:
    Calculator(int b) : base(b) {}
    
    auto getAdder() {
        // 捕获 this 指针
        return [this](int x) {
            return base + x;
        };
    }
};

int main() {
    Calculator calc(10);
    auto adder = calc.getAdder();
    cout << adder(5) << endl;  // 输出:15
    
    return 0;
}

8. 总结

Lambda 表达式是现代 C++ 中最常用的特性之一:

  • 简洁:不需要单独定义函数,代码更紧凑
  • 灵活:可以捕获外部变量,适应各种场景
  • 强大:配合 STL 算法,写出更函数式的代码

最佳实践

  • 优先使用 [=][&] 的简洁形式
  • 只在需要时捕获特定变量
  • 避免捕获局部变量的引用
  • Lambda 体较长时考虑使用普通函数

下一节我们将学习多线程编程,Lambda 在其中会发挥重要作用。

更多推荐