【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 表达式有如下注意事项:

  1. 父作用域是指包含 lambda 函数的语句块,捕捉列表可以捕捉父作用域中位于 lambda 函数之前定义的所有变量;
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割;
    比如:
    • [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量;
    • [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量;
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误;
  4. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错;
  5. 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;
}

在这里插入图片描述


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

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

相关文章

zookeeper学习记录

本文Java代码地址&#xff1a; https://gitee.com/blackjie_1/ljUp/tree/master/zookeeperDemo 个人博客网站&#xff1a;什么是快乐 基于docker 安装 拉取zookeeper 3.4.10 docker pull zookeeper:3.4.10启动服务端 docker run -d -p 2181:2181 -v /root/docker/zookeepe…

现在做跨境电商还需要全球代理IP吗?全球代理IP哪家靠谱?

随着全球互联网的发展&#xff0c;电商平台的发展和跨境贸易的便利化&#xff0c;跨境电商在过去几年中也一直呈现增长趋势&#xff0c;吸引了越来越多的企业和个体创业者入行。 然而&#xff0c;行业竞争也在不断加剧&#xff0c;跨境电商面临更多的越来越多的挑战&#xff0…

c语言中,/100和/100.0的区别是什么?

c语言中&#xff0c;/100和/100.0的区别是什么&#xff1f; 应该是整数除法和浮点数除法的区别吧。/100 时&#xff0c;结果只会保留整数部分&#xff0c;余数会丢弃。 最近很多小伙伴找我&#xff0c;说想要一些c语言的资料&#xff0c;然后我根据自己从业十年经验&#xff0…

哈夫曼编码详细证明步骤

关于哈夫曼编码&#xff0c;想必大家都很清楚&#xff0c;这里来讲解一下他的详细证明方法。代码的话就不给了网上一大堆&#xff0c;我再给也没什么意思&#xff0c;这里主要讲明白正确性的证明。我将采取两种方法进行证明&#xff0c;一种常规的方法&#xff0c;还有一种采取…

完整时间线!李开复Yi大模型套壳争议;第二届AI故事大赛;AI算命GPTs;LLM应用全栈开发笔记;GPT-5提上日程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f440; 李开复「零一万物」大模型陷套壳争议&#xff0c;事件时间线完整梳理 https://huggingface.co/01-ai/Yi-34B/discussions/11#65531458…

振南技术干货集:比萨斜塔要倒了,倾斜传感器快来!(1)

注解目录 1、倾斜传感器的那些基础干货 1.1 典型应用场景 &#xff08;危楼、边坡、古建筑都是对倾斜敏感的。&#xff09; 1.2 倾斜传感器的原理 1.2.1 滚珠式倾斜开关 1.2.2 加速度式倾斜传感器 1)直接输出倾角 2)加速度计算倾角 3)倾角精度的提高 &#xff08;如果…

Kettle工具使用小结1

1.背景 客户数据库限定为tidb数据库&#xff0c;相关业务数据均存储在内。因为tidb数据库是分布式的&#xff0c;且不支持存储过程、job等功能&#xff0c;需要通过外部工具进行脚本批量处理&#xff0c;所以这里引入kettle进行脚本批量执行和作业调度。 2.环境信息 &#xf…

SpringBoot配置数据库密码加密的方法

由于系统安全的考虑,配置文件中不能出现明文密码的问题,本文就给大家详细介绍下springboot配置数据库密码加密的方法,下面话不多说了,来一起看看详细的介绍吧,需要的朋友可以参考下 1.导入依赖 <!--数据库密码加密--> <dependency><groupId>com.github.uli…

探索arkui(2)--- 布局(列表)--- 2(支持分组/实现响应滚动位置)

前端开发布局是指前端开发人员宣布他们开发的新网站或应用程序正式上线的活动。在前端开发布局中&#xff0c;开发人员通常会展示新网站或应用程序的设计、功能和用户体验&#xff0c;并向公众宣传新产品的特点和优势。前端开发布局通常是前端开发领域的重要事件&#xff0c;吸…

Leetcode88 合并两个有序数组

合并两个有序数组 题解1 正向(记得插1删1)题解2 逆向 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减…

WEB 自动化神器 TestCafe(一)—安装和入门篇

今天小编给大家带来WEB 自动化神器 TestCafe(一) —安装和入门篇 一、TestCafe 介绍&#xff1a; TestCafe 是一款基于 Node.js 的端到端 Web 自动化测试框架&#xff0c;支持 TypeScript 或 JavaScript 来编写测试用例&#xff0c;运行用例&#xff0c;并生成自动化测试报告。…

传输层——— UDP协议

文章目录 一.传输层1.再谈端口号2.端口号范围划分3.认识知名端口号4.两个问题5.netstat与iostat6.pidof 二.UDP协议1.UDP协议格式2.UDP协议的特点3.面向数据报4.UDP的缓冲区5.UDP使用注意事项6.基于UDP的应用层协议 一.传输层 在学习HTTP等应用层协议时&#xff0c;为了便于理…

计算机网络的发展

目录 一、计算机网络发展的四个阶段 1、第一阶段&#xff1a;面向终端的计算机网络&#xff08;20世纪50年代&#xff09; 2、第二阶段&#xff1a;计算机—计算机网络&#xff08;20世纪60年代&#xff09; 3、第三阶段&#xff1a;开放式标准化网络&#xff08;20世纪70年…

vue3别名配置(vite)

1、配置别名的优点&#xff1a; 在VUE项目中import导入文件时&#xff0c;可以写相对路径. 2、在vite.config.js中配置 a. 首先引入path import path from "path"/* */ b.在resolve添加别名&#xff0c;例如&#xff1a; alias:{"~":path.resolve(__di…

jQuery【jQuery树遍历、jQuery动画(一)、jQuery动画(二)】(四)-全面详解(学习总结---从入门到深化)

目录 jQuery树遍历 jQuery动画(一) jQuery动画(二) jQuery树遍历 1、 .children() 获得子元素&#xff0c;可以传递一个选择器参数 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-…

批量处理文件夹及子文件夹下文件名

从此烟雨落京城&#xff0c;一人撑伞两人行。 问题描述 下载的资源被打过标记&#xff0c;不能直接使用&#xff0c;甚是痛苦 问题&#xff1a; 所有文件的文件名都加入了【更多it教程 微信号&#xff1a;…】字段&#xff0c;包括当前文件夹和子文件夹的全部文件&#xff0c…

leetcode:链表的中间结点

1.题目描述 题目链接&#xff1a;876. 链表的中间结点 - 力扣&#xff08;LeetCode&#xff09; 我们先看题目描述&#xff1a; 2.解题思路 我们用画图用快慢指针来解决这个问题 定义一个快指针fast&#xff0c;一个慢指针slow 快指针一次走两个结点&#xff0c;慢指针一次…

vscode终端npm install报错

报错如下&#xff1a; npm WARN read-shrinkwrap This version of npm is compatible with lockfileVersion1, but package-lock.json was generated for lockfileVersion2. Ill try to do my best with it! npm ERR! code EPERM npm ERR! syscall open npm ERR! errno -4048…

【AI视野·今日Sound 声学论文速览 第三十四期】Thu, 26 Oct 2023

AI视野今日CS.Sound 声学论文速览 Thu, 26 Oct 2023 Totally 9 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Dynamic Processing Neural Network Architecture For Hearing Loss Compensation Authors Szymon Drgas, Lars Bramsl w, Archontis Poli…

LeetCode(21)反转字符串中的单词【数组/字符串】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 151. 反转字符串中的单词 1.题目 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单…