[c++11(二)]Lambda表达式和Function包装器及bind函数

1.前言

Lambda表达式着重解决的是在某种场景下使用仿函数困难的问题,而function着重解决的是函数指针的问题,它能够将其简单化。

本章重点:

本章将着重讲解lambda表达式的规则和使用场景,以及function的使用场景及bind函数的相关使用方法。

2.为什么要有Lambda表达式

在C++98中,对自定义类型进行排序时,需要自己写仿函数,并传递给sort库函数

但是如果每次要按照自定义类型的不同成员变量进行排序的话,就要写很多个仿

函数,十分的不方便,于是C++11给出了一个新玩法:

cpp 复制代码
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
}
表达式部分说明:

capture-list\] : **捕捉列表** ,该列表总是出现在 lambda 函数的开始位置, **编译器根据\[\]来** **判断接下来的代码** **是否为lambda函数** , **捕捉列表能够捕捉上下文中的变量供** **lambda** **函数使用** 。 (parameters) :参数列表。与 **普通函数的参数列表一致** ,如果不需要参数传递,则可以 连同 () 一起省略 mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量 性。 **使用该修饰符时,参数列表不可省略(即使参数为空)。** **-\>returntype** **:返回值类型** 。用 **追踪返回类型形式声明函数的返回值类型** ,没有返回 值时此部分可省略。 **返回值类型明确情况下,也可省略,由编译器对返回类型进行推** **导** **{statement}** **:函数体** 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获 到的变量。 > **注意:** > 在 lambda 函数定义中, **参数列表和返回值类型都是可选部分** **,而捕捉列表和函数体可以为** **空** 。因此 C++11 中 **最简单的** **lambda** **函数为:** **\[\]{}** ; 该 lambda 函数不能做任何事情。 例: ```cpp int main() { // 最简单的lambda表达式, 该lambda表达式没有任何意义 []{}; // 省略参数列表和返回值类型,返回值类型由编译器推导为int int a = 3, b = 4; [=]{return a + 3; }; // 省略了返回值类型,无返回值类型 auto fun1 = [&](int c){b = a + c; }; fun1(10) cout<int{return b += a+ c; }; cout< 通过上述例子可以看出, lambda 表达式实际上可以理解为无名函数,该函数无法直接调 > 用,如果想要直接调用,可借助 auto 将其赋值给一个变量。 ## **4.Lambda表达式的捕捉列表** lambda表达式的捕捉列表\[ \]可以捕捉父作用域的变量供自己使用。 规则如下: ![](https://i-blog.csdnimg.cn/direct/b1e42aaac5824cf997e2f64bd4aa7a4f.png) ```cpp #include #include #include int main() { int x = 10; int y = 20; std::vector v = {1, 2, 3, 4, 5}; // 混合捕获 std::vector 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` 的引用。 注意: ![](https://i-blog.csdnimg.cn/direct/946a2a024fbf45b4875e9ededd990e3b.png) lambda表达式之间不能相互赋值,即使看起来类型是相同的。 > **lambda表达式的使用方法和仿函数非常相似,实际在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理的即:如果定义了一个lambda表达式, > 编译器会自动生成一个类,在该类中重载了operator()** **mutable关键字详解:** 由于lambda表达式是具有常属性的,所以在通常的情况下是无法被修改的,因此在C++14中引入了`mutable`关键字,可以用于Lambda表达式中,以允许Lambda表达式修改捕获的变量。 例: ```cpp #include 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包装器** function 包装器 也叫作适配器。 **C++中的function本质是一个类模板,也是一个包装器。** 那么我们来看看,我们为什么需要 function 呢? ```cpp ret = func(x); // 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)? //也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型 //可能会导致模板的效率低下! template 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函数的使用方法: ![](https://i-blog.csdnimg.cn/direct/3e506862e85f42a5ab48c87d9a8ee017.png) 第一个int表示返回值,()里面的int表示参数的类型。 回到上述要解决的问题,解决方式如下: ```cpp #include template 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 func1 = f; cout << useF(func1, 11.11) << endl; // 函数对象 std::function func2 = Functor(); cout << useF(func2, 11.11) << endl; // lamber表达式 std::function func3 = [](double d)->double{ return d / 4; }; cout << useF(func3, 11.11) << endl; return 0; } ``` ## **6.function包装器的使用场景** 例如:1.可以存储在容器中,使得可以动态地管理和调用函数。 ```cpp #include #include #include int main() { std::vector> 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` 类型的函数,并在运行时依次调用这些函数。 > 在操作系统中,不同的线程要执行不同的函数的话,那么就可以用这种方式 来进行封装并且调用函数。 2.函数适配器 `std::function` 可以用于创建函数适配器,使得可以将不同类型的函数适配为统一的接口。 ```cpp #include #include 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 adapterA = functionA; std::function adapterB = functionB; adapterA(10); adapterB(3.14); return 0; } ``` > 在这个例子中,`adapterA` 和 `adapterB` 分别适配了不同类型的功能函数,使得它们可以统一调用。 ## **7.bind函数** std::bind 函数定义在头文件中, **是一个函数模板,它就像一个函数包装器** **(** **适配器** **)** , **接受一个可** **调用对象(** **callable object** **),生成一个新的可调用对象来** **"** **适应** **"** **原对象的参数列表** 。一般而 言,我们用它可以把一个原本接收 N 个参数的函数 fn ,通过绑定一些参数,返回一个接收 M 个( M 可以大于 N ,但这么做没什么意义)参数的新函数。同时,使用 std::bind 函数还可以实现参数顺 序调整等操作。 ```cpp // 原型如下: template /* unspecified */ bind (Fn&& fn, Args&&... args); // with return type (2) template /* unspecified */ bind (Fn&& fn, Args&&... args); ``` **可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对** **象来"适应"原对象的参数列表。** **调用bind的一般形式:auto newCallable = bind(callable,arg_list);** **其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的** **callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中** **的参数。** 在正式使用bind函数之前,先介绍一下 `std::placeholders` 占位符 `std::placeholders` 提供占位符 `_1`, `_2`, `_3` 等,用于表示函数调用时的参数位置。 例如: ```cpp #include #include 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. 绑定带有默认参数的成员函数** ```cpp #include #include 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.绑定函数对象 ```cpp #include #include 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函数到这就讲解完毕了。**

相关推荐
YKPG3 分钟前
C++学习-入门到精通【13】标准库的容器和迭代器
c++·学习·stl
早日退休!!!12 分钟前
C++性能优化指南
开发语言·c++·性能优化
当归10241 小时前
Fuse.js:打造极致模糊搜索体验
开发语言·javascript·ecmascript
一只小小汤圆1 小时前
c# 显示正在运行的线程数
开发语言·c#
vvilkim2 小时前
深入理解C# MVVM模式:从理论到实践
开发语言·c#
magic 2452 小时前
return this;返回的是谁
java·开发语言
egoist20232 小时前
【Linux仓库】冯诺依曼体系结构与操作系统【进程·壹】
linux·运维·服务器·开发语言·操作系统·冯诺依曼体系结构
快乐肚皮2 小时前
深入解析Java17核心新特性(密封类、模式匹配增强、文本块)
开发语言·java17·密封类·模式匹配增强·文本块
钟离墨笺3 小时前
Go语言学习-->编译器安装
开发语言·后端·学习·golang
why1513 小时前
百度golang研发一面面经
开发语言·golang