【C++】C++11(三)

我们在C++11(2)中已经很好的解释了右值引用,这次来看看右值引用剩余的一些话题:可变参数包与emplace_back。

目录

  • 可变参数模板:
    • 可变参数的sizeof:
    • 可变参数的展开:
      • 递归函数方式展开参数包:
      • 逗号表达式展开参数包:
  • emplace_back:
    • 对于深拷贝的类:
      • 无区别:
      • 有区别:
    • 对于浅拷贝的类:
      • 无区别 &&有区别:

可变参数模板:

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。
现阶段我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止。

下面就是一个基本可变参数的函数模板:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

可变参数的sizeof:

我们有时需要知道传入的参数个数,于是我们可以使用sizeof进而得知。

示例:

template<class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	ShowList(1, "你好", 4);

	return 0;

结果:
在这里插入图片描述

可变参数的展开:

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。

递归函数方式展开参数包:

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value <<" ";
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

具体该怎么理解?
当我们调用函数传参时,第一个模板参数T会自动推演出参数包第一个参数,这样args这个参数包就少了一个参数,并继续调用自己,每次都会少第一个参数,就这样递归的推演直到遇到终止函数(即只剩一个参数时,因为编译器总会自动选择最匹配的函数进行调用)。

逗号表达式展开参数包:

这个比较奇葩。

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。

这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

expand函数中的逗号表达式:(PrintArg(args), 0),也是按照这个执行顺序,先执行PrintArg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(PrintArg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (PrintArg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。

由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

template <class T>
void PrintArg(T t)
{
	cout << t << " ";	
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args), 0)... };
	cout << endl;
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

注意:我们的可变模板参数都是在编译时推演出来的具体参数,所以我们也可以结合以下代码更好的理解逗号展开参数包。

void ShowList(char a1, char a2, std::string a3)
{
	int arr[] = { (PrintArg(a1), 0), 
				   (PrintArg(a2), 0),
				   (PrintArg(a3), 0),};
	cout << endl;
}

emplace_back:

在这里插入图片描述
注意到我们的emplace_back也是使用了可变模板参数,但是使用了万能引用,这样大大传值返回的开销。
我们上述进行展开参数包时也都没有使用万能引用。

另外,还有一个说法就是empalce普遍比push系列效率优秀,这又是怎么一回事?

我们来看看:

对于深拷贝的类:

首先对于深拷贝我们还是最好有一个确切的例子才能更好的理解:
下段代码是一个简单实现string的模拟类

namespace cyc
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "string(char* str)" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

		// 移动构造
		string(string&& s)
			:_str(nullptr)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		// s1 = 将亡值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}

无区别:

当我们创建一个list时,尾插一个string对象这是无区别的,原因在于

emplace时如果直接提供目标对象,则会调用拷贝构造或者移动语义进行资源的创建或者转移,在这里由于传递的是左值,因此会构造新的对象

	std::list<cyc::string> lt;
	// 深拷贝:
	// 无区别
	cyc::string s1("111");
	lt.push_back(s1);   // 拷贝构造
	lt.emplace_back(s1);// 拷贝构造

在这里插入图片描述
对于此的结论:两者都是深拷贝,无区别。
我们再来传入右值试一试:
在这里插入图片描述
可以看到仍没有区别,两者都是调用移动语义。

有区别:

当传入的都是一个常量字符串时呢?

emplace时当传入的是一个目标对象所需的构造参数时,会在最后调用一个定位new,直接构造.

在这里插入图片描述

在这里插入图片描述

对于浅拷贝的类:

对于深拷贝来说优化不是特别明显,但是对于浅拷贝来说,且很大的一个类时,emplace的性能就很棒了。

我们假设有一个日期类。

无区别 &&有区别:

	// 浅拷贝的类
	// 没区别
	std::list<Date> list2;
	Date d1(2023, 5, 28);
	list2.push_back(d1);
	list2.emplace_back(d1);

	cout << "-----------------------------------------" << endl;

	Date d2(2023, 5, 28);
	list2.push_back(move(d1));
	list2.emplace_back(move(d2));

	// 有区别
	cout << "-----------------------------------------" << endl;

	list2.push_back(Date(2023, 5, 28));
	list2.push_back({ 2023, 5, 28 });

	cout << "-----------------------------------------" << endl;
	list2.emplace_back(Date(2023, 5, 28)); // 构造+移动构造
	list2.emplace_back(2023, 5, 28);       // 直接构造

注意:这里对于可变参数模板传参有一个特点,不能使用initializer_list进行传参,因为本质上初始化列表是一个伪容器,除了迭代器就没有方法得到其中的元素了,而我们的参数包想要的是一个一个分离的参数,initializer_list会被认为是一个整体,编译器不能正确的将其解析为一个一个元素,故不支持。
而vector等STL容器就不一样了,他们是真正的容器。

虽然技术上可以实现,但是为了哲学性,严谨性等原因还是没有这样做。

完~~

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

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

相关文章

通过Arcgis从逐月平均气温数据中提取并计算年平均气温

通过Arcgis快速将逐月平均气温数据生成年平均气温数据。本次用2020年逐月平均气温数据操作说明。 一、准备工作 &#xff08;1&#xff09;准备Arcmap桌面软件&#xff1b; &#xff08;2&#xff09;准备2020年逐月平均气温数据&#xff08;NC格式&#xff09;、范围图层数据&…

k8s 部署RuoYi-Vue-Plus之server部署

1.先使用项目编排构建镜像, 修改prod使用的mysql, redis地址 获取运行的服务 kubectl get svc -n ruoyi对应连接修改 然后运行打包package命令, 生成jar包, 再打包为docker容器, 上传到所有节点上, 也可以上传到个人私有仓库 2.部署server-deploy.yaml 镜像名自行修改, apiV…

你知道的和你不知道的DOM操作技巧

你知道的和你不知道的DOM操作技巧 亲爱的前端小伙伴们&#xff0c;今天我们来聊聊那些你可能知道或者不知道的DOM操作技巧。作为一名前端开发者&#xff0c;如果你还在为DOM操作头疼&#xff0c;那么这篇文章绝对能让你茅塞顿开。让我们一起来探索一下DOM的奥秘吧&#xff01;…

【开源合规】开源许可证风险场景详细解读

文章目录 前言关于BlackDuck许可证风险对比图弱互惠型许可证举个例子具体示例LGPL系列LGPL-2.0-onlyLGPL-2.0-or-laterLGPL-2.1-onlyLGPL-2.1-or-laterLGPL-3.0-onlyLGPL-3.0-or-laterMPL系列MPL-1.0MPL-1.1MPL-2.0EPL系列EPL-1.0EPL-2.0互惠型许可证GPL系列GPL-1.0GPL-2.0GPL-…

Mac系统清理工具:您的数字生活杂务处理师

有没有觉得您的Mac有时候像是需要一个好的春季大扫除一样&#xff1f;随着我们不断使用电脑&#xff0c;各种不需要的文件、老旧的数据和忘记的安装包就像家里的灰尘一样慢慢积累。幸运的是&#xff0c;有了一些出色的Mac系统清理工具&#xff0c;我们可以轻松将这些数字灰尘拂…

Java中实现二维数组(矩阵)的转置

在矩阵运算中&#xff0c;矩阵的转置是一个基本操作&#xff0c;即将矩阵的行变成列&#xff0c;列变成行。在Java中&#xff0c;我们可以通过编写一个方法来实现二维数组的转置。下面&#xff0c;我将详细介绍如何在Java中完成这一任务&#xff0c;并提供完整的代码示例。 编…

【Linux】命令执行的判断依据:;,,||

在某些情况下&#xff0c;很多命令我想要一次输入去执行&#xff0c;而不想要分次执行时&#xff0c;该如何是好&#xff1f; 基本上有两个选择&#xff0c; 一个是通过shell脚本脚本去执行&#xff0c;一种则是通过下面的介绍来一次入多个命令。 1.cmd&#xff1a;cmd&#…

AR增强现实汽车装配仿真培训系统开发降低投入费用

随着互联网的无处不在&#xff0c;AR增强现实技术正逐步融入我们生活的每一个角落。深圳华锐视点作为一家引领行业潮流的AR内容开发的技术型公司&#xff0c;正以其卓越的技术实力和专业的服务团队&#xff0c;推动着国内AR技术向更加成熟和多元化的方向迈进。 深圳华锐视点提供…

成都晨持绪:抖音电商带货需要交钱吗

在抖音这个充满创意与可能的平台上&#xff0c;电商带货成为了一种新兴而又时尚的职业。然而&#xff0c;伴随着无数的点击与转发&#xff0c;有一个问题始终萦绕在人们心头——抖音电商带货需要交钱吗? 如画卷展开&#xff0c;抖音平台以其独特的算法和庞大的用户基础构建了一…

GDidees CMS v3.9.1 本地文件泄露漏洞(CVE-2023-27179)

前言 CVE-2023-27179 是一个影响 GDidees CMS v3.9.1 及更低版本的任意文件下载漏洞。这个漏洞存在于 /_admin/imgdownload.php 文件中&#xff0c;攻击者可以通过向 filename 参数传递恶意输入来下载服务器上的任意文件。 漏洞的根源在于对用户输入的 filename 参数处理不当…

前后端如何实现非对称加解密-使用RSA为例讲解!

写在最前面&#xff0c;RSA是一种非对称加密算法&#xff0c;使用不同的公钥和私钥进行加密和解密。 下面是使用RSA进行加密和解密的代码示例&#xff1a; 前端&#xff1a;使用CryptoJS进行RSA加密 在前端JavaScript中&#xff0c;使用jsencrypt库来进行RSA加密&#xff1a…

网络安全----防御----防火墙安全策略组网

防火墙组网 要求&#xff1a; 1&#xff0c;DMz区内的服务器&#xff0c;办公区仅能在办公时间内(9:00-18:00)可以访问&#xff0c;生产区的设备全天可以访问。 2&#xff0c;生产区不允许访问互联网&#xff0c;办公区和游客区允许访问互联网 3&#xff0c;办公区设备10.0.…

【AI前沿】深度学习:技术、发展与前沿应用

文章目录 一、深度学习的背景与发展1.1 背景1.2 早期发展1.3 突破性进展1.4 近年发展 二、深度学习的基本概念2.1 神经网络2.2 多层感知器&#xff08;MLP&#xff09;2.3 卷积神经网络&#xff08;CNN&#xff09;2.4 循环神经网络&#xff08;RNN&#xff09;2.5 生成对抗网络…

【C++修行之道】string类练习题

目录 387. 字符串中的第一个唯一字符 125. 验证回文串 917. 仅仅反转字母 415. 字符串相加&#xff08;重点&#xff09; 541. 反转字符串 II 387. 字符串中的第一个唯一字符 字符串中的第一个唯一字符 - 力扣&#xff08;LeetCode&#xff09; 给定一个字符串 s &#…

【Windows】实现窗口子类化(基于远程线程注入)

目录 前言 原理解释 完整项目 相关文献 文章出处链接&#xff1a;[https://blog.csdn.net/qq_59075481/article/details/140334106] 前言 众所周知&#xff0c;DLL 注入有多种用途&#xff0c;如热修补、日志记录、子类化等。本文重点介绍使用 DLL 注入对窗口进行子类化。…

路径规划之基于二次规划的路径平滑Matlab代码

参考&#xff1a; 自动驾驶决策规划算法第二章第二节(上) 参考线模块_哔哩哔哩_bilibili 自动驾驶决策规划算法第二章第二节(下) 参考线代码实践_哔哩哔哩_bilibili QP函数&#xff0c;二次规划的逻辑 function [smooth_path_x,smooth_path_y] QP(path_x, path_y, w_cost_s…

Java方法入门(006)

♦️方法的概念 什么是方法&#xff1f; 方法是将一组完成特定功能的代码整合在一起&#xff0c;以达到简化开发&#xff0c;减少代码耦合&#xff0c;提高代码复用性的结构&#xff0c;类似与C语言中的函数。方法是程序中最小的执行单元&#xff0c;可降低代码的重复性。 如用…

Python 100道基础入门练习题!附解析

看书&#xff0c;看视频都可以帮助你学习代码&#xff0c;但都只是辅助作用&#xff0c;学好 Python&#xff0c;最重要的还是 多敲代码&#xff0c;多刷题。 不知道怎么找题刷题的小伙伴&#xff0c;可以看看这里今天带来了100道练习题。覆盖了基本语法&#xff0c;数据结构&…

【Docker-compose】搭建php 环境

文章目录 Docker-compose容器编排1. 是什么2. 能干嘛3. 去哪下4. Compose 核心概念5. 实战 &#xff1a;linux 配置dns 服务器&#xff0c;搭建lemp环境&#xff08;Nginx MySQL (MariaDB) PHP &#xff09;要求6. 配置dns解析配置 lemp Docker-compose容器编排 1. 是什么 …

从零开学C++:类和对象(上)

引言&#xff1a;在学习了C的入门级知识之后&#xff0c;现在就让我们一起进入类和对象的学习吧&#xff0c;该知识点我将分为上&#xff0c;中&#xff0c;下三个部分对其进行讲解。 更多有关C语言和数据结构的知识详解可前往个人主页&#xff1a;计信猫 目录 一&#xff0c;类…