c++ Lambda 表达式使用笔记
Lambda 表达式的基本组成部分
Lambda 表达式是 C++11 引入的一种匿名函数,用于定义轻量级的可调用对象。它的基本语法如下:
[capture](parameters) -> return_type {
// 函数体
}
1. 捕获列表(Capture List)
捕获列表用于指定 lambda 表达式如何访问外部作用域的变量。捕获方式包括:
- 值捕获:捕获外部变量的副本。
- 引用捕获:捕获外部变量的引用。
- 混合捕获:同时使用值捕获和引用捕获。
this捕获:捕获当前对象的this指针。- 初始化捕获(C++14):在捕获列表中直接初始化变量。
*this捕获(C++17):捕获当前对象的副本。
示例:
int x = 10;
int y = 20;
// 值捕获
auto lambda1 = [x]() { return x; }; // x 被复制到 lambda 中
// 引用捕获
auto lambda2 = [&y]() { return y; }; // y 被引用捕获
// 混合捕获
auto lambda3 = [x, &y]() { return x + y; }; // x 被复制,y 被引用
// this 捕获
struct MyClass {
int value = 42;
auto get_lambda() {
return [this]() { return value; }; // 捕获 this 指针
}
};
// 初始化捕获(C++14)
auto lambda4 = [z = x + y]() { return z; }; // z 被初始化为 x + y
// *this 捕获(C++17)
auto lambda5 = [*this]() { return value; }; // 捕获当前对象的副本
2. 参数列表(Parameters)
参数列表与普通函数的参数列表类似,用于定义 lambda 表达式的输入参数。
示例:
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4); // 输出 7
3. 返回类型(Return Type)
返回类型可以显式指定,也可以由编译器自动推导。如果函数体只有一条 return 语句,编译器可以自动推导返回类型。
示例:
// 自动推导返回类型
auto square = [](int x) { return x * x; };
// 显式指定返回类型
auto cube = [](int x) -> int { return x * x * x; };
4. 函数体(Function Body)
函数体是 lambda 表达式的实现部分,包含具体的逻辑代码。
示例:
auto print = [](const std::string& msg) {
std::cout << msg << std::endl;
};
print("Hello, Lambda!");
5. 说明符(Specifiers)
mutable:允许修改值捕获的变量。constexpr(C++17):将 lambda 表达式声明为常量表达式。consteval(C++20):将 lambda 表达式声明为立即求值的常量表达式。
示例:
// mutable 示例
int a = 10;
auto lambda_mutable = [a]() mutable { a++; return a; };
std::cout << lambda_mutable(); // 输出 11,但外部的 a 仍然是 10
// constexpr 示例(C++17)
constexpr auto lambda_constexpr = [](int x) constexpr { return x * x; };
static_assert(lambda_constexpr(5) == 25); // 编译时计算
// consteval 示例(C++20)
consteval auto lambda_consteval = [](int x) consteval { return x * x; };
static_assert(lambda_consteval(5) == 25); // 编译时计算
6. 模板形参(C++20)
C++20 允许 lambda 表达式使用模板形参。
示例:
auto generic_lambda = []<typename T>(T x) { return x * x; };
std::cout << generic_lambda(5) << std::endl; // 输出 25
std::cout << generic_lambda(3.14) << std::endl; // 输出 9.8596
Lambda 表达式的深入应用
1. 即调用函数表达式(IIFE)
IIFE 是一种在定义 lambda 表达式后立即调用的技术,通常用于限制变量的作用域。
示例:
int result = [](int a, int b) { return a + b; }(3, 4);
std::cout << result; // 输出 7
2. 捕获时计算(C++14)
在捕获列表中直接初始化变量,可以在捕获时进行计算。
示例:
int x = 10;
int y = 20;
auto lambda = [z = x + y]() { return z; };
std::cout << lambda(); // 输出 30
3. 使用 auto 避免复制(C++14)
在捕获列表中使用 auto 可以避免显式指定类型,同时避免不必要的复制。
示例:
std::string msg = "Hello";
auto lambda = [msg = std::move(msg)]() { return msg; };
std::cout << lambda(); // 输出 "Hello"
4. Lifting(C++14)
Lifting 是指将 lambda 表达式提升为函数对象,以便在更广泛的上下文中使用。
示例:
auto make_adder = [](int x) {
return [x](int y) { return x + y; };
};
auto add5 = make_adder(5);
std::cout << add5(10); // 输出 15
5. 递归调用(C++14)
Lambda 表达式可以通过 std::function 实现递归调用。
示例:
std::function<int(int)> factorial = [&](int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
};
std::cout << factorial(5); // 输出 120
使用注意事项
- 捕获列表的作用:
- 值捕获会复制变量,引用捕获会共享变量。
- 避免在 lambda 表达式中修改值捕获的变量,除非使用
mutable。
- 性能问题:
- 引用捕获可能导致悬空引用,尤其是在异步编程中。
- 值捕获可能导致不必要的复制,尤其是对于大型对象。
- 生命周期问题:
- 确保 lambda 表达式中引用的对象在调用时仍然有效。
- 递归调用的限制:
- 直接递归调用 lambda 表达式需要借助
std::function或其他机制。
- 直接递归调用 lambda 表达式需要借助
深入应用举例
1. IIFE 用于初始化
int x = [] {
int a = 10;
int b = 20;
return a + b;
}();
std::cout << x; // 输出 30
2. 捕获时计算
int a = 10;
int b = 20;
auto lambda = [sum = a + b]() { return sum; };
std::cout << lambda(); // 输出 30
3. Lifting 示例
auto make_multiplier = [](int factor) {
return [factor](int x) { return x * factor; };
};
auto double_value = make_multiplier(2);
std::cout << double_value(5); // 输出 10
4. 递归调用示例
std::function<int(int)> fibonacci = [&](int n) {
return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
};
std::cout << fibonacci(10); // 输出 55
2万+

被折叠的 条评论
为什么被折叠?



