【C++11】lambda表达式 | 包装器
文章目录
- 一、 lambda表达式
- lambda表达式的引入
- lambda表达式的语法
- lambda表达式与函数对象
- lambda表达式的捕捉列表
- 二、包装器
- function包装器
- bind包装器
一、 lambda表达式
lambda表达式的引入
在C++98中,为了替代函数指针,C++设计出了仿函数,也称为函数对象。仿函数本质上就是一个普通的类,不过该类重载了函数调用操作符(),使得该类的对象可以像函数一样去使用。

虽然仿函数已经能够完全取代函数指针了,但是在一些场景下仍然有些难用。
// 商品类
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& gl, const Goods& gr){return gl._price < gr._price;}
};struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess()); // 按照价格上升进行排序sort(v.begin(), v.end(), ComparePriceGreater()); // 按照价格下降进行排序return 0;
}
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了**Lambda表达式**。
将上述排序仿函数使用lambda表达式的方式来实现:

lambda表达式的语法
💕 lambda表达式书写格式如下:
[capture-list] (parameters) mutable -> return-type { statement}
💕 lambda表达式各部分说明
[capture-list]: 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。mutable: 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意:
- 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:
[]{}; 该lambda函数不能做任何事情。
lambda表达式与函数对象
lambda表达式和仿函数一样,本质上也是一个可调用的函数对象,所以lambda表达式的使用方式和仿函数完全相同,但和仿函数不同的是,lambda表达式的类型是由编译器自动生成的,并且带有随机值,所以我们无法具体写出lambda表达式的类型,只能用auto进行推导。
int main()
{auto add1 = [](int x, int y)->int { return x + y; };cout << add1(1, 2) << endl;auto add2 = [](int x, int y)->int{return x + y;};cout << add2(1, 1) << endl;[] {}; // 最简单的lambda表达式return 0;
}

其实lambda表达式本质上是底层通过编译器生成了一个匿名的函数对象,然后再通过这个函数对象来调用 operator()() 函数,从而完成调用,换句话说,lambda表达式底层就是通过替换为仿函数来完成的。
lambda表达式的捕捉列表
lambda表达式的捕捉列表可以捕捉父作用域中lambda表达式之前的所有变量,捕捉方式如下:
[var]:表示值传递方式捕捉变量var。传值捕捉到的参数默认是被const 修饰的,因此不能再lambda表达式的函数体中修改他们,如果要修改,需要使用mutable修饰,但由于传值捕捉修改的是形参,所以一般我们也不会去修改它。

[&var]:表示引用传递捕捉变量var,通过引用传递捕捉,我们就可以在lambda表达式函数体中修改实参的值了。

[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

[=]:表示值传递方式捕获所有父作用域中的变量

除了上面这几种捕捉方式之外,lambda表达式的捕捉列表还支持混合捕捉,如下:

💕 lambda 表达式有如下注意事项:
- 父作用域是指包含 lambda 函数的语句块,捕捉列表可以捕捉父作用域中位于 lambda 函数之前定义的所有变量;
- 语法上捕捉列表可由多个捕捉项组成,并以逗号分割;
比如:- [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量;
- [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量;
- 捕捉列表不允许变量重复传递,否则就会导致编译错误;
- 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错;
- lambda 表达式之间不能相互赋值,即使看起来类型相同。
二、包装器
function包装器
function 是一个 可调用对象包装器, 也叫作适配器。它可以将函数指针、仿函数以及lambda表达式、成员函数等可调用对象进行包装,使他们具有相同的类型,包装器也可以像普通函数一样进行调用,包装器的本质还是仿函数。
在C++11标准中引入了 std::function 模板类,其定义在<function>头文件中。

std::function<返回值类型(参数类型1, 参数类型2, ...)> f;
function 的使用类似于普通类,可以先定义一个function对象,然后将需要调用的函数赋值给该对象,也可以在定义function对象时直接使用可调用对象完成初始化,最后通过function对象进行函数调用。
方案一:
int f(int a, int b)
{cout << "int f(int a, int b)" << endl;return a + b;
}struct Functor {int operator()(int a, int b){cout << "int operator()(int a, int b)" << endl;return a + b;}
};
// 先定义function对象,然后将需要调用的函数赋值给该对象
using func_t = function<int(int, int)>;func_t func0 = f;
func_t func1 = Functor();
func_t func2 = [](int a, int b)->int {cout << "[](int a, int b)->int{ return a + b; }" << endl;return a + b;};
方案二:
int main()
{//int(*pf1)(int,int) = f;function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b)->int {cout << "[](int a, int b)->int{ return a + b; }" << endl;return a + b;};cout << f1(1, 2) << endl;cout << f2(10, 20) << endl;cout << f3(100, 200) << endl;return 0;
}

经过上面 function 的包装,使得函数指针 f、仿函数 Functor、lambda 表达式以及类的静态成员函数具有了统一的类型 —— function<int(int, int)>;类的普通成员函数我们也可以通过后面的绑定来让它的类型变为 function<int(int, int)>。
function封装类内成员函数
当function封装的是类内成员函数时,需要对该成员函数进行类域的声明,并且还需要在类域前面加一个取地址符。
- 静态成员函数没有this指针,所以function类实例化时不需要添加一个成员函数所属类的类型参数,在调用时也不需要传递一个成员函数所属类的对象。
- 非静态成员函数有this指针,所以需要传递成员函数所属类的对象并且进行类域声明。这里传递的是类的类型和类的对象。
class Plus {
public:Plus(int rate = 2):_rate(rate){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _rate;}private:int _rate = 2;
};int main()
{// 静态成员函数//function<int(int, int)> f1 = Plus::plusi;function<int(int, int)> f1 = &Plus::plusi; // 上面的写法也是可以的cout << f1(1, 2) << endl;// 非静态成员函数function<double(Plus, double, double)> f2 = &Plus::plusd;cout << f2(Plus(), 1, 2) << endl;function<double(Plus*, double, double)>f3 = &Plus::plusd;Plus p;cout << f3(&p, 1, 2) << endl;return 0;
}

这里我们需要注意的是,因为this指针是不能显示的传递的。所以这里传递的并不是对应的this指针。
bind包装器
bind 也是一种包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来"适应"原对象的参数列表,C++中的bind本质是一个函数模板。
原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
fn:可调用对象。args:要绑定的参数列表、值或占位符。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
解释说明:
callable:需要包装的可调用对象newCallable:生成的新的可调用对象arg_list:逗号分割的参数列表,对应给定的callable的参数。当调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
placeholders 是 C++11 引入的一个命名空间域,它包含了一些占位符对象(placeholder objects),用于在使用 bind 绑定函数时,指定某个参数需要在调用时再传递进来。其中参数可能是形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置,比如_1为newCallable的第一个参数,_2为第二个参数,以此类推。
此外,除了用auto接收包装后的可调用对象,也可以用function类型指明返回值和形参类型后接收包装后的可调用对象。

int Plus(int a, int b)
{return a + b;
}
int main()
{function<int(int, int)> func1 = bind(Plus, placeholders::_1, placeholders::_2);auto func2 = bind(Plus, 5, 8);cout << func1(1, 2) << endl;cout << func2() << endl;return 0;
}

bind函数同时也可以绑定类的成员函数和类的静态成员函数。
class Sub {
public:int sub(int a, int b){return a - b;}static int mul(int a, int b){return a * b;}
private:
};

💕 bind调整参数顺序
bind可以通过调整占位符的顺序来调整参数的顺序:
class Sub {
public:int sub(int a, int b){return a - b;}static int mul(int a, int b){return a * b;}
private:
};int main()
{function<int(int, int)> func3 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);// 参数调换顺序function<int(int, int)> func4 = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);cout << func3(1, 2) << endl;cout << func4(1, 2) << endl;return 0;
}

💕 bind调整参数个数
bind可以在形参列表中直接绑定具体的函数对象,这样该参数就会自动传递,而不需要我们在调用函数时显示传递,并且也不需要我们在function的参数包中显示声明。这样我们就可以通过绑定让我们将类的普通成员函数和类的静态成员函数以及lambda表达式、函数指针一样定义为统一的类型了。
int main()
{// 调整参数个数——非静态成员函数function<int(Sub, int)> func5 = bind(&Sub::sub, placeholders::_1, 100, placeholders::_2);cout << func5(Sub(), 20) << endl;// 调整参数个数——静态成员函数function<int(Sub, int)> func6 = bind(&Sub::mul, 100, placeholders::_2);cout << func6(Sub(), 20) << endl;return 0;
}

相关文章:
【C++11】lambda表达式 | 包装器
文章目录 一、 lambda表达式lambda表达式的引入lambda表达式的语法lambda表达式与函数对象lambda表达式的捕捉列表 二、包装器function包装器bind包装器 一、 lambda表达式 lambda表达式的引入 在C98中,为了替代函数指针,C设计出了仿函数,也…...
网络安全准入技术之MAC VLAN
网络准入控制作为主要保障企业网络基础设施的安全的措施,特别是对于中大型企业来说,终端类型多样数量激增、终端管理任务重难度大、成本高。 在这样的一个大背景下,拥有更灵活的动态识别、认证、访问控制等成为了企业网络安全的最核心诉求之…...
MyBatis 操作数据库
文章目录 1. 什么是MyBatis?2. 入门MyBatis2.1 准备工作2.2.1 创建springboot项目2.2.2 数据准备 2.2 配置数据库连接2.3 写持久层代码2.4 单元测试2.4.1 web测试2.4.2 自动测试 1. 什么是MyBatis? MyBatis是一种持久层框架,用于简化JDBC的开…...
设计模式 -- 建造者模式(Builder Pattern)
这个模式以前也义Android-kotlin的场景下讲过 Android 用建造者模式模式写一个Dialog-CSDN博客 不过用的是 变种的建造者模式 建造者模式: 属于创建型模式 提供了一种创建对象的最佳方式, 使用多个简单的对象一步一步构建成一个复杂的对象 。 介绍 意图…...
如何下载 Apache + PHP + Mysql 集成安装环境并结合内网穿透工具实现公网访问内网服务
🌈个人主页:聆风吟 🔥系列专栏:网络奇遇记、Cpolar杂谈 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 📋前言一. WampServer下载安装二. WampServer启动三. 安装cpolar内网穿透3.1 注册账号…...
一招告别百度广告烦恼,同时效率提高100倍的几个常用搜索技巧!
《博主简介》 小伙伴们好,我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推荐--…...
文件上传 [ACTF2020 新生赛]Upload1
打开题目,发现是一道文件上传题目 随便上传个一句话木马上去 发现网站前端有白名单限制,只能上传含有jpg,png,gif的后缀文件 那我们便传个2.jpg的一句话木马上去,bp抓包 我们改成php文件后缀试试,发现重发…...
振南技术干货集:比萨斜塔要倒了,倾斜传感器快来!(1)
注解目录 1、倾斜传感器的那些基础干货 1.1 典型应用场景 (危楼、边坡、古建筑都是对倾斜敏感的。) 1.2 倾斜传感器的原理 1.2.1 滚珠式倾斜开关 1.2.2 加速度式倾斜传感器 1)直接输出倾角 2)加速度计算倾角 3)倾角精度的提高 (如果…...
手把手教你搭建属于自己的快递小程序
在数字化时代,小程序已经成为各行各业连接用户、提供服务、创造价值的重要工具。其中,快递寄件小程序因其实用性和广泛的需求,成为很多企业和开发者关注的焦点。本文将详细介绍如何快速创建快递寄件小程序,以及如何利用它实现盈利…...
C# Onnx LSTR 基于Transformer的端到端实时车道线检测
目录 效果 模型信息 项目 代码 下载 效果 端到端实时车道线检测 模型信息 lstr_360x640.onnx Inputs ------------------------- name:input_rgb tensor:Float[1, 3, 360, 640] name:input_mask tensor:Float[1, 1, 360, …...
Java相关编程思想
少用继承多用“组合”——在现有类的基础上组织一个新类。 2.继承要用“is”来检验,如果继承者is被继承者,说明这是一个比较好的继承。 3.向上造型,把实现方法留给继承者去实现。(动态绑定) 4.把接口理解为抽象类的进一…...
Hadoop-HDFS架构与设计
HDFS架构与设计 一、背景和起源二、HDFS概述1.设计原则1.1 硬件错误1.2 流水访问1.3 海量数据1.4 简单一致性模型1.5 移动计算而不是移动数据1.6 平台兼容性 2.HDFS适用场景3.HDFS不适用场景 三、HDFS架构图1.架构图2.Namenode3.Datanode 四、HDFS数据存储1.数据块存储2.副本机…...
OpenAI暂停新的ChatGPT Plus注册 | OpenAI 的 GPT Builder 创建您的 GPTs
OpenAI DevDay 才过去仅仅一周时间,伴随着开发者大会上发布的一系列重磅升级和新特性,无疑这样的进化速度让广大网友炸锅了,其火热程度可见一斑。 就在四个小时前,OpenAI的CEO Sam Altma突然宣布,ChatGPT Plus账号暂停…...
Git目录不对,即当前文件夹不对应git仓库
报错信息是: fatal: not a git repository (or any of the parent directories): .git 如: 是当前文件夹不对应git仓库,一般在git clone之后,需要进入下一级文件夹才对应仓库。 在文件夹看,本层中没有.git文件夹&…...
Python基础:正则表达式(regular expression)详解
在Python中,正则表达式是一种强大的工具,可用于匹配和操作字符串。什么是正则表达式? 正则表达式是一种模式匹配语言,用于匹配字符串中的特定模式。这些模式可以是字母、数字、字符组合或其他符号。正则表达式通常用于文本处理、网…...
sqlmap requires ‘python-pymysql‘ third-party library
使用sqlmap进行udf提权报错: [14:06:04] [CRITICAL] sqlmap requires python-pymysql third-party library in order to directly connect to the DBMS MySQL. You can download it from https://github.com/PyMySQL/PyMySQL. Alternative is to use a package pyt…...
05 robotFrameWork+selenium2library 一维数组的使用
一、原生数组: 1、Excel中: LIST_OneRange 项目1|项目2 2、生成的PY: LIST_OneRange [u项目1,u项目2] 3、脚本使用: :FOR ${Local_I} IN RANGE len(${OneRange}) ${value} Evaluate ${OneRange}[${Local_I}] …...
SpringCloud Alibaba组件入门全方面汇总(上):注册中心-nacos、负载均衡-ribbon、远程调用-feign
文章目录 NacosRibbonFeignFeign拓展 Nacos 概念:Nacos是阿里巴巴推出的一款新开源项目,它是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos致力于帮助用户发现、配置和管理微服务,它提供了一组简单易用的特性集&am…...
Zabbix钉钉机器人告警
目录 一.在钉钉群里添加机器人 二.配置钉钉告警脚本 1.安装python依赖模块python-requests 2.配置钉钉告警配置脚本zabbix_ding.conf 3.创建告警日志并且授权。 4.配置钉钉告警执行脚本dingding.py 5.测试 三.配置zabbix告警 1.创建媒介 2.给用户添加报警媒介 3.配置…...
unity shaderGraph实例-扫描效果
文章目录 效果展示整体结构各区域内容区域1区域2区域3区域4区域5区域6GraphSetttings注意事项使用方法 效果展示 整体结构 各区域内容 区域1 用场景深度减去顶点的View空间的视野深度(Z值),这里Z值需要乘-1是因为从相机看到的物体顶点的视野…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...
基于单片机的宠物屋智能系统设计与实现(论文+源码)
本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢,连接红外测温传感器,可实时精准捕捉宠物体温变化,以便及时发现健康异常;水位检测传感器时刻监测饮用水余量,防止宠物…...
医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...
