【C++11】lambda表达式及包装器

一.lambda表达式

1.可调用对象

可调用对象即可以像函数一样被调用的对象,有以下三种:

  1. 函数(指针)
  2. 仿函数对象
  3. lambda表达式

 tips:调用函数时,既可以用函数名,也可以用函数地址,因为函数名和函数地址是一回事。

2.lambda表达式格式

[捕捉列表](参数列表)mutable->返回值类型 {函数体} 

  1. 捕捉列表不能省略,即使它为空
  2. 参数列表为空时可以省略,但是有mutable时不能省略
  3. mutable用法后面会讲
  4. 返回值类型可以省略

例如:<algorithm>中的sort是一个函数模版,第三个参数需要一个可调用对象,我们就可以传一个lambda表达式过去。如果要传仿函数对象,需要定义一个类,相比之下,lambda表达式更加轻便。

struct Good
{Good(const string& name, double price, int id):_name(name),_price(price),_id(id){}string _name;double _price;int _id;
};
int main()
{vector<Good> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2 ,3 }, { "菠萝", 1.5, 4 } };auto priceLess = [](const Good& g1, const Good& g2)->bool{return g1._price < g2._price;};sort(v.begin(), v.end(), priceLess);
}

3.lambda表达式的本质

lambda表达式底层是一个仿函数对象,是一个重载了operator()的类的匿名对象。这个类的名称是<lambda_uuid>(uuid是一个很长的字符串,通过某种算法得到,同一台机器上基本不会重复),这个类对用户也是匿名的,不能直接使用。

 4.详解捕捉列表

捕捉列表用于捕捉父作用域的局部变量,供函数体中使用。父作用域指的是lambda表达式所在的语句块。捕捉列表内的东西实质上是该匿名对象的成员。

(1)值捕捉

int main()
{int x = 0, y = 1;//捕捉lambda表达式函数体的父作用域,即main函数作用域内的变量x和变量yauto f = [x, y]()mutable->void{int tmp = x;x = y;y = tmp;};f();cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;
}

lambda表达式是一个仿函数对象,它的operator()函数是const的,也即函数体内不能修改成员,使用mutalbe可以取消const。lambda表达式不建议取消const特性。

可以看出x和y的值并没有交换,因为捕捉列表中的x和y是main函数中的x,y的拷贝,二者互不影响。

(2)引用捕捉

int main()
{int x = 0, y = 1;auto f = [&x, &y]()->void //不用加mutable,因为引用可以修改{int tmp = x;x = y;y = tmp;};f();cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;
}

(3)全部传值捕捉

int main()
{int x = 0, y = 1;auto f = [=]()->void{cout << x << endl;cout << y << endl;};f();return 0;
}

如果lambda表达式在成员函数中,那么this指针也会捕捉过来 


class AA
{
public:void func(){auto f1 = [=]()->void{cout << _a;//this->_acout << _b;//this->_b};//这样写反而不行,因为_a和_b不在func作用域内/*auto f1 = [_a, _b]()->void{cout << _a;cout << _b;};*/}
private:int _a;int _b;
};

(4)全部引用捕捉

int main()
{int x = 0, y = 1;auto f = [&]()->void{cout << x << endl;cout << y << endl;};f();return 0;
}

同理,如果lambda表达式在成员函数中,this指针也会捕捉过来。 

 (5)混合捕捉


int main()
{int x = 0, y = 1;auto f1 = [&, x]()->void //传值捕捉x,其余全部引用捕捉,&或=必须在前{cout << x << endl;cout << y << endl;};auto f2 = [x, &y]()->void //传值捕捉x,引用捕捉y{cout << x << endl;cout << y << endl;};//auto f3 = [=, &]()->void  //不允许重复捕捉,因为使用时会有歧义//{//	cout << x << endl;//	cout << y << endl;//};return 0;
}

前文说到,捕捉列表中的东西实质上是匿名对象的成员,证明如下:

由结果可知,当捕捉父作用域的全部局部变量时,编译器并非憨憨地全部捕捉,而是看你在函数体内用到了哪些,没用到的就不捕获了。 

二.function包装器

1.function包装器用法

  1. function是一个类模版,在<functional>中。
  2. 不同的可调用对象虽然类型不同,但是却可能有相同的调用形式,即返回类型,参数类型相同。
  3. function包装器作用就是包装可调用对象,把调用形式相同的可调用对象的类型统一起来,便于书写它们的类型。
  4. function实际是一个类模版,实例化时的模版参数写可调用对象的调用形式,即返回值类型(参数类型列表),如void(int, double),这一点和普通模版不一样。
void swap_func(int& x, int& y)
{int tmp = x;x = y;y = x;cout << "函数指针" << endl;
}class Swap_func
{
public:void operator()(int& x, int& y){int tmp = x;x = y;y = x;cout << "仿函数对象" << endl;}
};
int main()
{int x = 1, y = 0;function<void(int&, int&)> f1 = swap_func;f1(x, y);auto lambda_swap_func = [](int& x, int& y)->void{int tmp = x;x = y;y = x;cout << "lambda表达式" << endl;};f1 = lambda_swap_func;f1(x, y);f1 = Swap_func();f1(x, y);return 0;
}

 

 将具有同种调用形式的可调用对象类型统一起来,下面这段代码能体现其用处:

map<string, function<void(int&, int&)>> cmdOP = {{"函数指针", swap_func},{"lambda表达式", lambda_swap_func},{"仿函数对象", Swap_func()}};//列表初始化cmdOP["函数指针"](x, y);cmdOP["lambda表达式"](x, y);cmdOP["仿函数对象"](x, y);

如果没有function包装器,map的第二个模版参数就只能用函数指针了,并且只能使用函数这一种可调用对象,缺乏灵活性。并且函数指针用起来很麻烦,这也是为什么C++仿函数被广泛应用的原因。

2.function包装器包装成员函数

(1)静态成员函数

class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{//包装静态成员函数function<int(int, int)> f1 = Plus::plusi;int ret = f1(1, 2);return 0;
}

与普通全局函数唯一不同的一点,就是要指明类域。

(2)非静态成员函数

function<double(double, double)> f2 = Plus::plusd;
//error C3867: “Plus::plusd”: 非标准语法;请使用 "&" 来创建指向成员的指针

非静态成员函数比较特殊,函数前还需加上取地址符号,一般情况下不管函数取不取地址,都代表函数地址,这里特殊情况记住就行。

function<double(double, double)> f2 = &Plus::plusd;
//error C2440: “初始化”: 无法从“double (__thiscall Plus::* )(double,double)”
//转换为“std::function<double (double,double)>”

还是有问题,因为成员函数都是隐式传了this指针的,不然函数体内就无法访问成员变量。

最终写法:

int main()
{double x = 1.1;double y = 2.2;function<double(Plus*, double, double)> f2 = &Plus::plusd;double ret = f2(&p, x, y);return 0;
}

还支持这样写:

function<double(Plus, double, double)> f2 = &Plus::plusd;
f2(Plus(), x, y);//传匿名对象

在底层上可以理解为前者是通过对象指针去调用plusd,后者通过对象调用plusd,二者是一样的。而且后者可以传匿名对象,更加方便。

但是,每次调用f2都好麻烦啊,需要传递对象或者对象指针。如果函数体内部根本不涉及成员变量的操作,也就是说第一个参数传什么根本不重要。有没有什么办法,每次自动传一个匿名对象,让我自己少传一个参数呢?有的,使用bind函数!!!

三.bind包装器

  1. bind是一个函数模版,在<functional>中
  2. 它的作用是包装可调用对象,返回一个新的可调用对象,以达到调整调用形式的目的,即参数个数及顺序

一般形式:auto newCallable = bind(callable, arg_list)  

callalbe是原来的可调用对象,newCallable是返回的新的可调用对象,arg_list是以逗号分隔的参数列表

 

1.改变参数顺序

int Sub(int x, int y)
{return x - y;
}
int main()
{//调整参数顺序auto f1 = bind(Sub, placeholders::_2, placeholders::_1);cout << f1(10, 5) << endl;return 0;
}

其中_n(n=1, 2, 3……)是“占位符”,表示新的可调用对象的参数,它定义在命名空间placeholders中。实际上,bind返回的newCallable封装了Callable,当我们调用newCallable时,newCallable会调用Callable,并传递给它arg_list中的参数。

2.改变参数个数

bind意为“绑定”,它真正的作用在于绑定某些参数,从而减少传参的个数。

int main()
{//调整参数个数auto lambda = [](int a, int b, int c)->void{cout << a << endl;cout << b << endl;cout << c << endl;};function<void(int, int)> f = bind(lambda, placeholders::_1, 10, placeholders::_2);f(1, 100);
}

 

注意观察,bind返回的是一个可调用对象,当然可以用function包装。并且function的模版参数就是新的可调用对象的调用形式。 

同理,用function包装非静态成员函数时就可以使用bind来减少参数传递 

class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};int main()
{function<double(double, double)> f = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);double ret = f(1.1, 2.2);cout << ret << endl;
}

3.bind返回的可调用对象本质

可见,bind返回的可调用对象也是一个类的实例对象,这个类肯定封装了operator()。其实无论是lambda表达式,还是bind返回的对可调用象,都是用的仿函数技术,这不过外面做了一层精美的包装,使我们使用起来更加方便。

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

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

相关文章

Python从入门到精通五:Python数据容器

数据容器入门 为什么学习数据容器 思考一个问题&#xff1a;如果我想要在程序中&#xff0c;记录5名学生的信息&#xff0c;如姓名。 如何做呢&#xff1f; 学习数据容器&#xff0c;就是为了批量存储或批量使用多份数据 Python中的数据容器&#xff1a; 一种可以容纳多份…

Kalman滤波、扩展Kalman滤波、无迹Kalman滤波和异步滤波的原理及其Matlab代码

目录 引言Kalman滤波代码及其结果展示 扩展Kalman滤波代码及其结果展示 无迹Kalman滤波无迹变换无迹Kalman滤波代码及其结果展示 异步无迹Kalman滤波原理代码及其结果展示 引言 本文给出了Kalman Filter&#xff08;卡尔曼滤波&#xff09;、Extended Kalman Filter&#xff0…

leetcode 98. 验证二叉搜索树

leetcode 98. 验证二叉搜索树 题目 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是…

vue3 引入 markdown编辑器

参考文档 安装依赖 pnpm install mavon-editor // "mavon-editor": "3.0.1",markdown 编辑器 <mavon-editor></mavon-editor>新增文本 <mavon-editor ref"editorRef" v-model"articleModel.text" codeStyle"…

Adams与Abaqus冲突问题

随着工程仿真软件的广泛应用&#xff0c;Adams和Abaqus已成为众多工程师的首选工具。然而&#xff0c;在使用过程中&#xff0c;一些用户可能会遇到这两个软件之间的冲突问题&#xff0c;导致无法正常进行仿真分析。为了帮助大家解决这一难题&#xff0c;我们推出了一篇关于Ada…

Softmax回归

一、Softmax回归关键思想 1、回归问题和分类问题的区别 Softmax回归虽然叫“回归”&#xff0c;但是它本质是一个分类问题。回归是估计一个连续值&#xff0c;而分类是预测一个离散类别。 2、Softmax回归模型 Softmax回归跟线性回归一样将输入特征与权重做线性叠加。与线性回归…

Linux安装Nginx并部署Vue项目

今天部署了一个Vue项目到阿里云的云服务器上&#xff0c;现记录该过程。 1. 修改Vue项目配置 我们去项目中发送axios请求的文件里更改一下后端的接口路由&#xff1a; 2. 执行命令打包 npm run build ### 或者 yarn build 打包成功之后&#xff0c;我们会看到一个dist包&a…

[MySQL]SQL优化之索引的使用规则

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、索引失效 &#x1f4d5;最左前缀法则 &#x1f4d5;范围查询> &#x1f4d5;索引列运算&#xff0c;索引失效 &#x1f4d5;前模糊匹配 &#x1f4d5;or连接的条件 &#x1f4d5;字符串类型不加 …

110. 平衡二叉树(Java)

给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;t…

如何通过SPI控制Peregrine的数控衰减器

概要 Peregrine的数控衰减器PE4312是6位射频数字步进衰减器(DSA,Digital Step Attenuator)工作频率覆盖1MHz~4GHz,插入损耗2dB左右,衰减步进0.5dB,最大衰减量为31.5dB,高达59dBm的IIP3提供了良好的动态性能,切换时间0.5微秒,供电电源2.3V~5.5V,逻辑控制兼容1.8V,20…

​如何使用https://www.krea.ai/来实现文生图,图生图,

网址&#xff1a;https://www.krea.ai/apps/image/realtime Krea.ai 是一个强大的人工智能艺术生成器&#xff0c;可用于创建各种创意内容。它可以用来生成文本描述的图像、将图像转换为其他图像&#xff0c;甚至写博客文章。 文本描述生成图像 要使用 Krea.ai 生成文本描述…

设计模式——建造者模式(Java示例)

引言 生成器是一种创建型设计模式&#xff0c; 使你能够分步骤创建复杂对象。 与其他创建型模式不同&#xff0c; 生成器不要求产品拥有通用接口。 这使得用相同的创建过程生成不同的产品成为可能。 复杂度&#xff1a; 中等 流行度&#xff1a; 流行 使用示例&#xff1a…

【conda】利用Conda创建虚拟环境,Pytorch各版本安装教程(Ubuntu)

TOC conda 系列&#xff1a; 1. conda指令教程 2. 利用Conda创建虚拟环境&#xff0c;安装Pytorch各版本教程(Ubuntu) 1. 利用Conda创建虚拟环境 nolonolo:~/sun/SplaTAM$ conda create -n splatam python3.10查看结果&#xff1a; (splatam) nolonolo:~/sun/SplaTAM$ cond…

Java 中的 Deque 接口及其用途

文章目录 Deque 介绍Deque 使用双端队列普通队列栈 总结 在 Java 中&#xff0c;Deque 接口是一个双端队列&#xff08;double-ended queue&#xff09;的数据结构&#xff0c;它支持在两端插入和移除元素。Deque 是 “Double Ended Queue” 的缩写&#xff0c;而且它可以同时充…

Linux系统编程(一):基本概念

参考引用 Unix和Linux操作系统有什么区别&#xff1f;一文带你彻底搞懂posix Linux系统编程&#xff08;文章链接汇总&#xff09; 1. Unix 和 Linux 1.1 Unix Unix 操作系统诞生于 1969 年&#xff0c;贝尔实验室发布了一个用 C 语言编写的名为「Unix」的操作系统&#xff0…

【基于LSTM的电商评论情感分析:Flask与Sklearn的完美结合】

基于LSTM的电商评论情感分析&#xff1a;Flask与Sklearn的完美结合 引言数据集与爬取数据处理与可视化情感分析模型构建Flask应用搭建词云展示创新点结论 引言 在当今数字化时代&#xff0c;电商平台上涌现出大量的用户评论数据。了解和分析这些评论对于企业改进产品、服务以及…

❀expect命令运用于bash❀

目录 ❀expect命令运用于bash❀ expect使用原理 expet使用场景 常用的expect命令选项 Expect脚本的结尾 常用的expect命令选参数 Expect执行方式 单一分支语法 多分支模式语法第一种 多分支模式语法第二种 在shell 中嵌套expect Shell Here Document&#xff08;内…

基于Java实验室管理系统

基于Java实验室管理系统 功能需求 1、实验室设备管理&#xff1a;系统需要提供实验室设备管理功能&#xff0c;包括设备的查询、预订、使用记录、维护和报废等。 2、实验项目管理&#xff1a;系统需要提供实验项目管理功能&#xff0c;包括项目的创建、审批、执行和验收等&a…

以太坊:前世今生与未来

一、引言 以太坊&#xff0c;这个在区块链领域大放异彩的名字&#xff0c;似乎已经成为了去中心化应用&#xff08;DApps&#xff09;的代名词。从初期的萌芽到如今的繁荣发展&#xff0c;以太坊经历了一段曲折而精彩的旅程。让我们一起回顾一下以太坊的前世今生&#xff0c;以…

树实验代码

哈夫曼树 #include <stdio.h> #include <stdlib.h> #define MAXLEN 100typedef struct {int weight;int lchild, rchild, parent; } HTNode;typedef HTNode HT[MAXLEN]; int n;void CreatHFMT(HT T); void InitHFMT(HT T); void InputWeight(HT T); void SelectMi…