C++ 中的匿名函数(Lambda 表达式)是 C++11 引入的一项重要特性,它允许你在需要的地方定义一个临时的、无名的函数对象,使代码更加简洁和灵活。
1. 基本语法
Lambda 表达式的基本结构:
cpp
[capture list](parameter list) -> return type { function body }
[capture list]
:捕获外部变量,指定如何将外部作用域的变量传递给 lambda。(parameter list)
:参数列表,与普通函数的参数类似(可省略,但若省略括号必须为空)。-> return type
:返回类型(可省略,编译器会自动推导)。{ function body }
:函数体,包含具体的实现逻辑。
示例:
cpp
auto add = [](int a, int b) -> int { return a + b; };
int result = add(3, 4); // 结果为 7
2. 捕获列表(Capture List)
捕获列表用于访问外部作用域中的变量,有以下几种方式:
值捕获(By Value)
- 使用
[var]
捕获变量的副本。 - Lambda 创建时拷贝变量,后续修改不影响 lambda 内部的值。
cpp
int x = 10;
auto lambda = [x]() { return x * 2; }; // 捕获 x 的值
x = 20;
std::cout << lambda(); // 输出 20(捕获的是 x 的副本)
引用捕获(By Reference)
- 使用
[&var]
捕获变量的引用。 - Lambda 内部使用的是变量的引用,外部修改会影响 lambda 内部。
cpp
int x = 10;
auto lambda = [&x]() { return x * 2; }; // 捕获 x 的引用
x = 20;
std::cout << lambda(); // 输出 40(引用 x 的当前值)
隐式捕获
- 使用
[=]
捕获所有外部变量的值(值捕获)。 - 使用
[&]
捕获所有外部变量的引用(引用捕获)。
cpp
int a = 5, b = 10;
auto lambda = [=]() { return a + b; }; // 值捕获 a 和 b
auto lambda2 = [&]() { a++; return a + b; }; // 引用捕获 a 和 b
混合捕获
- 同时使用值捕获和引用捕获,例如
[=, &a]
(默认值捕获,a
引用捕获)。
cpp
int a = 5, b = 10;
auto lambda = [=, &a]() { a++; return a + b; }; // a 引用捕获,b 值捕获
3. 参数列表
Lambda 的参数列表与普通函数类似,但不支持默认参数。
cpp
auto greet = [](const std::string& name) {
std::cout << "Hello, " << name << "!" << std::endl;
};
greet("Alice"); // 输出 "Hello, Alice!"
4. 返回类型
返回类型可省略,编译器会自动推导。若需要显式指定,使用 -> type
。
cpp
auto sum = [](int a, int b) -> int { return a + b; }; // 显式指定返回类型
auto square = [](double x) { return x * x; }; // 自动推导返回类型
5. 可变 Lambda(Mutable Lambda)
默认情况下,值捕获的变量在 lambda 内部是只读的。使用 mutable
关键字可修改值捕获的变量。
cpp
int x = 10;
auto lambda = [x]() mutable {
x++; // 允许修改值捕获的 x
return x;
};
std::cout << lambda(); // 输出 11(但外部 x 仍为 10)
6. 泛型 Lambda(C++14+)
使用 auto
作为参数类型,使 lambda 成为泛型函数。
cpp
auto print = [](const auto& value) {
std::cout << value << std::endl;
};
print(42); // 输出整数
print("test"); // 输出字符串
7. 捕获 this
指针
在类成员函数中,可捕获 this
指针以访问类的成员变量和方法。
cpp
class MyClass {
public:
int value = 10;
void func() {
auto lambda = [this]() { return value * 2; };
std::cout << lambda(); // 输出 20
}
};
8. 捕获初始化(C++14+)
允许在捕获列表中初始化新变量,可移动构造对象或重命名捕获的变量。
cpp
int x = 10;
auto lambda = [y = x + 5]() { return y; }; // 初始化 y 为 15
std::cout << lambda(); // 输出 15
// 移动捕获(适用于不可复制的对象,如 std::unique_ptr)
auto ptr = std::make_unique<int>(42);
auto lambda2 = [ptr = std::move(ptr)]() { return *ptr; };
9. Lambda 的类型和存储
- Lambda 表达式的类型是一个唯一的、未命名的闭包类型(Closure Type)。
- 可使用
auto
或std::function
存储 lambda。
cpp
// 使用 auto(推荐,效率更高)
auto add = [](int a, int b) { return a + b; };
// 使用 std::function(需包含 <functional>)
std::function<int(int, int)> multiply = [](int a, int b) { return a * b; };
10. Lambda 在 STL 中的应用
Lambda 常用于简化 STL 算法的使用。
cpp
#include <algorithm>
#include <vector>
std::vector<int> nums = {1, 2, 3, 4, 5};
// 使用 lambda 作为谓词
auto sum = std::accumulate(nums.begin(), nums.end(), 0,
[](int acc, int x) { return acc + x; });
// 排序
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; }); // 降序排序
// 查找第一个大于 3 的元素
auto it = std::find_if(nums.begin(), nums.end(),
[](int x) { return x > 3; });
11. 常量表达式 Lambda(C++17+)
使用 constexpr
关键字使 lambda 可以在编译时求值。
cpp
constexpr auto add = [](int a, int b) { return a + b; };
static_assert(add(3, 4) == 7, "Error"); // 编译时检查
12. 模板 Lambda(C++20+)
使用模板参数(template <typename T>
的简写)使 lambda 更灵活。
cpp
auto lambda = []<typename T>(const T& a, const T& b) { return a + b; };
int sum_int = lambda(1, 2); // T 推导为 int
double sum_double = lambda(1.5, 2.5); // T 推导为 double
13. 异常规范(C++17 前)
使用 noexcept
指定 lambda 是否抛出异常。
cpp
auto safe_divide = [](double a, double b) noexcept {
return b != 0 ? a / b : 0;
};
14. 性能考虑
- Lambda 通常比普通函数指针或
std::function
更高效,因为编译器可内联其代码。 - 值捕获会复制变量,可能影响性能(尤其是大对象),此时应优先使用引用捕获。
总结
C++ 的匿名函数(Lambda)提供了强大而灵活的语法,使代码更简洁、更易读。掌握捕获列表、参数、返回类型和各种特性(如泛型、捕获初始化)是使用 Lambda 的关键。合理使用 Lambda 可以显著提升 C++ 代码的表达力和效率。