C++11详解(三) -- 可变参数模版和lambda

文章目录

  • 1.可变模版参数
    • 1.1 基本语法及其原理
    • 1.2 包扩展
    • 1.3 empalce系列接口
      • 1.3.1 push_back和emplace_back
      • 1.3.2 emplace_back在list中的使用(模拟实现)
  • 2. lambda
    • 2.1 lambda表达式语法
    • 2.2 lambda的捕捉列表
    • 2.3 lambda的原理

1.可变模版参数

1.1 基本语法及其原理

1. C++11支持可变参数模版,也就是说支持可变数量参数的函数模版和类模版,可变数目的参数为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包:表示零或多个函数参数。
2. 用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class…或typename…指出接下来的参数表示零或多个类型列表;
在函数参数列表中,类型名后面跟…指出接下来表示零或多个形参对象列表;
函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
3. 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
4. 可变模版参数:参数类型可变,参数个数可变

…Args:模版参数包
…args:函数参数包
在这里插入图片描述

// 0-N个参数
// ...:表示多个参数
template<class ...Args>
void Print(Args&& ... args)
{
    // sizeof...是一个新的运算符
	// sizeof... 计算参数包里面有几个参数
	cout << sizeof...(args) << endl;
}

int main()
{
	Print(); // 0个参数
	Print(1);// 1个参数
	Print(1, 2.2);// 2个参数

	return 0;
}

在这里插入图片描述

  • 可变模版参数:本质上是模版的模版,可变模版参数实例化出对应的各个模版,这些模版再实例化出对应的类或者是函数

在这里插入图片描述

  • 总结:模版,一个函数模版可以实例化出多个不同类型参数的函数,可变参数模版,一个可变参数模版函数可以实例化出多个不同参数个数的模版函数
  • 主要就是一个可变参数模版实例化出多个函数模版,函数模版再实例化多个函数

1.2 包扩展

1. 包扩展:解析出参数包的内容
2. 编译时递归包括展,其实也不是递归,因为每次都生成不同的函数重载,只是每次用自己这个函数进行传参(包扩展)

参数包的第一种扩展方式(编译时递归):

// 打印参数包内容
template <class ...Args>
void Print(Args... args)
{
  // 可变参数模板是编译时解析,不是运行时解析
 // 下面是运行获取和解析,所以不支持这样用
 //  cout << sizeof...(args) << endl;
    for (size_t i = 0; i < sizeof...(args); i++)
    {
	   cout << args[i] << " ";
    }

     cout << endl;
}

// 这是编译时的逻辑,不是运行时逻辑
void ShowList()
{
	// 编译器递归终止的条件,参数包是0个时,直接匹配这个函数
	cout << endl;
}

template<class T,class ...Args>
void ShowList(T x, Args ...args)
{
    // 运行时
    /*if (sizeof...(args) == 0)
	return;*/
	
	cout << x << " ";
	// args是N个参数的参数包,一个参数传给x,剩下的N-1个参数
	// 传给args,继续往下递归
	ShowList(args...);
}

// 编译时递归推导解析参数
template<class ...Args>
void Print(Args ... args)
{
	ShowList(args...);
}

int main()
{
	Print();
	Print(1);
	Print(1, string("xxxxxx"));
	Print(1,string("xxx"),2.2);

	return 0;
}

模版写给编译器,让编译器生成对应的包括展
在这里插入图片描述
第二种扩展方式(通过函数调用):

// 下面两个GetArg都可以用
// 可以随便返回任何数
template<class T>
int GetArg(const T& x)
{
	cout << x << " ";
	return 0;
}

//template <class T>
//const T& GetArg(const T& x)
//{
//	cout << x << " ";
//	return x;
//}

template <class ...Args>
void Arguments(Args... args)
{}

template <class ...Args>
void Print(Args... args)
{
	// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
	Arguments(GetArg(args)...);
	有几个参数调用几次Arguments
}

// 实际上就是下面这段
// 本质可以理解为编译器编译时,包的扩展模式
// 将上⾯的函数模板扩展实例化为下⾯的函数
//void Print(int x, string y, double z)
//{
//   Arguments(GetArg(x), GetArg(y), GetArg(z));
//}

int main()
{
	Print(1, string("xxxxx"), 2.2);
	return 0;
}

1.3 empalce系列接口

1.3.1 push_back和emplace_back

  1. 传左值:都会走拷贝构造
    在这里插入图片描述
  2. 传右值:都走移动构造
    在这里插入图片描述
  3. 对于深拷贝
    直接传参,push_back,类模版实例化出string,会构造临时对象+移动构造
    emplace_back,会直接用const char* 构造
    所以emplace_back会稍微快一点
    对于浅拷贝
    比如Date,字节大小不大,push_back变为构造 + 拷贝构造,emplace_back还是直接构造
    在这里插入图片描述
    多参数的:
  4. 对于pair键值对,It1.emplace_back({“苹果”,1})是不支持的,因为emplace_back支持传多个参数类型,而不知道你传的是键值对pair
    在这里插入图片描述

1.3.2 emplace_back在list中的使用(模拟实现)

其实并不都是要像Print中解析包扩展,这样往下传即可

// 初始化列表
list_node() = default;

template <class... Args>
list_node(Args&&... args)
		: _next(nullptr)
		, _prev(nullptr)
		, _data(std::forward<Args>(args)...)
	    {}

// emplace_back()
template <class... Args>
void emplace_back(Args&&... args)
{
	insert(end(), std::forward<Args>(args)...);
}

// insert()
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
		Node* cur = pos._node;
		Node* newnode = new Node(std::forward<Args>(args)...);
		Node* prev = cur->_prev;

		// prev newnode cur
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;
		return iterator(newnode);
}

2. lambda

2.1 lambda表达式语法

1. lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部,也可以写在全局,lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。
2.lambda表达式的格式:
[捕捉列表](参数列表)->返回值类型{函数体}
3. lambda可以传给模版的参数,也可以传给auto,让auto自动推导

#include<algorithm>

struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

// 仿函数,价格升序
struct Compare1
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};

// 价格降序
struct Compare2
{
	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 } };
	// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中
	// 不同项的⽐较,相对还是⽐较⿇烦的,因为只能比较一项,那么这⾥lambda就很好⽤了

	// 写法一
	// sort(v.begin(), v.end(), Compare1());
	// sort(v.begin(), v.end(), Compare2());

	// 写法二
	// 用 lambda就可以用匿名函数对象比较多种数据,不用写专门的仿函数单独比较一项了
	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;});
	// [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; }
	// 匿名的函数对象

	return 0;
}

2.2 lambda的捕捉列表

1. lambda 表达式中默认只能用 lambda 函数体参数中的变量,如果想用外层作用域中的变量就需要进行捕捉
2. 第⼀种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x,y, &z] 表示x和y传值捕捉,z引用捕捉
3. lambda支持的是轻量级的

int y = 0;

// lambda可以写在全局
auto func2 = []()
{
	y++;
};

int main()
{
	int a = 0, b = 1, c = 2, d = 3;
	// 可以用lambda局部域,捕捉对象和全局对象
	auto func1 = [a, &b]
	{
	    // 传值捕捉,捕捉过来的是被const修饰的,不能修改
		// 引用捕捉可以修改
		// a++;
		b++;
		int ret = a + b + y;
		// 可以直接使用全局域的东西
		return ret;
	};

	cout << func1() << endl;
	func2();

	return 0;
}

3. 第二种捕捉方式叫隐式捕捉(全部捕捉):我们在捕捉列表写一个=表示隐式值捕捉,在捕捉列表写一个&表示隐式引用捕捉

// 隐式值捕捉
auto func3 = [=]
{
	int ret = a + b + c;
	return ret;
};
cout << func3() << endl;
// 匿名函数对象 + ()是调用函数

// 隐式引用捕捉
auto func4 = [&]
{
	a++;
	b++;
	c++;
};
func4();
cout << a << ":" << b << ":" << c << endl;

4. 混合捕捉:

  1. 有些传值捕捉,其他引用捕捉
  2. 有些引用捕捉,其他传值捕捉
混合捕捉,有些传值捕捉,其他引用捕捉
auto func5 = [&, a, b]
{
	// a++;
	// b++;
	c++;
	d++;

	return a + b + c + d;
};
func5();
cout << a << " " << b << " " << c << " " << d << endl;

 混合捕捉,有些传引用捕捉,其他传值捕捉
auto func6 = [=, &a, &b]
{
	a++;
	b++;

	return a + b + c;
};
func6();
cout << a << " " << b << " " << c << endl;

5. 局部的静态和全局不用捕捉,也不能捕捉,直接可以使用

static int t = 0;
// 局部的静态和全局变量不用捕捉直接使用
auto func7 = []
{
	int ret = t + y;
	return ret;
};

6. 默认传值捕捉是被const修饰的,但是在参数列表后面加mutable可以取消它的const属性,还有就是在lambda体内是可以修改它了,到外面它的值是不变的

int f = 0;
auto func8 = [=]()mutable
{
	a++;
	b++;
	c++;
	f++;
	
	return a + b + c + f;
};

f++后到外面值不变,还是传值捕捉
在这里插入图片描述

2.3 lambda的原理

1. lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for
这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了一个lambda 以后,编译器会生成一个对应的仿函数的类
2. 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传哪些对象(用谁就捕捉谁,并不是全部都捕捉

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

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

相关文章

【数据结构】_时间复杂度相关OJ(力扣版)

目录 1. 示例1&#xff1a;消失的数字 思路1&#xff1a;等差求和 思路2&#xff1a;异或运算 思路3&#xff1a;排序&#xff0b;二分查找 2. 示例2&#xff1a;轮转数组 思路1&#xff1a;逐次轮转 思路2&#xff1a;三段逆置&#xff08;经典解法&#xff09; 思路3…

OSPF基础(2):数据包详解

OSPF数据包(可抓包) OSPF报文直接封装在IP报文中&#xff0c;协议号89 头部数据包内容&#xff1a; 版本(Version):对于OSPFv2&#xff0c;该字段值恒为2(使用在IPV4中)&#xff1b;对于OSPFv3&#xff0c;该字段值恒为3(使用在IPV6中)。类型(Message Type):该OSPF报文的类型。…

第二篇:前端VSCode常用快捷键-以及常用技巧

继续书接上一回&#xff0c; 我们讲解了常用的vscode 插件。 vscode 常用的插件地址&#xff1a; 前端VSCode常用插件-CSDN博客 本篇文章&#xff0c;主要介绍vscode常用的快捷键&#xff0c;可以提高我们的开发效率。 一、VSCode常用的快捷键 注意&#xff0c;其实这个快捷…

【LeetCode】152、乘积最大子数组

【LeetCode】152、乘积最大子数组 文章目录 一、dp1.1 dp1.2 简化代码 二、多语言解法 一、dp 1.1 dp 从前向后遍历, 当遍历到 nums[i] 时, 有如下三种情况 能得到最大值: 只使用 nums[i], 例如 [0.1, 0.3, 0.2, 100] 则 [100] 是最大值使用 max(nums[0…i-1]) * nums[i], 例…

vue生命周期及其作用

vue生命周期及其作用 1. 生命周期总览 2. beforeCreate 我们在new Vue()时&#xff0c;初始化一个Vue空的实例对象&#xff0c;此时对象身上只有默认的声明周期函数和事件&#xff0c;此时data,methods都未被初始化 3. created 此时&#xff0c;已经完成数据观测&#xff0…

什么是三层交换技术?与二层有什么区别?

什么是三层交换技术&#xff1f;让你的网络飞起来&#xff01; 一. 什么是三层交换技术&#xff1f;二. 工作原理三. 优点四. 应用场景五. 总结 前言 点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神的孩子都在歌唱 大家好…

e2studio开发RA2E1(5)----GPIO输入检测

e2studio开发RA2E1.5--GPIO输入检测 概述视频教学样品申请硬件准备参考程序源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置GPIO口配置按键口配置按键口&Led配置R_IOPORT_PortRead()函数原型R_IOPORT_PinRead()函数原型代码 概述 本篇文章主要介绍如何…

【LLM】为何DeepSeek 弃用MST却采用Rejection采样

文章目录 拒绝采样 Rejection sampling&#x1f3af;马尔可夫搜索树 &#x1f333;RFT和SFT1. RFT和SFT的区别2. 如何将RFT用于数学推理任务&#xff1f; Reference 在提升大语言模型&#xff08;LLM&#xff09;推理能力时&#xff0c;拒绝采样&#xff08;Rejection Sampling…

股指入门:股指期货是什么意思?在哪里可以做股指期货交易?

股指期货是一种以股票指数为标的物的期货合约&#xff0c;也可以称为股票指数期货或期指。 股指期货是什么意思&#xff1f; 股指期货是一种金融衍生品&#xff0c;其标的资产是股票市场上的股指&#xff0c;例如标普500指数、道琼斯工业平均指数、上证50指数等。 股指期货允…

前端构建工具大比拼:Vite、Webpack、Parcel、esbuild 等热门工具使用分析

前端构建工具大比拼&#xff1a;Vite、Webpack、Parcel、esbuild 等热门工具使用分析 随着前端技术的不断发展&#xff0c;构建工具成为了每个前端项目的核心部分。通过合适的构建工具&#xff0c;我们能够优化开发效率、提升构建速度&#xff0c;并最终实现更加高效和灵活的开…

安装和使用 Ollama(实验环境windows)

下载安装 下载 https://ollama.com/download/windows 安装 Windows 安装 如果直接双击 OllamaSetup.exe 安装&#xff0c;默认会安装到 C 盘&#xff0c;如果需要指定安装目录&#xff0c;需要通过命令行指定安装地址&#xff0c;如下&#xff1a; # 切换到安装目录 C:\Use…

node.js使用mysql2对接数据库

一、引言 在现代Web开发中&#xff0c;Node.js作为一种高效、轻量级的JavaScript运行时环境&#xff0c;已经广泛应用于后端服务的开发中。而MySQL&#xff0c;作为一个广泛使用的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;提供了强大的数据存储和查询功能…

Unity 快速入门 1 - 界面操作

本项目将快速介绍 Unity 6的基本操作和功能&#xff0c;下载附件的项目&#xff0c;解压到硬盘&#xff0c;例如 D:\Unity Projects\&#xff0c; 注意整个文件路径中只有英文、空格或数字&#xff0c;不要有中文或其他特殊符合。 1. 打开Unity Hub&#xff0c;点击右上角的 O…

携程Java开发面试题及参考答案 (200道-上)

说说四层模型、七层模型。 七层模型(OSI 参考模型) 七层模型,即 OSI(Open System Interconnection)参考模型,是一种概念模型,用于描述网络通信的架构。它将计算机网络从下到上分为七层,各层的功能和作用如下: 物理层:物理层是计算机网络的最底层,主要负责传输比特流…

云轴科技ZStack+海光DCU:率先推出DeepSeek私有化部署方案

针对日益强劲的AI推理需求和企业级AI应用私有化部署场景&#xff08;Private AI&#xff09;&#xff0c;云轴科技ZStack联合海光信息&#xff0c;共同推动ZStack智塔全面支持DeepSeek V3/R1/Janus Pro系列模型&#xff0c;基于海光DCU实现高性能适配&#xff0c;为企业提供安全…

通信易懂唠唠SOME/IP——SOME/IP协议简介

一 简介 1.1 面向服务的中间件 SOME/IP是Scalable service-Oriented MiddlewarE over IP (SOME/IP)的缩写&#xff0c;基于IP的可扩展面向服务的中间件。 1.2 广泛应用于汽车嵌入式通信 SOME/IP是一种支持远程通信的汽车/嵌入式通信协议 。支持远程过程调用&#xff08;RPC…

游戏引擎学习第89天

回顾 由于一直没有渲染器&#xff0c;终于决定开始动手做一个渲染器&#xff0c;虽然开始时并不确定该如何进行&#xff0c;但一旦开始做&#xff0c;发现这其实是正确的决定。因此&#xff0c;接下来可能会花一到两周的时间来编写渲染器&#xff0c;甚至可能更长时间&#xf…

PostgreSql-COALESCE函数、NULLIF函数、NVL函数使用

COALESCE函数 COALESCE函数是返回参数中的第一个非null的值&#xff0c;它要求参数中至少有一个是非null的; select coalesce(1,null,2),coalesce(null,2,1),coalesce(null,null,null); NULLIF(ex1,ex2)函数 如果ex1与ex2相等则返回Null&#xff0c;不相等返回第一个表达式的值…

【苍穹外卖 Day1】前后端搭建 Swagger导入接口文档

项目技术选型 前端 直接使用打包好的nginx运行。 后端 1、导入初始代码结构如下&#xff1a; 2、将代码上传远程仓库。 3、创建数据库&#xff0c;并修改数据库配置。 4、断点调试&#xff0c;前后端联调。 5、使用Nginx代理&#xff0c;修改Nginx配置 好处&#xff1a;提…

八大排序算法细讲

目录 排序 概念 运用 常见排序算法 插入排序 直接插入排序 思想&#xff1a; 步骤&#xff08;排升序&#xff09;: 代码部分&#xff1a; 时间复杂度&#xff1a; 希尔排序 思路 步骤 gap的取法 代码部分&#xff1a; 时间复杂度&#xff1a; 选择排序 直接选…