[c++11(二)]Lambda表达式和Function包装器及bind函数
1.前言
Lambda表达式着重解决的是在某种场景下使用仿函数困难的问题,而function着重解决的是函数指针的问题,它能够将其简单化。
本章重点:
本章将着重讲解lambda表达式的规则和使用场景,以及function的使用场景及bind函数的相关使用方法。
2.为什么要有Lambda表达式
在C++98中,对自定义类型进行排序时,需要自己写仿函数,并传递给sort库函数
但是如果每次要按照自定义类型的不同成员变量进行排序的话,就要写很多个仿
函数,十分的不方便,于是C++11给出了一个新玩法:
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price < g2._price; });//按照价格升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price > g2._price; });//按照价格降序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate < g2._evaluate; });//按照评价升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate > g2._evaluate; });//按照评价降序
后面那一坨就完美的代替了仿函数。他其实就是传说中的lambda表达式
上述代码具体分析如下:
3.Lambda表达式的语法
lambda 表达式书写格式: [capture-list] (parameters) mutable -> return-type { statement}
注意:在 lambda 函数定义中, 参数列表和返回值类型都是可选部分 ,而捕捉列表和函数体可以为空 。因此 C++11 中 最简单的 lambda 函数为: []{} ; 该 lambda 函数不能做任何事情。
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
通过上述例子可以看出, lambda 表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助 auto 将其赋值给一个变量。
4.Lambda表达式的捕捉列表
lambda表达式的捕捉列表[ ]可以捕捉父作用域的变量供自己使用。
规则如下:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int x = 10;
int y = 20;
std::vector<int> v = {1, 2, 3, 4, 5};
// 混合捕获
std::vector<int> filtered;
std::copy_if(v.begin(), v.end() [x, &y](int z) { return z > x && z < y; });
// 输出过滤后的结果
for (int n : filtered) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
在这个例子中,[x, &y]
捕获 x
的值和 y
的引用。
注意:
lambda表达式之间不能相互赋值,即使看起来类型是相同的。
lambda表达式的使用方法和仿函数非常相似,实际在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理的即:如果定义了一个lambda表达式,
编译器会自动生成一个类,在该类中重载了operator()
mutable关键字详解:
由于lambda表达式是具有常属性的,所以在通常的情况下是无法被修改的,因此在C++14中引入了mutable
关键字,可以用于Lambda表达式中,以允许Lambda表达式修改捕获的变量。
例:
#include <iostream>
int main() {
int x = 10;
auto lambda = [=]() mutable { ++x; }; // 值捕获,允许修改
lambda();
std::cout << x << std::endl; // 输出: 10
return 0;
}
分析:为什么这里用了mutable之后,输出还是10呢?
简单理解就是:类比函数传参,你在这里传的只是x的副本,并不是真正的x,所以外部的x并没有被修改。
mutable
关键字的作用是允许 Lambda 表达式修改通过值捕获的变量。然而,值捕获的本质是复制外部变量的值到 Lambda 表达式的内部环境,
5.function包装器
ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?
//也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型
//可能会导致模板的效率低下!
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
cout << useF(f, 11.11) << endl;
// 函数对象
cout << useF(Functor(), 11.11) << endl;
// lamber表达式
cout << useF([](double d)->double{ return d/4; }, 11.11) << endl;
return 0;
}
这样的话一份useF就实例化出了三份代码,这样就比较low了。
那么如果用function的话,那么就可以提高效率了。
Function函数的使用方法:
第一个int表示返回值,()里面的int表示参数的类型。
回到上述要解决的问题,解决方式如下:
#include <functional>
template<class F, class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
int main()
{
// 函数名
std::function<double(double)> func1 = f;
cout << useF(func1, 11.11) << endl;
// 函数对象
std::function<double(double)> func2 = Functor();
cout << useF(func2, 11.11) << endl;
// lamber表达式
std::function<double(double)> func3 = [](double d)->double{ return d /
4; };
cout << useF(func3, 11.11) << endl;
return 0;
}
6.function包装器的使用场景
例如:1.可以存储在容器中,使得可以动态地管理和调用函数。
#include <iostream>
#include <vector>
#include <functional>
int main() {
std::vector<std::function<void()>> functions;
functions.push_back([]() { std::cout << "Function 1" << std::endl; });
functions.push_back([]() { std::cout << "Function 2" << std::endl; });
for (auto& func : functions) {
func();
}
return 0;
}
在这个例子中,functions
容器存储了多个 std::function<void()>
类型的函数,并在运行时依次调用这些函数。
在操作系统中,不同的线程要执行不同的函数的话,那么就可以用这种方式 来进行封装并且调用函数。
2.函数适配器
std::function
可以用于创建函数适配器,使得可以将不同类型的函数适配为统一的接口。
#include <iostream>
#include <functional>
void functionA(int x) {
std::cout << "Function A called with " << x << std::endl;
}
void functionB(double x) {
std::cout << "Function B called with " << x << std::endl;
}
int main() {
std::function<void(int)> adapterA = functionA;
std::function<void(double)> adapterB = functionB;
adapterA(10);
adapterB(3.14);
return 0;
}
在这个例子中,
adapterA
和adapterB
分别适配了不同类型的功能函数,使得它们可以统一调用。
7.bind函数
// 原型如下:
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::placeholders
占位符
std::placeholders
提供占位符
_1
,
_2
,
_3
等,用于表示函数调用时的参数位置。
#include <iostream>
#include <functional>
void print(int a, int b) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
int main() {
auto bound_func = std::bind(print, std::placeholders::_2, std::placeholders::_1);
bound_func(20, 10); // 输出: a: 10, b: 20
return 0;
}
在这个例子中,std::bind
将 print
函数的参数位置交换了,使得 _2
即func里面的第二个参数作为print的第一个参数,_1
即func里面的第一个参数作为第二个参数传递给 print
函数。
简单总结一下:
std::bind
是 C++ 标准库中的一个函数模板,用于绑定函数和对象,以便创建新的可调用对象。std::bind
可以用于创建适配器,将函数、成员函数、甚至是函数对象绑定到特定的参数,从而生成新的可调用对象。
8.bind函数的使用场景
1. 绑定带有默认参数的成员函数
#include <iostream>
#include <functional>
class MyClass {
public:
void print(int a, int b = 0) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
void bindAndCall() {
// 使用 std::bind 绑定成员函数和 this 指针
auto bound_func = std::bind(&MyClass::print, this, std::placeholders::_1, 20);
// 调用绑定后的函数
bound_func(10); // 输出: a: 10, b: 20
}
};
int main() {
MyClass obj;
obj.bindAndCall();
return 0;
}
2.绑定函数对象
#include <iostream>
#include <functional>
class PrintFunc {
public:
void operator()(int a, int b) {
std::cout << "a: " << a << ", b: " << b << std::endl;
}
};
int main() {
PrintFunc pf;
auto bound_func = std::bind(pf, std::placeholders::_1, 20);
bound_func(10); // 输出: a: 10, b: 20
return 0;
}
9.总结
lambda表达式和function包装器以及bind函数到这就讲解完毕了。