lambda表达式的使用(2)
类型四:广义 Lambda 捕获 / 表达式捕获(Init-capture,C++14)
核心要点:允许在捕获列表中定义并初始化新的成员变量,等号右边可以是任意表达式(包括 std::move、字面量等)。这是将 只移类型(Move-only Types,如 unique_ptr) 安全传入 Lambda 的唯一标准方式。
#include <iostream>
#include <memory>
int main() {
auto important = std::make_unique<int>(10);
// v1 = 1:闭包内新建 int v1 = 1
// v2 = std::move(important):闭包内新建 unique_ptr v2,从外部移动资源
auto add = [v1 = 1, v2 = std::move(important)](int x) -> int {
return x + v1 + (*v2);
};
std::cout << add(5) << std::endl; // 输出 16(5+1+10)
// important 此时已为空,资源已被闭包 v2 接管
return 0;
}
类型五:泛型 Lambda(Generic Lambda,C++14)
核心要点:参数列表中使用 auto,让编译器生成模板化的 operator()。一个 Lambda 可以适应多种参数类型(类似函数模板),极大提高了复用性。
#include <iostream>
int main() {
// 泛型 Lambda:支持 int, double, string 等任意支持 + 操作符的类型
auto add = [](auto a, auto b) {
return a + b;
};
std::cout << add(1, 2) << std::endl; // 输出 3(int)
std::cout << add(1.5, 2.7) << std::endl; // 输出 4.2(double)
std::cout << add(std::string("Hello "), std::string("World")) << std::endl; // 输出 Hello World
return 0;
}
类型六:可变 Lambda(Mutable Lambda)
核心要点:在参数列表后加上 mutable 关键字,移除 operator() 的 const 属性。这使得按值捕获的副本可以在 Lambda 体内被修改。注意:修改的永远是闭包内部的副本,绝不影响外部变量。
#include <iostream>
int main() {
int count = 0;
// 值捕获 count,但使用 mutable 允许修改副本
auto increment = [count]() mutable -> int {
count += 1; // 修改的是闭包内部副本,外部 count 始终为 0
return count;
};
std::cout << increment() << std::endl; // 输出 1(副本变成 1)
std::cout << increment() << std::endl; // 输出 2(副本变成 2)
std::cout << increment() << std::endl; // 输出 3(副本变成 3)
std::cout << "外部 count = " << count << std::endl; // 输出 0(外部纹丝不动)
return 0;
}
注意:
1:广义(大圈子):往大了说,看共性和外延。只要沾点边的都算,侧重“是什么”。狭义(小圈子):往小了说,看特性和内涵。必须严格符合核心定义才算,侧重“特指什么
2:lambda表达式的本质:在 C++ 标准中,闭包(Closure) 是指 “由 Lambda 表达式生成的、匿名的、独一无二的类(Closure Type)的实例对象。编译器看到你的 Lambda 表达式,并不会直接执行它,而是偷偷在后台造了一个新的类。auto Add = …,其实就是创建了这个“偷偷造的类”的一个对象。这个对象,就是闭包。因为在其他编程语言(如 JavaScript、Python)中,闭包强调的是“函数 + 它携带的外部环境”。在 C++ 中完全同理:闭包 = Lambda 的可调用对象 + 捕获到的外部变量副本/引用。
class Unnamed {
private:
int x;
public:
Unnamed(int x_) : x(x_) {}
int operator()(int y) const { return x + y; }
};
int x = 10;
auto lambda = [x](int y) { return x + y; };
// 等价于
Unnamed obj(x);
原始的lambda:
#include <iostream>
int main() {
int c = 10;
// 1. 这是一个 Lambda 表达式
auto lambda = [c](int a) -> int {
return a + c;
};
// 2. 调用它
int result = lambda(5);
std::cout << result << std::endl; // 输出 15
return 0;
}
编译器背后的工作:
#include <iostream>
// 这就是编译器根据你的 Lambda 表达式生成的“闭包类型”(本质是一个类)
class __AnonymousLambda {
private:
int c; // 对应你捕获列表中的 [c](值捕获,成为类的成员变量)
public:
// 构造函数:对应 Lambda 定义时(auto lambda = ...)执行的初始化
__AnonymousLambda(int c_) : c(c_) {}
// 重载函数调用运算符:对应 Lambda 的函数体
// 默认是 const 的,因为你没加 mutable(和 Lambda 行为完全一致)
int operator()(int a) const {
return a + c;
}
};
int main() {
int c = 10;
// 1. 创建这个类的实例(这就是“闭包对象”)
// 等价于 auto lambda = [c](int a)... 那一行
__AnonymousLambda lambda(c);
// 2. 调用重载的 operator(),等价于 lambda(5)
int result = lambda(5);
std::cout << result << std::endl; // 输出 15
return 0;
}
C++ 标准库提供了类型特征(Type Traits),你可以直接验证 Lambda 的类型确实是一个类(Class Type):
#include <iostream>
#include <type_traits>
int main() {
int c = 10;
auto lambda = [c](int a) { return a + c; };
// 静态断言:验证 lambda 的类型确实是一个类(Class)
static_assert(std::is_class<decltype(lambda)>::value, "Lambda is NOT a class?");
std::cout << "Lambda 确实是类!验证通过!" << std::endl;
return 0;
}
顺带解释:为什么“空捕获列表”的 Lambda 能转成函数指针?
因为如果捕获列表为空([]),生成的类就没有非静态成员变量。编译器会让这个类额外提供一个隐式转换运算符,把它转成普通的函数指针。
class __EmptyLambda {
public:
int operator()(int a) const { return a; }
// 隐式转换运算符:返回原始函数指针
using FuncPtr = int(*)(int);
operator FuncPtr() const {
return [](int a) { return a; }; // 静态函数
}
};
这就是为什么 void(*func)(int) = [](int x){}; 可以编译通过
更多推荐
所有评论(0)