【C++11(下)】—— 我与C++的不解之缘(三十二)

【C++11(下)】—— 我与C++的不解之缘(三十二)

前言

随着 C++11 的引入,现代 C++ 语言在语法层面上变得更加灵活、简洁。其中最受欢迎的新特性之一就是 lambda 表达式(Lambda Expression),它让我们可以在函数内部直接定义匿名函数。配合 std::function 包装器 使用,可以大大提高代码的表达力与可维护性。

一、lambda 表达式基础语法

lambda表达式本质上就是一个匿名函数对象,与普通函数不同的是,它可以定义在函数内部;

一般情况下我们是使用auto或者模版参数定义的对象去接受lambda对象。

lambda表达式本质上是一个可调用对象(函数对象),其语法格式如下:

[capture](parameter_list)-> return_type { function_body };

各部分含义如下:

部分含义
[]捕捉列表(capture list)
()参数列表(与函数类似)
-> return_type返回类型(可省略,自动推导)
{}函数体
intmain(){auto add =[](int a,int b)->int{return a + b;}; std::cout <<add(2,3);// 输出:5}

也可以省略 -> int,由编译器自动推导:

intmain(){auto add =[](int a,int b){return a + b;}; std::cout <<add(2,3);// 输出:5}
这里呢,参数列表,如果不需要传参,可以省略,()也可以省略;返回值类型可以省略,让编译器自行推导。

而参数列表和函数体,就算为空,参数列表的[]和函数体的{}也不能省略。

捕获列表

捕获列表决定了 Lambda 表达式如何访问其所在作用域的变量

捕获方式语法说明
值捕捉[x]捕获变量 x 的当前值(拷贝)
引用捕捉[&x]捕获变量 x 的引用
隐式值捕捉[=]lambda使用了哪些变量,编译器就会对哪些变量进行值捕捉
隐式引用捕捉[&]lambda使用了哪些变量,编译器就会对哪些变量进行引用捕捉
混合捕捉[=, &y]除 y 外的所有变量为值捕获,y 为引用捕获
混合捕捉[&, x]x外的所有变量为引用捕捉,x为值捕捉
intmain(){int x =10, y =20;auto f1 =[=](){return x + y;};// 值捕获auto f2 =[&](){ x += y;};// 引用捕获,修改外部变量auto f3 =[x,&y](){ y += x;};// 混合捕获return0;}

这里lambda表达式如果在函数局部域中,它可以捕捉lambda位置之前定义的变量,但是不能捕捉静态局部变量和全局变量(静态局部变量和全局变量也不需要捕捉,lambda表达式中也可以直接使用)。

如果lambda定义在全局,那捕捉列表必须为空

值捕获的变量在 Lambda 中是“只读”的,不能修改,除非加上 mutable

mutable 关键字

默认情况下,lambda捕捉列表的值是被const修饰的,值捕获的变量不能在 Lambda 中被修改。要想修改值捕获的副本,可以使用 mutable

intmain(){int a =5;auto f =[a]()mutable{ a +=10;// 修改的是a的拷贝,不影响外部 a cout << a << endl;};f();// 15 std::cout << a << endl;// 5return0;}

lambda 的实际应用场景

与 STL 算法结合

std::vector<int> vec ={1,2,3,4,5}; std::for_each(vec.begin(), vec.end(),[](int x){ std::cout << x <<" ";});

条件查找

auto it = std::find_if(vec.begin(), vec.end(),[](int x){return x >3;});if(it != vec.end()) std::cout <<*it;// 输出 4

排序自定义规则

std::sort(vec.begin(), vec.end(),[](int a,int b){return a > b;// 降序});

lambda的原理

lambda的原理和范围for非常相似,编译之后从汇编的角度来看,我们就要发现根本就没有lambda范围for

范围for的底层是迭代器,而lambda的底层是仿函数对象,简单来说我们写了一个lambda,编译器就会生成一对应的仿函数。

而仿函数的类名是编译器按照一定的规则生产的,保证不同的lambda生成的仿函数不同。lambda的参数/返回值/函数体就是仿函数operator()的参数/返回类型/函数体lambda捕捉列表的本质是生成的仿函数的成员变量。

简单来说就是捕捉列表的量都是lambda类构造函数的实参;

这样就很好解释值捕捉引用捕捉了:

值捕捉:lambda生成的仿函数的成员就是对捕捉变量的值拷贝。引用捕捉:lambda生成的仿函数的成员就是对捕捉变量的引用。

而也支持隐式捕捉,这个就是编译器看需要用哪些对象,就传哪些对象(用多少捕捉多少)。

在这里插入图片描述

二、包装器

function

function是一个类模板,也是一个包装器。std::function的实例化对象可以包装存储其它可以调用的对象:包括函数指针仿函数lambdabind表达式;

其中存储的对象被称为std::function的目标;如果std::function不含目标,那它为空(调用空目标导致抛出std::bad_function_call异常。
template<classT>classfunction;// undefinedtemplate<classRet,class... Args>classfunction<Ret(Args...)>;

以上是 function 的原型,他被定义头⽂件中

#include<functional>intmain(){ function<int(int,int)> func; func =[](int a,int b){return a + b;}; cout <<func(3,4)<< endl;// 输出 7return0;}
function 的优势在于统一函数接口、做函数回调或作为参数传递。

函数指针、仿函数、lambda这些可以调用的对象的类型各不相同,std::function的优势就在于统一类型;

只要它们返回值,参数都相同,function就能对它们进行包装;这样在很多的地方就可以声明这些可调用对象的类型;

intfun(int a,int b){return a + b;}structFun{public:intoperator()(int a,int b){return a + b;}};classPlus{public:Plus(int n =10):_n(n){}staticintplusi(int a,int b){return a + b;}doubleplusd(double a,double b){return(a + b)* _n;}private:int _n;};intmain(){ function<int(int,int)> f1 = fun;//函数指针 function<int(int,int)> f2 =Fun();//仿函数 function<int(int,int)> f3 =[](int a,int b){return a + b;};//lambda 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包装后需要传对象或者对象的指针过去才能进行调用 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; 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;return0;}

这里再来看一道可以使用function包装优化的题目

150. 逆波兰表达式求值 - 力扣(LeetCode)

classSolution{public:intevalRPN(vector<string>& tokens){ stack<int> st;for(auto& e:tokens){if(e =="+"||e =="-"|| e =="*"|| e =="/"){int right = st.top(); st.pop();int left = st.top(); st.pop();switch(e[0]){case'+': st.push(left + right);break;case'-': st.push(left - right);break;case'*': st.push(left * right);break;case'/': st.push(left / right);break;}}else{ st.push(stoi(e));}}return st.top();}};

这道题,向上述这样写,特别难受好吧,我们可以使用function进行优化:

我们知道+-*/运算它返回值和参数类型都是相同的,那我们不妨将其包装起来;

然后使用map存储运算符和对应的函数/调用对象

这样直接使用map[]就可以访问到要调用的函数/对象。
classSolution{public:intevalRPN(vector<string>& tokens){ stack<int> st; map<string,function<int(int,int)>> mp ={{"+",[](int x,int y){return x+y;}},{"-",[](int x,int y){return x-y;}},{"*",[](int x,int y){return x*y;}},{"/",[](int x,int y){return x/y;}}};for(auto& e:tokens){if(mp.count(e))//mp中存在就代表是操作符{int right = st.top(); st.pop();int left = st.top(); st.pop(); st.push(mp[e](left,right));}else st.push(stoi(e));}return st.top();}};

这样我们代码看起来简洁了好多,用起来也很方便;

如果再多几个运算符,我们只需要在mp再新增即可。

从这个角度来看:lambda算是统一了那些可调用对象的类型,这样对于可调用对象(函数指针仿函数lambda),只要参数和返回值相同,那我们就可以使用function包装起来,方便调用。

bind 与占位符

simple(1)template<classFn,class... Args>bind(Fn&& fn, Args&&... args); with returntype(2)template<classRet,classFn,class... Args>bind(Fn&& fn, Args&&... args);
bind是一个函数模版,它也是一个可调用对象的包装器;简单来说它就是一个函数适配器,可以对接受的Fn可调用对象进行处理后返回一个可调用对象。bind可以用来调整参数个数和参数的顺序。bind也在这个头文件中。
intFun(int a,int b){return(a - b)*10;}intFunc(int a,int b,int c){return(a - b - c)*10;}intmain(){//这里_1始终指接受第一个实参//_2指接受第二个实参auto fun1 =bind(Fun, _1, _2); cout <<fun1(10,5)<< endl;//fun1(10,5) -> Fun(10,5)auto Fun2 =bind(Fun, _2, _1); cout <<Fun2(10,5)<< endl;//fun2(10,5) -> Fun(5,10)auto fun3 =bind(Fun,100, _1); cout <<fun3(5)<< endl;//fun3(5) -> Fun(100,5)auto fun4 =bind(Fun, _1,100); cout <<fun4(5)<< endl;//fun4(5) -> Fun(5,100)auto fun5 =bind(Func,100, _1, _2); cout <<fun5(5,1)<< endl;//fun5(5,1) -> Func(100,5,1)auto fun6 =bind(Func, _1,100, _2); cout <<fun6(5,1)<< endl;//fun6(5,1) -> Func(5,100,1)auto fun7 =bind(Func, _1, _2,100); cout <<fun7(5,1)<< endl;//fun7(5,1) -> Func(5,1,100);return0;}
调用bindauto newCallable = bind(callable, arg_list)(这里newCallable本身就是一个可调用对象,arg_list是参数列表,对应给定的callable的参数(也是可调用对象)

这样我们调用newCallablenewCallable就会调用callable,并传给它arg_list中的参数。

当我们使用function去包装类的非静态成员函数时,我们在调用时总是需要传该类型的对象或者该类型对象的指针,来完成调用;

这样的设计好难看,我每一次调用还要创建一个该类型的对象,那我还不如直接去调用呢

 function<double(Plus&&,double,double)> f1 =&Plus::plusd; Plus pd; cout <<f1(move(pd),1.1,1.1)<< endl; cout <<f1(Plus(),1.1,1.1)<< endl;
bind这个绑定,我们可以同来绑定一些固定的参数;

就比如这里需要传递该类类型的对象或者该类型对象的指针,我们使用bind绑定,直接锁死这个参数,那这样在调用时就不用显示传递了。
 function<double(double,double)> f2 =bind(&Plus::plusd,Plus(), _1, _2); cout <<f2(1.1,1.1)<< endl;

到这里本篇文章内容就结束了
感谢各位的支持

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

Read more

Python 与数据科学工具链入门:NumPy、Pandas、Matplotlib 快速上手

Python 与数据科学工具链入门:NumPy、Pandas、Matplotlib 快速上手

Python 与数据科学工具链入门:NumPy、Pandas、Matplotlib 快速上手 “工欲善其事,必先利其器。” ——在机器学习的世界里,你的“器”就是 Python 数据科学工具链。 一、为什么工具链如此重要? 想象你要做一道菜。即使你背熟了所有食谱,如果厨房里只有生锈的刀、没校准的秤、漏底的锅,你依然做不出好菜。 机器学习也是如此。 算法是“菜谱”,而 NumPy、Pandas、Matplotlib 就是你的“刀、秤、锅”——它们构成了现代数据科学工作的基础设施。 很多初学者一上来就急着学“神经网络”“梯度提升”,却连如何读取一个 CSV 文件都磕磕绊绊。结果是:想法很丰满,代码跑不动。 本篇文章的目标很明确: ✅ 让你在 2 小时内掌握三大核心库的基础用法; ✅ 能独立完成 数据加载 → 清洗

By Ne0inhk
Python 列表内存存储本质:存储差异原因与优化建议

Python 列表内存存储本质:存储差异原因与优化建议

文章目录 * 1. 问题引入:列表存储的内存 "膨胀" * 2. 理论存储与实际存储的差异 * 2.1 64位整数的存储差异 * 2.2 短字符串的存储差异 * 3. 列表的内存存储本质 * 3.1 相同元素列表内存少的核心原因:对象复用 * 3.1.1 小整数的缓存复用机制 * 3.1.2 字符串的驻留(Intern)机制 * 3.2 不同元素列表内存高的原因:对象重复创建 * 3.2.1 不同整数的内存开销 * 3.2.2 不同字符串的内存开销 * 4. 内存占用对比分析 * 5. 优化建议:利用对象复用减少内存开销 * 6. 总结

By Ne0inhk
[特殊字符] Python在CentOS系统执行深度指南

[特殊字符] Python在CentOS系统执行深度指南

文章目录 * 1 Python环境安装与配置问题 * 1.1 系统自带Python的限制 * 1.2 安装Python 3的常见问题及解决方案 * 1.3 SSL模块问题解决方案 * 1.4 环境变量配置与管理 * 1.5 软件集合(SCL)替代方案 * 2 包管理与虚拟环境问题 * 2.1 pip包管理器问题与解决方案 * 2.2 虚拟环境的最佳实践 * 2.3 依赖兼容性问题解决 * 2.4 虚拟环境目录结构理解 * 3 模块导入与路径问题 * 3.1 Python模块搜索路径机制 * 3.2 常见模块导入错误与解决 * 3.3 路径配置最佳实践 * 3.4 特殊模块问题处理 * 3.

By Ne0inhk
基于Python大数据旅游数据分析与推荐系统的爬虫 数据分析可视化系统

基于Python大数据旅游数据分析与推荐系统的爬虫 数据分析可视化系统

文章目录 * 摘要 * 技术亮点 * 项目简介 * 大数据系统开发流程 * 主要运用技术介绍 * 爬虫核心代码展示 * 结论 * 源码文档获取定制开发/同行可拿货,招校园代理 :文章底部获取博主联系方式! 摘要 该系统基于Python技术栈构建,整合了网络爬虫、大数据分析、机器学习推荐算法及可视化技术,旨在为旅游行业提供数据驱动的决策支持与个性化服务。 数据采集层采用Scrapy框架爬取主流旅游平台(如携程、TripAdvisor)的多维数据,包括景点信息、用户评论、价格动态及地理位置,通过反爬策略(动态IP代理、请求头模拟)确保数据完整性。数据存储使用MongoDB处理非结构化文本,MySQL管理结构化属性字段。 数据分析层基于Pandas与NumPy进行数据清洗(缺失值填充、异常值剔除)和特征工程(情感分析、热度指数计算)。结合PySpark实现分布式处理,对海量用户行为日志进行聚类分析(K-Means)与关联规则挖掘(Apriori算法),识别游客偏好与消费模式。 推荐系统层采用协同过滤(Surprise库)与内容推荐(TF-IDF向

By Ne0inhk