【C++】C++11 (3): lambda表达式和包装器

news2025/1/23 13:14:54

一、lambda表达式

  • C++98中的一个例子
    在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
#include <algorithm>
#include <functional>
int main()
{
	int a[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(a, a + sizeof(a) / sizeof(a[0]));
	// 如果需要降序,需要改变元素的比较规则
	std::sort(a, a + sizeof(a) / sizeof(a[0]), std::greater<int>());
	return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

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 = { { "apple", 1.0, 5 }, { "banana", 3, 4 }, 
				{ "orange", 2.2, 3 }, { "pineapple", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}

上面的写法太复杂了,每次为了实现一个algorithm算法, 都要重新去写一个类。如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

int main()
{
	vector<Goods> v = { { "apple", 1.0, 5 }, { "banana", 3, 4 },
				{ "orange", 2.2, 3 }, { "pineapple", 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;
	});
}

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数

1. lambda表达式语法

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

-lambda表达式各部分说明

  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上文中的变量供lambda 函数使用。
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即参数为空)。
  4. ->return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获 到的变量。

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

int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};
	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {
		return a + 3;
	};
	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {
		b = a + c;
	};
	fun1(10);
	cout << a << " " << b << endl;
	// 各部分都很完善的lambda函数
	auto fun2 = [=, &b](int c)->int {
		return b += a + c;
	};
	cout << fun2(10) << endl;
	// 复制捕捉x
	int x = 10;
	auto add_x = [x](int a) mutable {
		x *= 2;
		return a + x;
	};
	cout << add_x(10) << endl;
	return 0;
}

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调 用,如果想要直接调用,可借助auto将其赋值给一个变量。

2. 捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

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

注意:

  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
  • 比如:[=, &a, &b]表示以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
  • [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。
  • 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域 或者 非局部变量都会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同
void (*PF)();
int main()
{
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
	// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
	//f1 = f2;    // 编译失败--->提示找不到operator=()
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	// 可以将lambda表达式赋值给相同类型的函数指针
	PF = f2;
	PF();
	return 0;
}

3. 函数对象与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);
	// lambda
	auto r2 = [=](double monty, int year)->double {
		return monty * rate * year;
	};
	r2(10000, 2);
	return 0;
}

从使用方式上来看,函数对象与lambda表达式完全一样。

函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

在这里插入图片描述

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。


二、包装器

1. function

function包装器,也可以叫作适配器。C++中的function本质是一个类模板,也是一个包装器。 那么我们为什么需要function呢?

ret = func(x);func可能是什么呢?
func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lambda表达式对象?这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!
为什么呢?我们继续往下看

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);// 返回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;
	// lambda表达式
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

在这里插入图片描述

通过上面的程序验证,我们会发现useF函数模板实例化了三份(count有三个不同地址)。

包装器可以很好的解决上面的问题

std::function在头文件<functional>

在这里插入图片描述

模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

使用方法如下:

#include <functional>
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b) {
		return a + b;
	}
};
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lambda表达式
	std::function<int(int, int)> func3 = [](const int a, const int b) {
		return a + b;
	};
	cout << func3(1, 2) << endl;
	// 类的成员函数
	// 静态成员函数可以不加 &
	std::function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	// 注意有this参数
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

有了包装器,如何解决模板的效率低下,实例化多份的问题呢?

#include <functional>
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;
	// lambda表达式
	std::function<double(double)> func3 = [](double d)->double {
		return d / 4;
	};
	cout << useF(func3, 11.11) << endl;
	return 0;
}

在这里插入图片描述

包装器对这些可调用对象的类型进行了统一,useF只实例化了一份,三次调用的都是同一份。

在这里插入图片描述

包装器的其他一些场景:
逆波兰表达式求值:

class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		for (auto& str : tokens) {
			if (str == "+" || str == "-"
				|| str == "*" || str == "/") {
				int right = st.top(); st.pop();
				int left = st.top(); st.pop();
				switch (str[0]) {
				case '+':
					st.push(left + right);
					break;
				case '-':
					st.push(left - right);
					break;
				case '*':
					st.push(left * right);
					break;
				case '/':
					st.push(left / right);
					break;
				}
			}
			else
			{
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

使用包装器后:

class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		map<string, function<int(int, int)>> opFuncMap = {
			{ "+",  [](int i, int j) {return i + j; } }, 
			{ "-",  [](int i, int j) {return i - j; } }, 
			{ "*",  [](int i, int j) {return i * j; } }, 
			{ "/",  [](int i, int j) {return i / j; } } 
		};
		for (auto& str : tokens) {
			if (opFuncMap.find(str) != opFuncMap.end()) {
				int right = st.top(); st.pop(); 
				int left = st.top(); st.pop();
				st.push(opFuncMap[str](left, right));
			} else {
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

2. bind

std::bind函数定义在functional头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

在这里插入图片描述

可以将 bind 函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用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为第二个参数,以此类推。

// 使用举例
#include <functional>
int Plus(int a, int b)
{
	return a + b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};
int main()
{
	// 表示绑定函数Plus 参数分别由调用 func1 的第一,二个参数指定
	std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	// func2的类型为 function<void(int, int, int)> 与func1类型一样
	// 表示绑定函数 Plus 的第一,二为: 1, 2
	auto func2 = std::bind(Plus, 1, 2);
	cout << func1(1, 2) << endl;
	cout << func2() << endl;

	Sub s;
	// 绑定成员函数
	std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
	// 参数调换顺序
	std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, placeholders::_2, placeholders::_1);
	cout << func3(1, 2) << endl;// 1 - 2
	cout << func4(1, 2) << endl;// 2 - 1
	return 0;
}

在这里插入图片描述

通过使用 bind 函数,我们可以创建灵活的函数对象,方便地进行参数控制和函数重用。


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

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

相关文章

S32 Design Studio for ARM(S32DS)下载和安装

1. S32 Design Studio for ARM 介绍 S32 Design Studio for ARM&#xff08;下面简称S32DS&#xff09;&#xff0c;是 NXP 官方在 2014 年官方推出的&#xff0c;专门面向 S32K、KEA、MAC57D54H等系列微控制器的集成开发环境。 S32DS是由Eclipse和一些插件集成而来的开发平台…

自来水收费系统适合应用于哪些场景?

自来水收费系统是一种用于自来水公司或供水管理部门的软件系统&#xff0c;旨在帮助自动化自来水的收费和管理过程。该系统可以帮助自来水公司更好地管理水资源&#xff0c;提高供水质量和效率&#xff0c;同时也可以为用户提供更加便捷和安全的用水服务。下面将从多个方面来介…

centos7通过epel源安装redis

目录 一、下载epel源 二、下载并且启动 一、下载epel源 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo 二、下载并且启动 yum install redis systemctl enable --now redis

c代码和c++代码互相调用

note 不管是c代码调用c代码&#xff0c;还是c代码调用c代码&#xff0c;本质上都是g兼容gcc的功能 c代码调用c代码 lib.h #ifndef _LIB_H_void libfun(void);#endif lib.c #include <stdio.h> #include "lib.h"void libfun(void) {printf("this is i…

Intel nuc 清除cmos密码

设备为intel nuc7&#xff0c;其他版本可以参考&#xff0c;步骤如下 1、找到主板上的bios_sec的三针跳线 2、将跳线开关取下 3、按住电源键3秒&#xff0c;电源指示灯变为橙色后松手 4、耐心等待显示器显示bios recovery倒计时画面 5、按esc取消bios recovery 6、显示器显…

Oss文件上传-简化版

这里使用的是element的上传组件&#xff0c;相关上传钩子看官网 一、获取到oss后台相关的签名数据 建议通过接口获取 二、上传 //获取oss相关参数// getOsstokenasync beforeUploadMasterImg(file) {console.log("点击上传");await this.getOssSingnature(file);},…

这7款UI设计工具值得一试!

在设计工作中&#xff0c;UI设计更注重于人和产品互动式时的交互效果&#xff0c;也就是页面内容丰富程度。无论是想打造哪种风格的UI设计界面&#xff0c;这个过程都离不开好用的UI设计工具&#xff0c;本文总结了7款年度好用的UI设计工具&#xff0c;能帮助设计师更轻松地创作…

压裂工艺原理简述

一、压裂程序 1. 压裂概念 在石油领域&#xff0c;压裂是指采油或采气过程中&#xff0c;利用水力作用&#xff0c;使油气层形成裂缝的一种方法&#xff0c;又称水力压裂。压裂是人为地使地层产生裂缝&#xff0c;改善油在地下的流动环境&#xff0c;使油井产量增加&#xff…

【kubernetes系列】kubernetes之计算资源管理

资源类型 在 Kubernetes 中&#xff0c;Node 提供资源&#xff0c;而 Pod 使用资源。其中资源分为计算&#xff08;CPU、Memory、GPU&#xff09;、存储&#xff08;Disk、SSD&#xff09;、网络&#xff08;Network Bandwidth、IP、Ports&#xff09;。这些资源提供了应用运行…

DIN - 序列模型之深度兴趣网络(阿里)

&#x1f525; DIN来自于 阿里 盖坤团队 在 KDD-2018 发的论文《Deep Interest Network for Click-Through Rate Prediction》。该模型在当时已经应用于阿里的电商广告推荐业务&#xff0c;效果不错。 文章目录 1、介绍&#xff1a;2、单值特征 & 多值特征&#xff1a;3、动…

共筑信创生态!亚信科技AntDB数据库与用友、东方通、星辰天合达成兼容互认

近日&#xff0c;亚信科技AntDB数据库与用友U8 cloud、东方通应用服务器TongWeb V7.0、星辰天合全产品体系完成兼容适配。经测试&#xff0c;AntDB数据库与U8 cloud产品&#xff0c;TongWeb V7.0服务器&#xff0c;星辰天合天合翔宇分布式存储系统、统一数据平台XEDP、超融合平…

分析分布式架构-技术

分布式系统的主要目的 提高系统的性能 提高吞吐量&#xff0c;服务更多的客户。提高并发和流量。 通过以下的技术提高处理高并发场景的能力 缓存系统&#xff0c;更快的响应客户端的请求。降低对数据库的压力(提高响应速度) 前端浏览器&#xff0c;网络&#xff0c;后端服务&a…

深入理解Linux网络——TCP连接的开销

文章目录 一、相关实际问题二、Linux内核如何管理内存1&#xff09;node划分2&#xff09;zone划分3&#xff09;基于伙伴系统管理空闲页面4、slab分配器5&#xff09;小结 三、TCP连接相关内核对象1&#xff09;socket函数直接创建1. sock_inode_cache对象申请2. tcp对象申请3…

自建sqlserver迁移到aliyun的rds for sqlserver实战

大家好&#xff0c;在实际中有些客户有上云的需求&#xff0c;需要把线下自建的sqlserver迁移至aliyun的rds for sqlserver。大家第一时间想到的是用dts工具&#xff0c;根据工作经验&#xff0c;DTS迁移mysql类的数据库比较成熟&#xff0c;但是迁移sqlserver之类会有问题。首…

二本逆互联网大厂! 高薪就业

【二本屌丝也能逆袭&#xff01;毕业四年从小公司到大厂之路&#xff0c;这就是我的逆袭&#x1f680;✨】 大家对如何逆袭互联网大厂而感到惊讶&#xff1f;作为计算机专业的大学生&#xff0c;想必你对于进入互联网行业有很多的期望和追求。但是面对激烈的竞争和复杂的招聘要…

wasserstein distance简单记录

W a s s e r s t e i n Wasserstein Wasserstein d i s t a n c e distance distance一般被称为推土距离&#xff0c;假设有两个分布 P ( x ) P(x) P(x)和 Q ( y ) Q(y) Q(y) &#xff0c;两个分布间的推土距离为&#xff1a; W ( P , Q ) inf ⁡ γ ∈ Π ( P , Q ) E ( x ,…

深度学习trick

本次Tricks主要面向于深度学习中计算机视觉方向的研究&#xff0c;分为数据增广方法、训练技巧&#xff0c;参数调节这三个方面进行深入的分析。内容有一部分是基于openmmlab的mmdet和mmseg两个框架上的成熟应用案例进行详细阐述。 首先是数据增广的tricks&#xff1a; 0、Fli…

【Java基础教程】(十八)包及访问权限篇 · 下:Java编程中的权限控制修饰符、单例设计模式 (Singleton)和多例设计模式的综合探析~

Java基础教程之包及访问权限 下 本节学习目标1️⃣ 访问控制权限2️⃣ 命名规范3️⃣ 单例设计模式 (Singleton)4️⃣ 多例设计模式 本节学习目标 掌握Java 中的4种访问权限&#xff1b;掌握Java 语言的命名规范&#xff1b;掌握单例设计模式与多例设计模式的定义结构&#x…

【HISI IC萌新虚拟项目】cpu_if的接口cpu_agent utils搭建

关于整个虚拟项目,请参考: 【HISI IC萌新虚拟项目】Package Process Unit项目全流程目录_尼德兰的喵的博客-CSDN博客 前言 spt_agent utils已经完成了(虽然之后可能还会有微调),接下来完成配置通路cpu interface对应的utils。这个通路比较特殊,一是带反馈的接口,二是时…

Android 通过插桩来代理线程池

前言 在日常开发App的过程中&#xff0c;难免需要依赖第三方Sdk&#xff0c;这样就无形中增加了我们自己App的线程数&#xff0c;从而会导致App出现内存溢出、Cpu消耗增加等等负面影响。如果依赖的Sdk提供了线程池代理的接口还好&#xff0c;那样直接设置我们自定义的线程池。但…