C++ 11相关新特性(lambda表达式与function包装器)

目录

lambda表达式

引入

lambda表达式介绍

lambda表达式捕捉列表的传递形式

lambda表达式的原理

包装器

包装器的基本使用

包装器与重载函数

包装器的使用

绑定


C++ 11 新特性

lambda表达式

引入

在C++ 98中,对于sort函数来说,如果需要根据不同的比较方式实现不同的排序结果,需要写不同的仿函数,而在C++ 11中,可以通过lambda表达式解决这个问题,例如下面的例子:

struct Goods
{string _name;  // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};

当需要按照商品的价格和评价排序时,则需要写两个仿函数

struct ComparePrice
{// 按照价格排序bool operator()(const Goods& g1, const Goods& g2){return g1._price < g2._price;}
};struct CompareEvaluate
{// 按照评价排序bool operator()(const Goods& g1, const Goods& g2){return g1._evaluate < g2._evaluate;}
};

调用时传递仿函数匿名对象

sort(v.begin(), v.end(), ComparePrice());
sort(v.begin(), v.end(), CompareEvaluate());

但是当需要按照其他方式进行比较时,需要再写其他的仿函数,为了简化步骤,可以使用lambda表达式

lambda表达式介绍

lambda表达式基本结构如下:

[捕捉列表](形式参数)mutable->返回值类型
{函数体
}
  • 捕捉列表:编译器根据[]来判断接下来的代码是否为lambda函数,用于传递在lambda表达式体内的使用到的参数,一般为lambda表达式所在的直接作用域的变量
  • 形式参数:用于lambda表达式体内的变量,如果不需要传递形式参数,则当前项可以省略不写,如果需要加mutable,则不论是有还是没有形式参数,都需要带上()
  • mutable:默认情况下lambda表达式捕捉列表的参数是被const修饰的,所以捕捉列表的参数是以传值的方式传递时是无法直接在lambda表达式内部进行修改的,但是如果加了mutable,就可以取消const属性
  • ->返回值类型:与普通的函数体一样的返回类型声明,如果lambda表达式的返回值类型比较明确时,该项可以不写
  • 函数体:同普通函数

有了lambda表达式,引入部分的例子中的仿函数可以用lambda表达式进行替换,如下:

// lambda表达式
sort(v.begin(), v.end(), [](Goods& g1, Goods& g2){return g1._price < g2._price;});
sort(v.begin(), v.end(), [](Goods& g1, Goods& g2){return g1._evaluate < g2._evaluate;});

lambda表达式捕捉列表的传递形式

如果没有形式参数传递,lambda表达式想使用其所在的直接作用域中的变量(全局除外)需要在捕捉列表中传递,在lambda表达式中,捕捉列表的传递形式一共有4种:

  1. 具体变量值传递[variable]:直接传递变量的值,在lambda表达式中就是对该变量的值进行拷贝,所以lambda表达式内部对variable修改时不影响variable本身的内容,并且在没有mutable的情况下不可以在内部对variable进行修改
  2. 具体变量引用传递[&variable]:以variable引用的方式传递,在lambda表达式中可以对variable内容进行修改,从而达到传址调用的效果
  3. 所有变量值传递[=]:将lambda表达式所在作用域中的变量全部以传值的方式传递给lambda表达式,具体传递了哪些值需要看lambda表达式中使用到了哪些值
  4. 所有变量引用传递[&]:将将lambda表达式所在作用域中的变量全部以传址的方式传递给lambda表达式,具体传递了哪些值需要看lambda表达式中使用到了哪些值,如果lambda内部需要进行修改,需要加mutable
对于第二种情况,如果想在lambda表达式内部修改lambda所在作用域的变量的值,可以在lambda表达式的形式参数部分以引用的方式传递实参,此时就可以不需要添加 mutable关键字,这个方法与普通函数的思路一致

上面4种方法也可以交错使用,例如下面的代码:

int main()
{int a = 0;int b = 0;int c = 0;// a,b以值传递,c以引用传递auto func = [=, &c](){// a和b是值传递,不能修改// a = 10;// b = 20;// c是引用传递,可以修改c = 30;};func();return 0;
}

lambda表达式的原理

前面通过lambda表达式简化了原本应该使用仿函数改写比较方式的例子展示了lambda表达式的使用,但是lambda表达式实际与仿函数的原理基本一致,所以lambda表达式也被称为匿名函数,观察下面的代码的反汇编代码

class Rate
{
public:Rate(double rate): _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};int main()
{// 创建普通对象double rate = 0.1;Rate r(rate);// 使用仿函数r(10000, 2);// 使用lambda表达式auto func = [=](double money, int year){return money * rate * year;};func(10000, 2);return 0;
}

反汇编如下:

需要注意的是,lambda表达式对象不可以相互转化,尽管完全相同,在底层两个逻辑一模一样的lambda表达式存在不同的lambda+uuid名称

包装器

包装器的基本使用

C++11中引入了function包装器,也叫做适配器,在前面有了lambda表达式后,可以发现如果直接调用lambda表达式的对象,其方式和函数的调用基本相同,但是前面的函数还有可能是仿函数,为了使程序的模版使用效率变高,可以使用包装器

使用包装器需要引入头文件 <functional>

包装器可以根据已有的函数、函数指针、lambda表达式进行包装,基本结构如下:

template <class T> function;     // undefined
template <class Ret, class... Args> 
class function<Ret(Args...)>;// 其中Ret代表指定的函数的返回值
// Args代表指定的函数的参数
需要注意,使用包装器必须保证包装器中的模版参数(返回值和形式参数)与被包装的对象完全相同

使用方式如下:

#include <functional>class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}static double calculate(double money, int year){return money * 0.1 * year;}
private:double _rate;
};double func(double money, int year)
{return money * 0.1 * year;
}int main()
{// 包装普通函数function<double(double, int)> func1 = func;func1(10000, 2);// 包装仿函数function<double(Rate, double, int)> func2 = &Rate::operator();func2(Rate(0.1), 10000, 2);function<double(double, int)> func3 = Rate::calculate;func3(10000, 2);// 包装lambda表达式function<double(double, int)> func4 = [=](double money, int year){return money * 0.1 * year;};func4(10000, 2);return 0;
}

上面代码中,包装普通函数与包装函数指针类似,包装成员函数需要注意两种形式:1. 静态成员函数 2. 非静态成员函数,对于静态成员函数来说,可以直接取地址,与普通函数类似,但是需要指定类域,而对于非静态成语函数来说,需要加上&,因为非静态成员函数存在一个隐含的参数this,需要依赖对象实例进行调用,所以非静态成员需要传递一个类名(或类指针)代表调用时需要传递同类类型的(匿名/非非匿名)对象(或对象地址),在调用包装后的函数时,实际上是通过对象进行调用,而不是包装器进行调用,对于lambda表达式来说,直接使包装器接受lambda表达式即可

包装器与重载函数

重载函数的根本条件就是必须满足函数名相同,但是此时如果直接使用函数名作为包装器的对象就会产生二义性问题,例如下面的代码:

#include <functional>
int add(int a, int b)
{return a + b;
}double add(double a, double b)
{return a + b;
}int main()
{map<int, function<int(int, int)>> m;m.insert({ 1, add });return 0;
}报错信息:
'std::_Tree<std::_Tmap_traits<_Kty,_Ty,_Pr,_Alloc,false>>::insert': no overloaded function could convert all the argument types

在上面的代码中,map的模版参数是intfunction<int(int, int)>,代码中也存在对应包装器模版类型的add函数,但是编译器并不会自动选择对应的重载函数,所以在出现重载函数时,推荐使用函数指针对重载函数进行指代,再传入函数指针,避免传入重载函数的函数名,另外也可以使用lambda表达式,从而不使用函数重载,例如下面的代码:

int main()
{map<int, function<int(int, int)>> m;//m.insert({ 1, add }); 直接插入导致二义性// 使用函数指针指代需要插入的函数int (*pint)(int, int) = add;m.insert({ 1, pint });// 使用lambda表达式m.insert({ 2, [](int a, int b) {return a + b; } });return 0;
}

包装器的使用

C++形式的转移表,以实现简易计算器为例:

下面的代码是用于计算的函数:

int add(int a, int b)
{return a + b;
}int sub(int a, int b)
{return a - b;
}int divide(int a, int b)
{return a / b;
}int multiply(int a, int b)
{return a * b;
}

常规写法:

int main()
{// 处理操作数和操作符输入int num1 = 0;int num2 = 0;char opt = 0;int flag = 1;// 处理计算while (flag && cin >> opt){switch (opt){case '+':cin >> num1 >> num2;cout << add(num1, num2) << endl;break;case '-':cin >> num1 >> num2;cout << sub(num1, num2) << endl;break;case '*':cin >> num1 >> num2;cout << multiply(num1, num2) << endl;break;case '/':cin >> num1 >> num2;cout << divide(num1, num2) << endl;break;default:flag = 0;break;}if (flag == 0){break;}}return 0;
}

使用包装器后:

#include <functional>int main()
{map<char, function<int(int, int)>> m{ {'+', add}, {'-', sub}, {'*', multiply}, {'/', divide} };int num1 = 0;int num2 = 0;char opt = 0;while (cin >> opt){if (m.count(opt)) // 如果count为1,代表map中存在对应的键值对{cin >> num1 >> num2;cout << m[opt](num1, num2) << endl;// 返回的value是包装器,直接传参即可调用对应}else{break;}}return 0;
}

绑定

在C++ 11中,增加了绑定配合包装器的使用,包装器可以实现两种功能:

  1. 改变实参在传参时的顺序
  2. 固定形参中的某一个值
使用 bind时需要展开命名空间 placeholders,因为要使用其中的 _1_2...
placeholders中的内容表示调用绑定函数的实际参数, _1代表第一个实际参数, _2代表第二个实际参数,以此类推
  • 改变实参在传参时的顺序
#include <functional>
using namespace placeholders;int sub(int a, int b, int c)
{return a - b - c;
}int main()
{// 1. 改变实际参数顺序function<int(int, int, int)> func = sub;cout << func(1, 2, 3) << endl;// 绑定改变顺序func = bind(func, _2, _3, _1);// 改变后的传递顺序为:2, 3, 1cout << func(1, 2, 3) << endl;return 0;
}输出结果:
-4
-2

传递顺序改变如下图所示:

注意, bind改变的是实际参数的传递顺序,而不是形参的接收顺序,形参接收还是按照从左到右依次接收传递的实际参数,只是写的第一个实际参数(本应该传递给形参a)被 bind改变作为第三个实际参数,传递给形参 c,依次类推 ab
  • 固定形参中的某一个值

在前面使用function包装器调用非静态成员函数时,需要单独传递一个对象给隐含的this指针,如果每一次传递都需要传递一个对象会显得繁琐,可以考虑将对象参数进行固定,例如下面的代码:

class Rate
{
public:Rate(double rate): _rate(rate){}double calculate(double money, int year){return money * _rate * year;}private:double _rate;
};int main()
{// 不使用bind下使用包装器function<double(Rate, double, int)> func1 = &Rate::calculate;cout << func1(Rate(0.1), 10000, 2) << endl;// 使用bind下使用包装器function<double(Rate, double, int)> func2 = &Rate::calculate;// 使用bind固定对象Rate(0.1)function<double(double, int)> func3 = bind(func2, Rate(0.1), _1, _2);cout << func3(10000, 2) << endl;return 0;
}输出结果:
2000
2000

上面代码中,需要注意尽管固定了func2的第一个参数,实际参数的指代还是从_1开始,如果固定中间的参数,则最左边的为_1,最右边的为_2(代码如下),以此类推

function<double(Rate, int)> func4 = bind(func2, _1, 10000, _2);
cout << func4(Rate(0.1), 2) << endl;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/403247.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自闭症青年的行为特征有哪些

自闭症&#xff0c;又称孤独症&#xff0c;是一种神经发育障碍&#xff0c;它不仅影响儿童的成长&#xff0c;也会在青年时期展现出一系列独特的行为特征。了解这些特征对于更好地支持和帮助自闭症青年融入社会至关重要。 社交互动方面的困难是自闭症青年较为显著的特征之一。他…

Kubectl 常用命令汇总大全

kubectl 是 Kubernetes 自带的客户端&#xff0c;可以用它来直接操作 Kubernetes 集群。 从用户角度来说&#xff0c;kubectl 就是控制 Kubernetes 的驾驶舱&#xff0c;它允许你执行所有可能的 Kubernetes 操作&#xff1b;从技术角度来看&#xff0c;kubectl 就是 Kubernetes…

openEuler系统安装Visual Studio Code

openEuler系统安装Visual Studio Code 背景安装密钥和存储库更新包缓存并使用dnf安装包Fedora 22及以上版本旧版本使用yum 安装过程截图安装成功看桌面效果 背景 openEuler(openEuler-24.03-LTS)安装了麒麟UKUI桌面但是没有麒麟软件商店想安装Visual Studio Code 安装密钥和…

专业剪辑新选择!2024年TOP榜达芬奇剪辑软件VS三大劲敌的较量

到了2024年&#xff0c;科技飞快地进步&#xff0c;视频剪辑这一块儿也变了不少。老的剪辑方法一直被刷新&#xff0c;新的软件一个接一个冒出来&#xff0c;像达芬奇剪辑软件这样的&#xff0c;都成了拍视频的人的好伙伴。咱们今天就来聊聊这几款软件有啥神奇的&#xff0c;比…

安全基础学习-RC4加密算法

这里仅仅记录一些基础的概念。后期有需求进一步扩展。 RC4 是一种对称流加密算法&#xff0c;由罗恩里维斯特&#xff08;Ron Rivest&#xff09;于1987年设计。RC4 的设计目的是提供一种简单且高效的加密方法。尽管 RC4 曾经广泛使用&#xff0c;但它的安全性在现代已受到质疑…

Modbus 通信协议详解

目录 一、概述二、Modbus 的作用三、Modbus 的工作原理1、四种数据类型2、三种工作模式3、三类功能码3.1 标志功能码3.2 Modbus 封装接口3.3 异常 4、Modbus 协议层4.1 协议数据单元4.2 访问数据4.3 数据模型寻址4.3.1 数据寻址范围4.3.2 数据地址起始值 4.4 大数据类型4.4.1 位…

Java面试题———分布式篇

目录 1、什么是分布式事务 2、什么是CAP理论 3、为什么分布式系统中无法同时AC 4、什么是BASE理论 5、分布式事务的解决方案有哪些 6、Seata的架构是什么 7、XA模式的工作流程是什么 8、AT模型的工作原理是什么 9、TCC模型的工作原理是什么 1、什么是分布式事务 在分…

java:实现简单的验证码功能

效果 实现思路 验证码图片的url由后端的一个Controller生成&#xff0c;前端请求这个Controller接口的时候根据当前时间生成一个uuid&#xff0c;并把这个uuid在前端使用localStorage缓存起来&#xff0c;下一次还是从缓存中获取。 Controller生成验证码之后&#xff0c;把前…

spring-boot-3.2.6+spring-security-6.2.4+oauth2整合github示例

一、添加依赖 在 pom.xml 中添加如下依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"h…

【GD32】从零开始学GD32单片机 | PMU电源管理单元+深度睡眠和待机例程(GD32F470ZGT6)

1. 简介 PMU电源管理单元通俗讲就是用来管理MCU的电源域的&#xff0c;它主要有两个功能——电压监测和低功耗管理。在GD32中一共有3个电源域——VDD/VDDA域、1.2V域和备份域。 VDD/VDDA域主要供PMU控制器、ADC、DAC等外设使用&#xff1b;1.2V域就是大部分外设都会使用的电源域…

西安电子科技大学萌新智慧指南(校区篇)

本次是西安电子科技大学南校区【本部南校区】 刚刚进入校园 相信大家对校园环境还很陌生 接下来就用一张地图 带大家迅速了解一下南校区的构造 宿舍 学生宿舍主要分为三部分 竹园公寓 1-4 海棠公寓 5-10 丁香公寓 11-15 研究生们主要居住在 海棠续建5、丁香14、丁香1…

24年日语能力(JLPT)考试报名流程图解

报名方式 搜索JLPT中国教育考试网&#xff0c;在线报名&#xff0c;一般学生党从教育网入口登录&#xff0c;社会人士从公网入口登录 报名时间 N1-N5 8月20日 7:00 - 8月27日14:00 注册时间 8月13日7:00 - 8月27日14:00 报名步骤 阅读报考提示&#xff0c;注册个人信息→…

C++适配windows和linux下网络编程TCP简单案例

C网络编程 网络协议是计算机网络中通信双方必须遵循的一套规则和约定&#xff0c;用于实现数据的传输、处理和控制。这些规则包括了数据格式、数据交换顺序、数据处理方式、错误检测和纠正等。网络协议是使不同类型的计算机和网络设备能够相互通信的基础&#xff0c;是网络通信…

c语言中比较特殊的输入函数

目录 一.getchar()函数 1.基本功能 2.使用方法 (1).读取单个字符 (2).读取多个字符&#xff08;直到遇到换行符&#xff09; (3).处理输入中的空白字符 3.返回值 4.应用场景 5.注意事项 二.fgets()函数 1.函数原型 2.工作原理 3.使用示例 (1).从标准输入读取一行…

HarmonyOS NEXT - 通过 module 模块化引用公共组件和utils

demo 地址: https://github.com/iotjin/JhHarmonyDemo 代码不定时更新&#xff0c;请前往github查看最新代码 HarmonyOS NEXT 一、HAP & HSP & HAR介绍HAP官方介绍HAR官方介绍HSP官方介绍怎么理解App、HAP、HAR的关系HAR如何转换为HSPHSP模块如何快速切换成HAR模块 二…

PDF转markdown工具:magic-pdf

1. magic-pdf 环境安装 conda create -n MinerU python3.10 conda activate MinerU pip install boto3>1.28.43 -i https://pypi.tuna.tsinghua.edu.cn/simple/ pip install magic-pdf[full]0.7.0b1 --extra-index-url https://wheels.myhloli.com -i https://pypi.tuna.t…

《深入浅出多模态》(八)多模态经典模型:MiniGPTv4

🎉AI学习星球推荐: GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料,配有全面而有深度的专栏内容,包括不限于 前沿论文解读、资料共享、行业最新动态以、实践教程、求职…

【el-table】横向滚动条加粗后,滚动到固定列下被遮挡,已解决

横向滚动条按要求加粗后&#xff0c;遇到的问题&#xff1a;列表的操作列是固定在最右侧的&#xff0c;当滚动条滑动到最右侧的时候&#xff0c;滚动条被遮挡了 我尝试了几种方法都不行&#xff0c;比如找到.el-table__fixed-right .el-table__fixed-footer-wrapper &#xff…

智能监控,无忧仓储:EasyCVR视频汇聚+AI智能分享技术为药品仓库安全保驾护航

随着科技的飞速发展&#xff0c;药品仓库的安全管理正迎来前所未有的变革。药品作为直接关系到公众健康的重要物资&#xff0c;其安全存储和监管显得尤为重要。在这个背景下&#xff0c;视频汇聚平台EasyCVR视频智能管理系统的应用&#xff0c;为药品仓库的安全监管提供了强有力…

el-tree树状控件,定位到选中的节点的位置

效果图 在el-tree 控件加 :render-content"renderContent" 在掉接口的方法中 实际有用的是setTimeout 方法和this.$refs.xxxxxx.setCheckedKeys([industrycodeList]) if(res.data.swindustrylist.length>0){res.data.swindustrylist.forEach(item > {industry…