C++11 lambda表达式和包装器

C++11 lambda表达式和包装器

  • 一.lambda表达式
    • 1.lambda表达式的引入
    • 2.基本语法和使用
      • 1.基本语法
      • 2.使用
        • 1.传值捕捉的错误之处
        • 2.传引用捕捉
    • 3.lambda表达式的底层原理
    • 4.lambda的特殊之处
    • 5.lambda配合decltype的新玩法
  • 二.function包装器
    • 1.概念
    • 2.包装函数
      • 1.包装普通函数
      • 2.包装成员函数
    • 3.包装器的另一个应用:统一模板实例化的类型
    • 4.小小总结
  • 三.bind包装器
    • 1.概念
    • 2.bind包装器绑定固定参数
      • 1.无意义绑定
      • 2.绑定固定参数
    • 3.bind包装器调整传参顺序

C++11引入的语法当中
除了STL库当中的区别,还有几个很常用的语法
就是我们今天要介绍的lambda表达式和function包装器
(bind包装器不是特别常用,但是我们有些时候也可能会用到,因此我们也介绍一下)

一.lambda表达式

1.lambda表达式的引入

我们之前学习过函数指针和仿函数,它们都是一种回调函数
只不过仿函数的语法更加简单明了

但是仿函数有一个缺点:
实现它必须要定义一个类出来
例如:
我们要实现一个比较算法,对不同的商品按照价格分别升序和降序排序

#include <iostream>
using namespace std;
#include <algorithm>//sort的头文件
#include <vector>
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& left, const Goods& right)
    {
        return left._price < right._price;
    }
};

//按照价格降序
struct ComparePriceGreater
{
    bool operator()(const Goods& left, const Goods& right)
    {
        return left._price > right._price;
    }
};

int main()
{
    vector<Goods> v = { { "苹果", 2.5, 5 }, { "香蕉", 3.2, 4 }, { "橙子", 2.9, 3 }, { "菠萝", 1.5, 4 } };
    sort(v.begin(),v.end(), ComparePriceLess());
    for (auto& e : v)
    {
        cout << "商品名称: " << e._name << " 价格 :" << e._price << " 评价:" << e._evaluate << endl;
    }
    cout << endl;
    sort(v.begin(), v.end(), ComparePriceGreater());
    for (auto& e : v)
    {
        cout << "商品名称: " << e._name << " 价格 :" << e._price << " 评价:" << e._evaluate << endl;
    }
    cout << endl;
    return 0;
}

在这里插入图片描述
我们发现,仅仅是需要分别按照价格升序和降序排序就需要单独写两个类出来
那么如果我又有需求了:分别按照评分升序和降序等等…
那样的话还要再写几个类
而且仿函数的命名上面也有问题,
刚才我们命名的是ComparePriceLess和ComparePriceGreater
如果有人这么命名呢?
Compare1,Compare2…
那样的话代码可读性就很差了

因此C++11引入了lambda表达式来解决这一问题
我们先来用lambda表达式取代一下刚才的仿函数

int main()
{
    vector<Goods> v = { { "苹果", 2.5, 5 }, { "香蕉", 3.2, 4 }, { "橙子", 2.9, 3 }, { "菠萝", 1.5, 4 } };
    //按照价格升序
    sort(v.begin(), v.end(), [](const Goods& left, const Goods& right)
        {
            return left._price < right._price;
        });
    for (auto& e : v)
    {
        cout << "商品名称: " << e._name << " 价格 :" << e._price << " 评价:" << e._evaluate << endl;
    }
    cout << endl;
    //按照价格降序
    sort(v.begin(), v.end(), [](const Goods& left, const Goods& right)
        {
            return left._price > right._price;
        });
    for (auto& e : v)
    {
        cout << "商品名称: " << e._name << " 价格 :" << e._price << " 评价:" << e._evaluate << endl;
    }
    cout << endl;
    return 0;
}

在这里插入图片描述
此时我们每次调用sort的时候只需要传入一个lambda表达式
即可指明比较方式,大大简化代码并且提高了代码的可读性

2.基本语法和使用

1.基本语法

//按照价格升序
sort(v.begin(), v.end(), [](const Goods& left, const Goods& right)
{
    return left._price < right._price;
});

根据这个排序我们可以看出

lambda表达式的底层就是一个匿名函数
该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个对象,用那个对象来调用

在这里插入图片描述
在这里插入图片描述小例子:
在这里插入图片描述
lambda表达式中的捕捉列表可以捕捉上下文中的变量
被捕捉到的变量可以被lambda表达式使用,而且我们可以设置捕捉的方式是传值还是传引用
在这里插入图片描述

2.使用

下面我们用lambda表达式实现一下int类型的swap函数

1.传值捕捉的错误之处
int main()
{
    int a = 1, b = 2;
    //这里传值捕捉了a和b,所以后面调用myswap时无需传参
    auto myswap = [a, b]()
    {
        int tmp = a;
        a = b;
        b = tmp;
    };
    myswap();
    cout << "交换后 : " << " a: " << a <<" b: " << b << endl;
    return 0;
}

在这里插入图片描述
在这里插入图片描述
所以我们要加上mutable
在这里插入图片描述
可是交换之后a还是1,b还是2啊?
为什么?
因为我们是传值捕捉,形参的改变不会影响实参
因此mutable的意义就是提醒我们
1.如果你想要改变实参,请你传引用捕捉,传值捕捉的话改变的是形参不是实参
2.如果你不想改变实参,传值捕捉的话,需要加上mutable提醒别人这里是传值捕捉,不会影响实参

2.传引用捕捉

方案1:传值捕获a和b,参数列表无需传参
在这里插入图片描述
方案2:捕获列表不去捕获,参数列表传引用传参
在这里插入图片描述

3.lambda表达式的底层原理

其实lambda表达式的底层原理就是仿函数,对operator()进行了重载
就像是范围for的底层原理就是迭代器一样

struct Myswap
{
    void operator()(int& a, int& b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    }
};
int main()
{
    int a = 1, b = 2;
    //这里捕获列表不去捕获,参数列表需要传参
    auto myswap = [](int& a,int& b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    };
    int x = 9;//为了方便调试打断点看反汇编
    Myswap myswap2;
    myswap2(a, b);
    myswap(a,b);
    return 0;
}

在这里插入图片描述
在这里插入图片描述
这是因为每个lambda表达式的类型是不同的
(在VS下,lambda会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid>)
在这里插入图片描述
关于uuid
大家感兴趣的话可以百度一下
UUID
在这里插入图片描述

4.lambda的特殊之处

lambda不支持直接调用默认构造来构造对象
但是支持拷贝构造对象
在这里插入图片描述
在这里插入图片描述

5.lambda配合decltype的新玩法

sort是传入函数对象即可,可是对于priority_queue这种需要传入类的容器来说,lambda就没有用武之地了吗?
并不是的
在这里插入图片描述

//建一个小堆(注意:优先级队列:默认是大堆配less,小堆配greater)
auto f = [](int a, int b) {return a > b; };

priority_queue<int, vector<int>, decltype(f)> minheap(f);

在这里插入图片描述

二.function包装器

1.概念

在这里插入图片描述

2.包装函数

下面我们来使用一下function包装器
头文件是<functional>

1.包装普通函数

#include <iostream>
using namespace std;
#include <functional>

//1.函数
int add1(int a, int b)
{
	return a + b;
}

//2.仿函数
struct Add2
{
	int operator()(int a, int b)
	{
		return a + b;
	}
};

int main()
{
	//1.包装函数指针
	function<int(int, int)> f1;
	f1 = add1;
	cout << f1(1, 2) << endl;

	//2.包装仿函数
	function<int(int, int)> f2 = Add2();//Add2():匿名对象
	cout << f2(1, 2) << endl;

	//3.包装lambda表达式
	function<int(int, int)> f3 = [](const int a, const int b) {return a + b; };
	cout << f3(1, 2) << endl;
	return 0;
}

在这里插入图片描述

2.包装成员函数

在这里插入图片描述
在这里插入图片描述

可见:包装器的本质就是对各种可调用对象进行类型的统一

3.包装器的另一个应用:统一模板实例化的类型

在这里插入图片描述

//传入该函数模板的第一个参数可以是任意可调用对象:函数指针,仿函数,lambda表达式
//func中定义了静态变量count,每次调用时打印count的值和地址,可以用来判断多次调用时所调用的是否是用一个func函数
template<class A,class T>
T func(A a, T b)
{
	static int count = 0;
	cout << "count值: " << ++count << endl;
	cout << "count地址: " << &count << endl;
	return a(b);
}

//传入第二个参数相同的类型,但是传入的可调用对象的类型是不同的
//那么在编译阶段该模板函数就会被实例化多次
struct Func1
{
	double operator()(double d)
	{
		return d / 2;
	}
};

double func2(double d)
{
	return d / 2;
}

int main()
{
	//函数
	cout << func(func2, 2.2) << endl;
	//仿函数
	cout << func(Func1(), 2.2) << endl;
	//lambda表达式
	cout << func([](double d)->double {return d / 2; }, 2.2) << endl;
	return 0;
}

在这里插入图片描述
尽管三次调用传入的可调用对象的类型不同,
但是这三次调用对象的返回值和形参类型相同

因此我们就可以使用包装器来对这三个不同的可调用对象来进行包装,此时就可以只实例化一份func函数了
在这里插入图片描述

4.小小总结

function包装器可以统一可调用对象的类型
包装后明确了可调用对象的返回值和形参类型,更加方便使用者进行使用

三.bind包装器

1.概念

在这里插入图片描述
头文件:functional

2.bind包装器绑定固定参数

bind绑定可以跟function包装器集合使用
在这里插入图片描述

1.无意义绑定

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	//绑定函数Add,参数分别由调用func1的第一,二参数来指定
	function<int(int, int)> func1 = bind(Add, placeholders::_1, placeholders::_2);
	cout << func1(1, 2) << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述

2.绑定固定参数

在这里插入图片描述
在这里插入图片描述

3.bind包装器调整传参顺序

我们以Sub类为例
在这里插入图片描述
在这里插入图片描述
同理,普通函数也是如此
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上就是C++11 lambda表达式和包装器的全部内容,希望能对大家有所帮助!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Oracle篇】rman全库异机恢复:从RAC环境到单机测试环境的转移(第四篇,总共八篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

odoo10 编写审批拒绝弹窗

前言 在日常中有很多审批场景&#xff0c;例如请销假。审批拒绝的时候应该给出原因&#xff0c;此时&#xff0c;在form界面点击拒绝的时候应该弹出输入窗口。如下图所示。 编写模型 模块的目录下&#xff0c;新建wizard文件夹&#xff0c;然后直接创建一个models.py和views.p…

idea实用快捷键(持续更新...)

文章目录 1、快速输入try/catch/finally2、选中多个光标3、实现接口4、方法参数提示5、查看某个类的子类6、弹出显示查找内容的搜索框 1、快速输入try/catch/finally CtrlAltT 2、选中多个光标 ShiftAlt单机多选 End可以全部到行尾&#xff0c;Home则可以全部回到行首 3、实现接…

MySQL 使用方法以及教程

一、引言 MySQL是一个流行的开源关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;广泛应用于Web开发、数据分析等领域。它提供了高效、稳定的数据存储和查询功能。同时&#xff0c;Python作为一种强大的编程语言&#xff0c;也提供了多种与MySQL交互的库&#…

中国人工智能区域竞争力研究报告(2024)

来源&#xff1a;赛迪顾问 近期历史回顾&#xff1a;2024年NoETL开启自动化数据管理新时代白皮书.pdf 创新引领用户“换新生活”-从AWE2024看家电及消费电子行业发展趋势报告&#xff08;精简版&#xff09;.pdf 2024智能网联汽车“车路云一体化”规模建设与应用参考指南&#…

字节裁员!开启裁员新模式。。

最近&#xff0c;互联网圈不太平&#xff0c;裁员消息此起彼伏。而一向以“狼性文化”著称的字节跳动&#xff0c;却玩起了“低调裁员”&#xff0c;用一种近乎“温柔”的方式&#xff0c;慢慢挤掉“冗余”的员工。 “细水长流”&#xff1a;裁员新模式&#xff1f; 不同于以往…

FreeRTOS基础(九):FreeRTOS的列表和列表项

今天我们将探讨FreeRTOS中的一个核心概念——列表&#xff08;List&#xff09;和列表项&#xff08;List Item&#xff09;。在实时操作系统&#xff08;RTOS&#xff09;中&#xff0c;任务的管理和调度是至关重要的&#xff0c;而FreeRTOS使用列表来实现这一功能。列表可以说…

城市低空经济“链接力”指数报告(2024)

来源&#xff1a;城市进化论&火石创造 近期历史回顾&#xff1a;2024年NoETL开启自动化数据管理新时代白皮书.pdf 创新引领用户“换新生活”-从AWE2024看家电及消费电子行业发展趋势报告&#xff08;精简版&#xff09;.pdf 2024智能网联汽车“车路云一体化”规模建设与应用…

鬼刀画风扁平化粒子炫动引导页美化版

源码介绍 分享一款引导页,响应式布局&#xff0c;支持移动PC 添加背景图片&#xff0c;美化高斯模糊 &#xff0c;删除蒙版人物部分&#xff0c;更图片人物画风更美好 删除雪花特效 替换字体颜色 添加底备案号 预留友情连接 效果预览 源码下载 https://www.qqmu.com/3381.h…

总结2024/6/3

省流&#xff0c;蓝桥杯国优&#xff0c;还是太菜了&#xff0c;听说都是板子题但是还是写不出来&#xff0c;靠暴力好歹没有爆0&#xff0c;还是得多练&#xff0c;明年加油了

分享5款.NET开源免费的Redis客户端组件库

前言 今天大姚给大家分享5款.NET开源、免费的Redis客户端组件库&#xff0c;希望可以帮助到有需要的同学。 StackExchange.Redis StackExchange.Redis是一个基于.NET的高性能Redis客户端&#xff0c;提供了完整的Redis数据库功能支持&#xff0c;并且具有多节点支持、异步编…

Python中的元素相乘与矩阵相乘(附Demo)

目录 前言1. 元素相乘2. 矩阵相乘3. 差异 前言 深度学习的矩阵相乘引发的Bug&#xff0c;由此深刻学习这方面的相关知识 在Python中&#xff0c;特别是使用NumPy库时&#xff0c;元素相乘和矩阵相乘是处理数组和矩阵时的常见操作 1. 元素相乘 元素相乘是指对两个相同形状的…

Windows端口本地转发

参考 微软Netsh interface portproxy 命令 界面端口代理的 Netsh 命令 | Microsoft Learn 使用Windows系统的portproxy功能配置端口转发 使用Windows系统的portproxy功能配置端口转发-阿里云帮助中心 (aliyun.com) 将来自0.0.0.0地址对端口35623的访问转发到172.18.106.16…

Python中degrees怎么用

degrees() 函数可以将弧度转换为角度。 语法 以下是 degrees() 方法的语法&#xff1a; import math math.degrees(x) 注意&#xff1a;degrees() 是不能直接访问的&#xff0c;需要导入 math 模块&#xff0c;然后通过 math 静态对象调用该方法。 参数 x -- 一个数值。 返…

苹果设备mac/Paid/phone 下载使用anki记忆卡

安卓的设备直接可以下 如果你这个&#xff0c;如图。 首先点击下列网址&#xff0c;下载&#xff0c;在里面搜索anki记忆卡 https://www.i4.cn 下载好&#xff0c;打开应用软件爱思助手。搜索anki记忆卡&#xff0c;下载&#xff0c;然后用数据线一端连接电脑一端连接手机或者…

【C++练级之路】【Lv.23】C++11——可变参数模板、lambda表达式和函数包装器

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 一、可变参数模板1.1 参数包的概念1.2 参数包的展开1.3 emplace系列 二、lambda表达式2.1 lambda的格式2.2 捕…

二级指针简单介绍

我们之前学习的&#xff1a;变量的地址是存入指针变量中的&#xff0c;然而指针变量也是变量&#xff0c;是变量就有地址&#xff0c;那么指针变量的地址存放在哪里 &#xff1f; 这也就是二级指针 #include<stdio.h> int main() {int a10;int*p&a;int**pp&p;re…

假指纹与活体指纹检测

目录 1. 假指纹简介 2. 假指纹制作流程 3. 活体指纹检测 4. 活体指纹检测竞赛 1. 假指纹简介 随着科学技术的发展&#xff0c;指纹技术以各种各样的形式进入了我们的生活。在大多数情况下&#xff0c;指纹识别应用于移动设备和桌面设备解决方案&#xff0c;以提供安全方便的…

Linux命令篇(一):文件管理部分

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 1、cat命令常用参…

2.3Docker部署java工程

2.3Docker部署java工程 1.导入jar包 2.在Docker部署jdk&#xff08;容器名为myjdk17&#xff09; 3.修改jar包名 mv 原包名 新包名4. 配置启动脚本 Dockerfile是一个文本文件&#xff0c;其中包含了构建 Docker 镜像所需的一系列步骤和指令。通过编写 Dockerfile 文件&…