
lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
Lambda 表达式是一种匿名函数对象,它可以在代码中直接定义并使用。其基本语法结构如下:
[capture](parameters)->return-type { body }[x],将变量 x 的值复制到 Lambda 中)、引用捕获([&x],通过引用捕获变量 x)等多种方式。捕获列表位于 Lambda 表达式的开头,用方括号[]表示。它允许 Lambda 表达式捕获外部作用域中的变量,从而在 Lambda 表达式的函数体内使用这些变量。捕获列表的语法如下: [capture1, capture2, …] 捕获列表中的每个捕获项可以是一个变量名、一个变量名前加&符号(表示通过引用捕获),或者是一个变量名前加*符号(表示捕获变量的地址)。捕获项之间用逗号分隔。
值捕获是将外部变量的值复制到 Lambda 表达式中。这种方式下,Lambda 表达式内部的变量是外部变量的一个副本,对内部变量的修改不会影响外部变量。例如:
int x = 10;
auto lambda = [x]() {
x = 20; // 修改的是 Lambda 内部的 x 副本
};
lambda();
std::cout << x << std::endl; // 输出 10,外部 x 的值未改变值捕获适用于那些不需要修改外部变量,或者希望在 Lambda 表达式中独立操作变量的情况。
引用捕获是通过引用的方式捕获外部变量。这种方式下,Lambda 表达式内部的变量是外部变量的引用,对内部变量的修改会直接影响外部变量。例如:
int x = 10;
auto lambda = [&x]() {
x = 20; // 直接修改外部变量 x
};
lambda();
std::cout << x << std::endl; // 输出 20,外部 x 的值被修改引用捕获适用于那些需要在 Lambda 表达式中修改外部变量的情况,但需要注意的是,如果外部变量的生命周期短于 Lambda 表达式的生命周期,可能会导致悬空引用,引发运行时错误。
在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=,&x]表⽰其他变量隐式值捕捉,
x引⽤捕捉;[&,x,y]表⽰其他变量引⽤捕捉,x和y值捕捉。
当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时
后⾯的捕捉变量必须是值捕捉,
同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。除了显式指定捕获的变量外,Lambda 表达式还支持默认捕获。默认捕获有两种方式: [=]:表示默认按值捕获所有外部变量。 [&]:表示默认按引用捕获所有外部变量。 默认捕获可以简化捕获列表的书写,但需要谨慎使用,因为过度捕获可能会导致不必要的性能开销或潜在的错误。例如:
int x = 10;
int y = 20;
auto lambda = [=]() {
x = 30; // 修改的是 Lambda 内部的 x 副本
y = 40; // 修改的是 Lambda 内部的 y 副本
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 20,外部 x 和 y 的值未改变在默认捕获的情况下,如果需要对某个变量采用不同的捕获方式,可以通过显式捕获来覆盖默认捕获。例如:
int x = 10;
int y = 20;
auto lambda = [=, &y]() {
x = 30; // 修改的是 Lambda 内部的 x 副本
y = 40; // 直接修改外部变量 y
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 40,外部 x 的值未改变,y 的值被修改在这个例子中,x 按值捕获,y 按引用捕获。
// 局部的静态和全局变量不能捕捉,也不需要捕捉
static int m = 0;
auto func6 = []
{
int ret = x + m;
return ret;
};i
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了
// mutable相当于去掉const属性,可以修改了
// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉
auto func7 = [=]()mutable
{
a++;
b++;
c++;
d++;
return a + b + c + d;
};
cout << func7() << endl;在类中,若想要使用私有成员变量时,直接捕获私有成员是报错的;这时需要通过捕获this指针来使用私有成员变量。
• 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。
• lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
// ...
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price > gr._price;
}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3
}, { "菠萝", 1.5, 4 } };
// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
// 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;
});
return 0;
}• lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
• 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
std::function是C++标准模板库(STL)中定义在<functional>头文件中的一个类模板。它能够存储、复制,并调用任何可调用对象,如普通函数、Lambda表达式、函数对象、以及其他函数封装器(如std::bind)的结果。这使得std::function成为一种通用的函数封装方式,极大地增强了函数的通用性和可扩展性。
std::function 是⼀个类模板,也是⼀个包装器。
std::function 的实例对象可以包装存储其他的可以调⽤对象,
包括函数指针、仿函数、 lambda 、 bind 表达式等,
存储的可调⽤对象被称为std::function 的⽬标。
若std::function 不含⽬标,则称它为空。
调⽤空std::function 的⽬标导致抛出std::bad_function_call异常。std::function的声明与定义std::function的声明形式如下:
template <class> class function; // 未定义
template <class Ret, class... Args>
class function<Ret(Args...)>; // 定义这里Ret表示函数返回值的类型,Args...表示函数参数的类型列表。例如,std::function<void()>表示一个没有参数且没有返回值的函数;std::function<int(int, int)>表示一个接受两个int参数并返回一个int值的函数。
函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯的第⼆个代码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。特别要注意的是在调用普通成员函数
#include<functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
// 包装各种可调⽤对象
function<int(int, int)> f1 = f;
function<int(int, int)> f2 = Functor();
function<int(int, int)> f3 = [](int a, int b) {return a + b; };
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
// 包装静态成员函数
// 成员函数要指定类域并且前⾯加&才能获取地址
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
cout << f6(pd, 1.1, 1.1) << endl;
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
return 0;
}bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在这个头⽂件中。
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);std::bind 的原理基于模板和函数对象。它通过模板参数推导,将函数和参数封装成一个函数对象。这个函数对象重载了 operator(),当调用这个函数对象时,它会按照绑定时的参数和函数进行调用。 在实现上,std::bind 使用了完美转发和模板元编程技术。它能够处理各种类型的参数,包括左值、右值、引用等,并且能够正确地将参数转发到目标函数中。
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
// bind 本质返回的⼀个仿函数对象
// 调整参数顺序(不常⽤)
// _1代表第⼀个实参
// _2代表第⼆个实参
// ...
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
// 调整参数个数 (常⽤)
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
// 分别绑死第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;
// 成员函数对象进⾏绑死,就不需要每次都传递了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;
// bind⼀般⽤于,绑死⼀些固定参数
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
}