C++11新增特性:lambda表达式、function包装器、bind绑定

news2024/11/24 14:40:33

一、lambda表达式

1)、为啥需要引入lambda?

 在c++98中,我们使用sort对一段自定义类型进行排序的时候,每次都需要传一个仿函数,即手写一个完整的类。甚至有时需要同时实现排升序和降序,就需要各自手写一个类,非常不方便!所以C++11引入了lambda表达式!
lamaba是一个匿名函数对象,是一个可调用对象,表达式本质上也是一个类(vs中类名为lambda_uuid),并实现了operator()。

【C98玩法】:

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());
}

【C++11 lambda表达式】:

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, 
						{ "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate; });
}

2)、lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

  • capture-list(捕捉列表):编译器根据[ ]来判断接下来的代码是否为lambda函数,[ ]可以捕捉父作用域的变量供lambda函数使用。
  • parameters(列表参数):和普通函数列表参数一样。如果没用,可以连同()一起省略。
  • mutable 默认情况下lambda是一个const函数(实际上是用于修饰operator()的),mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • return-type:用于声明返回值类型。如果没用返回值可以省略;即使有返回值也可以省略,由编译器自动推导!
  • statement 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

3)、capture-list(捕捉列表)细节

 捕捉列表用于捕捉父作用域中变量供lambda函数使用,捕捉方分为以下几种:

  1. [var]:表示值传递方式捕捉变量var。
  2. [=]:表示传值方式捕捉父作用域中的所有变量(包括this)。
  3. [&var]:表示引用传递方式捕捉变量var。
  4. [&]:表示引用方式捕捉父作用域中的所有变量(包括this)。
  5. [this]:表示值传递方式捕捉当前的this指针

 除此之外,捕捉列表可以由多个捕捉项组成,以逗号分割。比如:

     [=, &a, &b]:除引用捕捉a、b外,其他父作用域变量传值捕捉!
     [&, a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。

 并且捕捉列表不能重复变量传递,否则会导致编译错误。比如:[=, a]:a变量传递了两次。lambda表达式之间不能相互赋值,即使类型相同,但编译后类名都变为了lambda_uuid),类名不相同!

4)、函数对象与lambda表达式

 函数对象又称仿函数,既可以像普通函数一样使用(在类中重载operator())。而lambda本质上一个一个函数对象,在编译后会转化成一个仿函数,并对operator()进行重载!

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.49;
	Rate r1(rate);
	r1(10000, 2);

	// lamber
	auto r2 = [=](double monty, int year)->double {
		return monty * rate * year;
	};
	r2(10000, 2);
	return 0;
}

【反汇编结果】:
在这里插入图片描述

二、function包装器

1)、包装器由来

 function包装器 也叫作适配器,包装的是可调用对象。C++中的function本质是一个类模板,也是一个包装器。
在C++中有3种可调用对象:函数指针、仿函数、lambda!但我们发现想要获得他们的都存在一些缺陷:函数指针非常麻烦,类型和对象嵌在一起;仿函数很重,即使是一个很简单的比较逻辑也要在外面定义一个类;lambda没法写类型,不同的机器和时间uuid不同,并且相对匿名(当然decltype可以获的)

除此在外,众多的可调用对象类型会导致模板的效率低下! 原因在于假设现在存在一个函数模板,我需要将一个可调用对象作为模板参数进行传递来达到某种目的。但众多的可调用对象可能实现的是同一个功能,但分别传递给模板后需要实例化3份,导致模板效率降低!!所以包装器对可调用对象进行了统一!

【实例】:

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{ 
	// 函数名
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d){ return d / 4; }, 11.11) << endl;
	return 0;
}

2)、包装器原型

std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

即现在存在一个包装器为:function<int(int, int)>,该包装器将所有的参数为2个int,返回值为int的所有可调用对象进行统一,认为是一个类型!!

3)、使用

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

int main()
{
	// 函数名
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d){
		return d;
	 };
	cout << useF(func3, 11.11) << endl;
	return 0;
}

4)、面试题

150. 逆波兰表达式求值
在这里插入图片描述

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        unordered_map<string, function<int(int, int)>> ophash = {
            {"+", [](int x, int y){return x + y;}},
            {"-", [](int x, int y){return x - y;}},
            {"*", [](int x, int y){return x * y;}},
            {"/", [](int x, int y){return x / y;}}
        };

        stack<int> st;
        for(auto str : tokens)
        {
            if(ophash .find(str) != ophash .end())
            {
                int right = st.top(); st.pop();
                int left = st.top(); st.pop();
                st.push(ophash [str](left, right));
            }
            else
                st.push(stoi(str));
        }
        return st.top();
    }
};

三、bind绑定

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);

 调用bind的一般形式:auto newCallable = bind(callable,arg_list);其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
 arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

1)普通绑定用法

【实例】:

int Sub(int a, int b)
{
	return a - b;
}

int main()
{
	std::function<int(int, int)> func1 = std::bind(Plus, 
										 std::placeholders::_1, std::placeholders::_2);
	std::cout << func1(10, 2) << std::endl;
}

g.cn/direct/08ddd54d0cc649cba0b6d963bd07d864.png)

 bind函数中,_1和_2都是占位符。当执行func1(10, 2)函数时,参数10和2会被作为参数传递给Plus函数。其中std::placeholders::_1表示func1第一个参数的位置, std::placeholders::_2表示第二个参数所在位置!

2)、改变参数位置

 既然_1和_2是占位符,所以仅需改变两者位置,所以func函数传递给Plus的参数即可转换。具体结果如下:

int Sub(int a, int b)
{
	return a - b;
}

int main()
{
	std::function<int(int, int)> func1 = std::bind(Sub, 
										 std::placeholders::_2, std::placeholders::_1);
	std::cout << func1(10, 2) << std::endl;
}

在这里插入图片描述

3)改变参数的个数

 我们在类外调用一个类成员函数,调用成员函数时第一个参数为该对象的指针。但每次我们手动传递的时候非常麻烦。所以我们可以通过bind()将第一个参数进行绑死,改变参数个数。具体如下:

class Plus
{
public:
	static int Plusi(int x, int y)
	{
		return x + y;
	}

	double Plusd(double x, double y)
	{
		return x + y;
	}
};
int main()
{
	Plus ps;
	
	// 正常情况下,第一个参数this指针需要手动传递, 麻烦
	std::function<double(Plus*, double, double)> func1 = &Plus::Plusd;
	std::cout << func1(&ps, 10.1, 20.2) << std::endl;

	// 将第一个参数绑死为Plus*, 
	std::function<double(double, double)> func2 = std::bind(&Plus::Plusd,
	 									&ps, std::placeholders::_1, std::placeholders::_2);
	std::cout << func2(10.1, 20.2) << std::endl;

	// 将第一个参数和第三个参数绑死
	std::function<double(double)> func3 = std::bind(&Plus::Plusd, 
													&ps, std::placeholders::_1, 1.1);
	std::cout << func3(10.1) << std::endl;
	
	// 所有参数直接全部绑死, 注意此处博主绑定的是静态函数
	auto func4 = std::bind(&Plus::Plusi, 10, 10);
	std::cout << func4() << std::endl;
	
	//绑定时,第一个参数因为指针,但编译器做了特殊处理,可以绑定临时对象
	std::function<double(double, double)> func5 = std::bind(&Plus::Plusd, 
									Plus(), std::placeholders::_1, std::placeholders::_2);
	std::cout << func5(1.1, 2.2) << std::endl;
}

在这里插入图片描述

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

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

相关文章

基于SSM的社区爱心捐赠管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSSMVueMySQL的社区爱…

任嘉伦新剧《流水迢迢》:卫昭多层人设引关注

近日&#xff0c;由晋江文学城同名小说改编的武侠古装爱情传奇剧《流水迢迢》即将开播&#xff0c;这部由任嘉伦主演的新剧&#xff0c;在原著和阵容的双双加持下热度直线上涨&#xff0c;宣传阶段就已备受网友期待&#xff0c;预约人数截止9月13日已达到206万&#xff0c;上升…

通信工程学习:什么是GPON吉比特无源光网络

GPON&#xff1a;吉比特无源光网络 GPON&#xff08;Gigabit-Capable Passive Optical Network&#xff0c;吉比特无源光网络&#xff09;是一种基于ITU-T G.984.x标准的最新一代宽带无源光综合接入技术。该技术以其高带宽、高效率、大覆盖范围和用户接口丰富等特点&#xff0c…

ubuntu服务器版NVIDIA驱动失效解决方案

ubuntu服务器版NVIDIA驱动失效解决方案 1. 问题描述2. 解决方法--卸载并重新安装最新版显卡驱动cudacudnn2.1 卸载显卡驱动2.2 重新安装最新版显卡驱动cudacudnn2.2.1 显卡驱动2.2.2 cuda2.2.3 cuda安装cudnn 1. 问题描述 在终端输入nvidia-smi&#xff0c;输出如下&#xff1…

Leetcode—移除元素

移除元素 题目描述 思路 思路&#xff1a;定义两个指针变量指向数组第一个位置&#xff0c;判断nums[scr]是否等于val case1:相等&#xff0c;scr; case2:不相等&#xff0c;nums[dst]nums[scr],scr,dst; 时间复杂度&#xff1a;O&#xff08;n&#xff09;&#xff1b;空间复杂…

微信支付开发-后台统计工厂实现

一、数据库设计图 二、后端统计工厂逻辑 1、统计父抽象类 a、StatisticsHandle.php 2、统计工厂通道类 a、StatisticsFactory.php 3、查询实现类 a、答题统计(Answer.php) 三、后端统计工厂代码实现 1、统计父抽象类(StatisticsHandle.php) <?php /*** 统计父抽象类* Use…

基于密码的大模型安全治理的思考

文章目录 前言一、大模型发展现状1.1 大模型技术的发展历程1.2 大模型技术的产业发展二、大模型安全政策与标准现状2.1 国外大模型安全政策与标准2.2 我国大模型安全政策与标准前言 随着大模型技术的迅速发展和广泛应用,其安全性问题日益凸显。密码学作为网络空间安全的核心技…

Linux搭建邮箱服务器(简易版)

本章是上一文档的简易版本搭建方式更为快速简洁&#xff08;只需要两条命令即可搭建&#xff09;&#xff0c;如果想了解更详细一些可以看我上一文档 Linux接发邮件mailx_linux mailx o365-CSDN博客文章浏览阅读857次&#xff0c;点赞25次&#xff0c;收藏19次。本文详细描述了…

计算机组成原理-3.1储存系统

现代结构 1.储存器的层次结构 辅存的数据要调入主存后才能被CUP&#xff0c;与操作系统的进程进行联动 运行速度&#xff1a;CPU>寄存器>Cache>主存>磁盘>磁盘和光盘 主存-辅存:实现了虚拟系统&#xff0c;解决了主存容量不够的问题。 Cache-主存&#xff1a…

二叉树的前中后序遍历(递归法)( 含leetcode上三道【前中后序】遍历题目)

文章目录 深入理解递归思想递归三要素 leetcode上三道题目&#xff1a;144.二叉树的前序遍历145.二叉树的后序遍历94.二叉树的中序遍历 深入理解递归思想 这次我们要好好谈一谈递归&#xff0c;为什么很多同学看递归算法都是“一看就会&#xff0c;一写就废”。 主要是对递归…

宝塔部署python项目

宝塔部署-python项目文章浏览阅读559次&#xff0c;点赞11次&#xff0c;收藏9次。在添加项目后&#xff0c;选择项目所在的路径&#xff0c;然后命令行启动主py文件。具体先看项目日志&#xff0c;根据日志在环境管理处下载包。首先下载项目需要的python版本。_宝塔部署python…

Typora安装,使用,图片加载全流程!!!

文章目录 前言&#xff1a;安装&#xff1a;破解&#xff1a;使用typora&#xff1a;关于CSDN加载不出图片&#xff1a;创建OSS&#xff1a;设置PicGo&#xff1a; 前言&#xff1a; ​ Typora是一款非常流行的Markdown编辑器&#xff0c;简单来说就是可以方便我们写博客。拿我…

禁忌搜索算法(TS算法)求解实例---旅行商问题 (TSP)

目录 一、采用TS求解 TSP二、 旅行商问题2.1 实际例子&#xff1a;求解 6 个城市的 TSP2.2 **求解该问题的代码**2.3 代码运行过程截屏2.4 代码运行结果截屏&#xff08;后续和其他算法进行对比&#xff09; 三、 如何修改代码&#xff1f;3.1 减少城市坐标&#xff0c;如下&am…

论文阅读: SigLit | SigLip |Sigmoid Loss for Language Image Pre-Training

论文地址&#xff1a;https://arxiv.org/pdf/2303.15343 项目地址&#xff1a;https://github.com/google-research/big_vision 发表时间&#xff1a;2023年3月27日 我们提出了一种用于语言图像预训练&#xff08;SigLIP&#xff09;的简单成对 Sigmoid 损失。与使用 softmax …

Redis 篇-初步了解 Redis 持久化、Redis 主从集群、Redis 哨兵集群、Redis 分片集群

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 分布式缓存概述 2.0 Redis 持久化 2.1 RDB 持久化 2.1.1 RDB 的 fork 原理 2.2 AOF 持久化 2.3 RDB 与 AOF 之间的区别 3.0 Redis 主从集群 3.1 搭建主从集群 3.2…

new/delete和malloc/free到底有什么区别

new和malloc 文章目录 new和malloc前言一、属性上的区别二、使用上的区别三、内存位置的区别四、返回类型的区别五、分配失败的区别六、扩张内存的区别七、系统调度过程的区别总结 前言 new和malloc的知识点&#xff0c;作为一个嵌入式工程师是必须要了解清楚的。new和malloc的…

驱动器磁盘未格式化危机:专业数据恢复实战指南

认识危机&#xff1a;驱动器中的磁盘未被格式化 在日常的数字生活中&#xff0c;我们时常依赖于各种存储设备来保存重要的文件、照片、视频等数据。然而&#xff0c;当某一天你尝试访问某个驱动器或外接硬盘时&#xff0c;突然弹出的“驱动器中的磁盘未被格式化。您想现在格式…

【CSS in Depth 2 精译_032】5.4 Grid 网格布局的显示网格与隐式网格(上)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

利用语义搜索和混合查询策略提升RAG系统的准确性

人工智能咨询培训老师叶梓 转载标明出处 在构建基于大模型&#xff08;LLM&#xff09;的生成式问答系统&#xff08;Generative Q&A&#xff09;时&#xff0c;检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;方法被广泛采用。RAG通过结合检索…

Gin渲染

HTML渲染 【示例1】 首先定义一个存放模板文件的 templates文件夹&#xff0c;然后在其内部按照业务分别定义一个 posts 文件夹和一个 users 文件夹。 posts/index.tmpl {{define "posts/index.tmpl"}} <!DOCTYPE html> <html lang"en">&…