C++11的更新介绍(lamada、包装器)

 

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:c++大冒险

总有光环在陨落,总有新星在闪烁


lambda表达式

C++98中的一个例子

在C++98中,如果想要进行排序,可以使用std::sort方法如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

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++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,

都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,

这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。


lambda表达式语法

  • lambda表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }

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

1.mutable可省略

int a = 0, b = 0;
auto func = [a, b]()->int {return 0; };

2.返回值类型可省略,编译器自动推导

int a = 0, b = 0;
auto func = [a, b]() {return 0; };

3.没有传参时列表可省略

int a = 0, b = 0;
auto func = [a, b] {return 0; };

4.而捕捉列表和函数体可以为空。

 // 省略了返回值类型,无返回值类型
    auto fun1 = [](int c){ }; 

因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

lambda表达式返回值实际上是仿函数,

该仿函数无法直接调用,如果想要调用,可借助auto将其赋值给一个变量,lambda返回的仿函数对象,其类名是随机的,因此必须用auto来接受这个仿函数对象。


捕获列表说明

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

1.[var]:表示值传递方式捕捉变量var

int a = 0, b = 0;
auto func = [a, b] (){return a+b};

注意:此时a,b具有常性,如果要去掉他们的常性就要加上mutable,此时lambda获得ab的方式是传值,所以不会影响到父作用域的ab

int a = 0, b = 0;
auto func = [a, b] ()mutable{return a+b};

2.[this]:表示值传递方式捕捉当前的this指针

3.[=]:表示值传递方式捕获所有父作用域中的变量(包括this)

int a = 0, b = 0;
auto func = [=]()mutable {return a + b; };

4.[&var]:表示引用传递捕捉变量var

此时lambda获得ab的方式是传引用,在lambda里修改ab会影响父作用域的ab

int a = 0, b = 0;
auto func = [&a,&b]{return a + b; };

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

int a = 0, b = 0;
auto func = [&]() {return a + b; };

6.我们还可以把传值和传引用混合使用,让部分参数传参,部分参数传引用

[x, &y]:以传值的形式捕获x,以传引用的形式捕获y
[=, &x]:以传值的形式捕获父作用域所有变量,以传引用的形式捕获x
[&, x]:以传值的形式捕获x,以传引用的形式捕获父作用域所有变量

注意:

  1.  父作用域指包含lambda函数的语句块
  2. c捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  3. 在块作用域以外的lambda函数捕捉列表必须为空
  4. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  5.  lambda表达式之间不能相互赋值,因为他们的实际类名不同(每一个类名都是lamdba随机生成的)

有lambda后,我们在需要仿函数的地方,就无需额外写一个仿函数的类,而是直接写lambda表达式:

int main()
{
 vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 
3 } };
 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; });
}

函数对象与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;
}

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

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

以直接将该变量捕获到。 ​

  

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如

果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator(),而这个类的名字是编译器随机产生的,所以lambda表达式之间不能相互赋值,即使看起来类型相同,因为他们的类名是不同的


模板参数中的lambda表达式

我们要给set传一个仿函数

set<int, Less<int>>;

请注意,这里我们所传的不是对象,而是类,但是lambda返回值本身就是对象,所以直接传lambda肯定是不可以的

这个时候decltype就登场了

auto func = [](int a, int b) {return a < b; };
set<int, decltype(func)>;

包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板

为什么需要function

如果一个变量f,可以按f()的形式调用函数,那么称f是一个可调用对象

基于此不难想到可调用对象包括:函数、仿函数、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);
}
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)->double { return d / 4; }, 11.11) << endl;
	return 0;
}

通过上面的程序验证,我们会发现useF函数模板实例化了三份

然而,这里的函数、仿函数、lambda的返回值和参数类型相同,可以认为三者极其相似,有没有办法让函数模板把他们识别为一种类型,从而只需要实例化一份呢

包装器可以很好的解决上面的问题,function包含在头文件<functional>中,是一个类模板,模板原型如下:

template <class T> function;

template <class Ret, class... Args>
class function<Ret(Args...)>;

其语法为:function<返回值(参数列表)>,只要是返回值和参数列表相同的可调用对象,经过这一层封装,都会变成相同的类型。 

int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
template<class F>
void Function(F f)
{
	static int count = 0;
	cout << count++<<endl;
	cout << &count << endl;
	cout << "================="<<endl;
}
int main()
{
	// 函数名(函数指针)
	function<int(int, int)> func1 = f;
	Function(func1);
	// 函数对象
	function<int(int, int)> func2 = Functor();
	Function(func1);
	// lamber表达式
	function<int(int, int)> func3 = [](const int a, const int b)
		{return a + b; };
	Function(func1);
}

可以看出func1、func2、func3被认为是一种类型


function接收对象成员函数

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)> func4 = &Plus::plusi;
	function<double(Plus*, double, double)> func5 = &Plus::plusd;
function<double(Plus, double, double)> func5 = &Plus::plusd;
}

注意事项:

  1. 等号右边的&符号别忘了写,对于非静态函数必需加上,非静态最好加上
  2. 等号右边的函数要指定类域
  3. 对于非静态函数,左边的第一个参数是类名或类指针

    适用场景:

比如力扣:波兰表达式

对于相邻两数我们要以case语句对+-*/进行讨论

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;
             }
         }
         //..........
     }
  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));
         }
         //........
     }
     return st.top();
}
};

冷知识:

  • function 类型相同的对象可以相互赋值

function<int(int)> f1 = [](int x) { return x * x; };
function<int(int)> f2 = f1; // f2 现在也是一个 lambda 表达式
  • function实现了对bool的重载

opearotr bool函数重载方式如下

class A
{
public:
	operator bool()
	{
		return 3==_a;
	}
	int _a = 2;
};

int main()
{
	A a;
	if (a)
		cout << "666" << endl;
	else
	{
		cout << "888" << endl;
	}
}

  

function 对象支持 bool 类型转换,可以用于判断 function 对象是否为空(未初始化)。

function<int(int)> f;
if (!f) {
	cout << "f is empty" << endl;
}

  


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

调用bind的一般形式

auto newCallable = bind(callable,arg_list);

newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数

  • 交换参数位置

交换了第一个参数和第二个参数的位置

C++11后新增一个命名空间域placeholders,其内部会存储很多变量,这些变量用于函数的传参

placeholders::_n表示原函数中的第n个参数

#include <functional>
void Mod(int a, int b)
{
	cout << a % b<<endl;
}
int main()
{
	std::function<void(int, int)> func1 = std::bind(Mod, placeholders::_2,placeholders::_1);
	func1(2, 1);
	Mod(2, 1);
	return 0;
}

  


  •   给参数绑定固定值

void Mod(int a, int b)
{
	cout << a % b<<endl;
}
int main()
{
	std::function<void(int,int)> func1 = std::bind(Mod, 1, placeholders::_2);
	func1(2, 10);
	Mod(1, 10);
	return 0;
}

  

可以看出即使我们在func1中给第一个参数传参为2,实际也依旧是1,

三种写法:

function<void(int,int)> func1 = std::bind(Mod, 1, placeholders::_1);
func1(1,10);
function<void(int)> func2 = std::bind(Mod, 1, placeholders::_1);
func2(10);
auto func3 = std::bind(Mod, 1, placeholders::_1);
func3(1, 10);
func3(10);

  我建议是auto,它的适用范围最大


🥰创作不易,你的支持对我最大的鼓励🥰

🪐~ 点赞收藏+关注 ~🪐

e3ff0dedf2ee4b4c89ba24e961db3cf4.gif

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

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

相关文章

51-42 NÜWA:女娲,统一的多模态预训练模型

21年11月&#xff0c;微软、北大联合发布了NUWA模型&#xff0c;一个统一的多模态预训练模型&#xff0c;在 8 个下游任务上效果惊艳。目前该项目已经发展成为一系列工作&#xff0c;而且都公开了源代码。 Abstract 本文提出了一种统一的多模态预训练模型N̈UWA&#xff0c;该…

FPGA中按键程序设计示例

本文中使用Zynq 7000系列中的xc7z035ffg676-2器件的100MHz PL侧的外部差分时钟来检测外部按键是否按下&#xff0c;当按键被按下时&#xff0c;对应的灯会被点亮。当松开按键时&#xff0c;对应的灯会熄灭。 1、编写代码 新建工程&#xff0c;选用xc7z035ffg676-2器件。 点击…

递归——汉诺塔

汉诺塔 法国数学家爱德华卢卡斯曾编写过一个印度的古老传说&#xff1a;在世界中心贝拿勒斯&#xff08;在印度北部&#xff09;的圣庙里&#xff0c;一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候&#xff0c;在其中一根针上从下到上地穿好了由大到小的64片金…

通过拖拽动态调整div的大小

最近遇到一个需求&#xff0c;页面展示两块内容&#xff0c;需要通过拖拽可以动态改变大小&#xff0c;如下图&#xff1a; 实现思路&#xff1a;其实就是改变div样式的width&#xff0c;本质上就是Dom操作。 完整代码&#xff1a;&#xff08;基于vue2项目实践&#xff09; …

23年新算法,SAO-SVM,基于SAO雪消融算法优化SVM支持向量机回归预测(多输入单输出)-附代码

SAO-SVM是一种基于SAO雪消融算法优化的支持向量机&#xff08;SVM&#xff09;回归预测方法&#xff0c;适用于多输入单输出的情况。下面是一个简要的概述&#xff0c;包括如何使用SAO-SVM进行回归预测的步骤&#xff1a; 步骤&#xff1a; 1. 数据准备&#xff1a; 收集并准…

API 自动化测试的实践与技巧

在软件开发的快速迭代过程中&#xff0c;及时准确地进行测试变得越来越重要。Apifox 作为一款先进的 API 接口管理和自动化测试平台&#xff0c;为测试人员提供了强大的工具来适应这种变化。以下是使用 Apifox 进行 自动化测试 的实际指南。 1. 接口管理与自动化测试设置 在 …

增强现实(AR)开发框架

增强现实&#xff08;AR&#xff09;开发框架为开发者提供了构建AR应用程序所需的基本工具和功能。它们通常包括3D引擎、场景图、输入系统、音频系统和网络功能。以下是一些流行的AR开发框架。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流…

证照之星是免费的吗?证照之星怎么使用?证照之星XE v7.0 免费版 证照之星版本区别

证件照是每个人都必须用到的&#xff0c;并且机构不同&#xff0c;对于证件照的规格也不同。为了提升我们的效率&#xff0c;我们会使用证照之星这类证件照编辑软件对证件照进行编辑&#xff0c;那么这种类型的证件照编辑软件应该如何使用&#xff0c;收费标准又是怎么样的呢&a…

C++中的运算符

一、算数运算符 1.1 加减乘除取模 #include <iostream> using namespace std;int main() {//加减乘除int a1 10;int b1 5;cout << "a1 b1 " << a1 b1 << endl;cout << "a1 - b1 " << a1 - b1 << endl;co…

安装zlmediakit和wvp-pro

通过docker安装zlmediakit&#xff0c;并单独启动wvp-pro.jar - zlmediakit安装 zlmediakit安装比较依赖环境和系统配置&#xff0c;所以这里直接使用docker的方式来安装。 docke pull拉取镜像 docker pull zlmediakit/zlmediakit:master使用下边命令先运行起来 sudo docke…

【深度学习实战(12)】训练之模型参数初始化

在深度学习模型的训练中&#xff0c;权重的初始值极为重要。一个好的初始值&#xff0c;会使模型收敛速度提高&#xff0c;使模型准确率更精确。一般情况下&#xff0c;我们不使用全0初始值训练网络。为了利于训练和减少收敛时间&#xff0c;我们需要对模型进行合理的初始化。 …

图文教程 | 2024年最新Typora激活使用教程合集

前言 汇总一下网上的三种方法。 &#x1f4e2;博客主页&#xff1a;程序源⠀-CSDN博客 &#x1f4e2;欢迎点赞&#x1f44d;收藏⭐留言&#x1f4dd;如有错误敬请指正&#xff01; 关于安装教程&#xff1a;http://t.csdnimg.cn/SCIQ8http://t.csdnimg.cn/SCIQ8自行跳转安装 一…

Ugee手写板Ex08 S在不同软件中的设置

手写笔的结构 功能对应于鼠标的作用笔尖鼠标左键上面第一个键鼠标右键&#xff08;效果有时候也不完全等同&#xff09;上面第二个键鼠标中键 以下测试的软件版本 软件版本windows10WPS2024春季16729Office2007SimpleTex0.2.5Ex08 S驱动版本4.2.4.231109 WPS-word ①点击审…

Zabbix 监控软件(一)

通常我们服务搭建成功 但不清楚服务器的运行状况&#xff0c;这时候就需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果&#xff0c;和网站的健康状态。 利用一个优秀的监控软件&#xff0c;我们可以: ●通过一个友好的界…

互联网技术知识点总览——操作系统知识点框架图

简介 本文对操作系统的知识点整体框架进行梳理和分享如下&#xff1a;

智能生活新体验:小米香薰加湿器技术解码

在现代家居生活中&#xff0c;科技与舒适性日益交织&#xff0c;智能家居产品成为提升生活品质的重要工具。小米香薰加湿器作为一款集科技与生活美学于一体的产品&#xff0c;其独特的设计和多功能性受到了广泛欢迎。今天&#xff0c;我们就来详细拆解这款融合了科技与香薰元素…

如何搭建线下陪玩系统(本地伴游、多玩圈子)APP小程序H5多端前后端源码交付,支持二开!

一、卡顿的优化方法 1、对陪玩系统源码中流媒体传输的上行进行优化&#xff0c;通过提升推流端的设备性能配置、推流边缘CDN节点就近选择等方式解决音视频数据源流的卡顿。 2、对陪玩系统源码中音视频数据的下载链路进行优化&#xff0c;通过选择更近更优质的CDN边缘节点来减少…

OpenHarmony实战开发-如何实现发布图片评论功能。

介绍 本示例将通过发布图片评论场景&#xff0c;介绍如何使用startAbilityForResult接口拉起相机拍照&#xff0c;并获取相机返回的数据。 效果图预览 使用说明 通过startAbilityForResult接口拉起相机&#xff0c;拍照后获取图片地址。 实现思路 1.创建CommentData类&…

Docker Desktop打开一直转圈的解决办法

安装Docker Desktop之前确保你的Hyper-V已经打开 开启后需要重新安装重新安装重新安装这是最关键的一步&#xff0c;博主自己看了很多教程&#xff0c;最后试着重装了一下解决了 安装DockerDesktop的时候我的电脑根本就没有Hyper-V这个功能选项&#xff0c;可能是这个问题 如…

RLHF强化学习对其算法:PPO、DPO、ORPO

参考&#xff1a; https://blog.csdn.net/baoyan2015/article/details/135287298 https://cloud.tencent.com/developer/article/2409553 最新的llama3是PPO、DPO两种方法使用 人类反馈强化学习 (RLHF)&#xff0c;它利用人类偏好和指导来训练和改进机器学习模型&#xff1a; …