【C++】C++11 ~ 右值引用和移动语义

news2024/12/23 0:28:55

🌈欢迎来到C++专栏~~右值引用和移动语义


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
  • 目前状态:大三非科班啃C++中
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!
    在这里插入图片描述

请添加图片描述

文章目录

  • 🌈欢迎来到C++专栏~~右值引用和移动语义
    • 一. 基本概念
      • 🌈左值 vs 右值
      • 🌈左值引用 vs 右值引用
    • 二. 右值引用的场景与意义
      • 🎨左值引用的使用场景
      • 🎨左值引用的短板
      • 🎨右值引用和移动语义
        • 🥑移动构造
        • 🥑移动赋值
        • 😎容器新增内容
      • 🎨右值引用能引用左值吗?
      • 🎨右值引用的其他使用场景
    • 三. 完美转发
      • ✨万能引用
      • ✨完美转发保持值的属性
      • ✨完美转发的使用场景
  • 📢写在最后

请添加图片描述

一. 基本概念

🌈左值 vs 右值

什么是左值?

左值是一个表示数据的表达式(如 变量名解引用的指针)

  • 左值可以被取地址,也可以被修改const修饰的左值除外
  • 左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边
int main()
{
	//左值:可以取地址
	int a = 10;
	const int b = 20;//const不能修改(例外)
	int* p = &a;
	*p = 10;
	return 0;
}

什么是右值?

右值也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等

  • 右值不能被取地址,也不能被修改
  • 右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边
int main()
{
	double x = 1.1, y = 2.2;

	//以下几个都是常见的右值,不能取地址
	10;
	x + y;
	fmin(x, y);

	//错误示例(右值不能出现在赋值符号的左边)
	//10 = 1;
	//x + y = 1;
	//fmin(x, y) = 1;
	return 0;
}
  • 右值本质就是一个临时变量或常量值,比如代码中的10就是常量值,表达式x+y和函数fmin的返回值就是临时变量,这些都叫做右值
  • 这些临时变量和常量值并没有被实际存储起来,这也就是为什么右值不能被取地址的原因,因为只有被存储起来后才有地址

🌈左值引用 vs 右值引用

C++11中新增了右值引用的语法特性,为了进行区分,于是将C++11之前的引用就叫做左值引用。但是无论左值引用还是右值引用,本质都是给对象取别名

左值引用

左值引用就是对左值的引用,给左值取别名,通过“&”来声明

int main()
{
	//以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;

	//以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;
	int& pvalue = *p;
	return 0;
}

右值引用

右值引用就是对右值的引用,给右值取别名,通过“&&”来声明

int main()
{
	double x = 1.1, y = 2.2;
	
	//以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);

	//以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	return 0;
}

要注意的是

左值引用可以引用右值吗?

  • 左值引用不能引用右值,因为这涉及权限放大的问题,右值是不能被改变的,而左值是可以修改的
  • 有一个例外:const,const左值引用能够保证被引用的数据不会被修改,使得const左值引用可以引用右值

那就是const左值引用既可以引用左值,也可以引用右值,这样的其实我们已经见多了

template<class T>
void func(const T& x)//x既能接收左值,也能接收右值
{
	cout << x<< endl;
}
int main()
{
	string s("hello");
	func(s);       //s为左值

	func("world"); //"world"为右值
	return 0;
}

🎃奇怪的现象
右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改,如果不想让被引用的右值被修改,可以用const修饰右值引用

int main()
{
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	const double&& rr2 = x + y;

	rr1 = 20;
	rr2 = 5.5; //报错
	return 0;
}

此处的rr1就被转化成左值了,埋下伏笔哈哈哈,后面会遇到

右值引用可以引用左值吗?

  • 右值引用只能引用右值,不能引用左值
  • 但是右值引用可以引用move以后的左值(boss登场)

move函数是C++11标准提供的一个函数,被move后的左值能够赋值给右值引用(斯国一)

int main()
{
	int a = 10;

	//int&& r1 = a;     //右值引用不能引用左值
	int&& r2 = move(a); //右值引用可以引用move以后的左值
	return 0;
}

二. 右值引用的场景与意义

🎨左值引用的使用场景

我们先来看看左值引用的使用场景:

  • 做参数:1️⃣减少拷贝,提高效率 2️⃣做输出型参数
  • 做返回值:1️⃣减少拷贝提高效率,2️⃣引用返回,可以修改返回对象

🎨左值引用的短板

左值引用虽然能避免不必要的拷贝操作,但左值引用并不能完全避免

  • 左值引用做参数,能够完全避免传参时不必要的拷贝操作
  • 左值引用做返回值,并不能完全避免函数返回对象时不必要的拷贝操作

💥短板:如果函数返回的是一个局部对象,该变量出了函数作用域就被销毁了,这种情况下不能用左值引用作为返回值,只能以传值的方式返回,这就是左值引用的短板。

还好之前写了博客复习:复习传送门

举个例子:int版本的to_string函数,这个to_string函数就不能使用左值引用返回,因为to_string函数返回的是一个局部变量(出作用域销毁了)

namespace ljj
{
	cl::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		cl::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += (x + '0');
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}

此时调用to_string函数返回时,就一定会调用string的拷贝构造函数

int main()
{
	ljj::string ret = to_string(3465);
	return 0;
}

为此C++11就出手了,提出右值引用就是为了解决左值引用的这个短板的!

🎨右值引用和移动语义

那怎么样才能让编译器不优化,我们手动操作呢?那就要增加移动构造和移动赋值方法

ps:C++11对右值进行了划分

  1. 内置类型的右值 —— 纯右值
  2. 自定义类型的右值 —— 将亡值(即将死亡的值)

🥑移动构造

移动构造是一个构造函数,该构造函数的参数是右值引用类型的,移动构造本质就是将传入右值的资源窃取过来,占为己有,这样就避免了进行深拷贝,所以它叫做移动构造,就是窃取别人的资源来构造自己的意思

调用swap函数将传入右值的资源窃取过来,占为己有

//移动构造
string(string&& s)//右值引用
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	cout << "string(string&& s) -- 资源转移" << endl;

	swap(s); //资源互换
}

在这里插入图片描述

移动构造的价值

  • 没有引入移动构造之前,拷贝构造采用的是const左值引用接收,无论传入的是左值还是右值,都会调用拷贝构造
  • 增加了移动构造之后,采用的是右值引用接收参数,如果传入的是右值的话,就会调用移动构造(最匹配原则
  • string的拷贝构造函数做的是深拷贝,而移动构造函数中只需要调用swap函数进行资源的转移,因此调用移动构造的代价比调用拷贝构造的代价小(少了一次深拷贝)
    在这里插入图片描述

我们来看看编译器的优化:

当一个函数在返回局部对象时,会先用这个局部对象拷贝构造出一个临时对象,然后再用这个临时对象来拷贝构造我们接收返回值的对象(深拷贝)

在这里插入图片描述

编译器会优化成:一步到位:只需要一次拷贝构造,还要什么临时对象,我懂你意思,直接给给ret

在这里插入图片描述

在C++11标准出来之前这里应该调用两次string的拷贝构造函数,但最终被编译器优化成了一次,减少了一次无意义的深拷贝。(并不是所有的编译器都做了这个优化)

C++11出来后,编译器仍然保持了这种优化方式

“将亡值”str马上就要被销毁了,那还不如把它的资源转移给别人用,因此编译器在识别这种“将亡值”时会将其识别为右值,这样就可以匹配到参数类型为右值引用的移动构造函数

在这里插入图片描述

可以理解成:

  • 移动构造:在战争中,你穿上了别人不用的鞋子
  • 拷贝构造:没有鞋子给你穿,你要自己去买(拷贝)一双

记住记住:右值引用swap()的是将亡值,拷贝构造中不能直接swap,因为对象不是将亡值,下面的例子中,swap完后s1就销毁了,那我们不可以这样做

int main()
{
	ljj::string s1("1111111");
	ljj::string s2(s1);
	return 0;
}

🥑移动赋值

😎移动赋值是一个赋值运算符重载函数,该函数的参数是右值引用类型的,移动赋值也是将传入右值的资源窃取过来,占为己有,这样就避免了深拷贝,所以它叫移动赋值,就是窃取别人的资源来赋值给自己的意思

  • 如果我们不是用函数的返回值来构造一个对象,而是用一个之前已经定义出来的对象来接收函数的返回值,这时编译器就无法进行优化了
    在这里插入图片描述

编译器并没有对这种情况进行优化,因此在C++11标准出来之前,对于深拷贝的类来说这里就会存在两次深拷贝,因为深拷贝的类的赋值运算符重载函数也需要以深拷贝的方式实现

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

	return *this; //返回左值(支持连续赋值)
}

移动赋值的优势:

  • 在没有增加移动赋值之前,由于原有operator=函数采用的是const左值引用接收参数,因此无论赋值时传入的是左值还是右值,都会调用原有的operator=函数
  • 由于移动赋值采用的是右值引用接收参数,因此如果赋值时传入的是右值,那么就会调用移动赋值函数(最匹配原则
  • string原有的operator=函数做的是深拷贝,而移动赋值函数中只需要调用swap函数进行资源的转移,因此 调用移动赋值的代价比调用原有operator=的代价小

在这里插入图片描述

此时当to_string函数返回局部的string对象时,会先调用移动构造生成一个临时对象,然后再调用移动赋值将临时对象的资源转移给我们接收返回值的对象,这个过程虽然调用了两个函数,但这两个函数要做的只是资源的移动,而不需要进行深拷贝,大大提高了效率

延长了资源的生命周期
在这里插入图片描述

😎容器新增内容

C++11标准出来之后,STL中的容器都增加了移动构造和移动赋值

以string类为例,这是string类增加的移动构造:
在这里插入图片描述
这是string类增加的移动赋值:

在这里插入图片描述

🎨右值引用能引用左值吗?

字面上是不可以的,但也不是完全不可以,当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值

move函数的名字具有迷惑性,move函数实际并不能搬移任何东西,该函数唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义

move定义如下:

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
	//forward _Arg as movable
	return ((typename remove_reference<_Ty>::type&&)_Arg);
}

🎨右值引用的其他使用场景

右值引用版本的插入函数

C++11标准出来之后,STL容器插入接口函数也增加了右值引用版本
在这里插入图片描述

右值引用版本的意义:
如果vector容器当中存储的是string对象,那么在调用push_back向vector容器中插入元素

int main()
{
	vector<ljj::string> v;
	ljj::string s1("hello");
	v.push_back(s1);//调用string的拷贝构造
	cout << "——————————————————————————————————" << endl;
	
	v.push_back("hello");//调用string的移动构造
	return 0;
}

push_back函数需要先构造一个结点(在内存池中定位new),然后将该结点插入到底层的双链表当中

  • C++11之前容器的push_back接口只有一个左值引用版本,因此在push_back函数中构造结点时,这个左值只能匹配到string的拷贝构造函数进行深拷贝
  • C++11出来之后,string类提供了移动构造函数,并且容器的push_back接口提供了右值引用版本,此时如果传入push_back函数的string对象是一个右值,那么在push_back函数中构造结点时,这个右值就可以匹配到string的移动构造函数进行资源的转移,这样就避免了深拷贝,提高了效率

在这里插入图片描述

三. 完美转发

✨万能引用

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值

template<class T>
void PerfectForward(T&& t)
{
	//...
}

万能引用是根据传入实参的类型进行推导,如果传入的实参是一个左值,那么这里的形参t就是左值引用,如果传入的实参是一个右值,那么这里的形参t就是右值引用

举个例子:

void Func(int& x)
{
	cout << "左值引用" << endl;
}
void Func(const int& x)
{
	cout << "const 左值引用" << endl;
}
void Func(int&& x)
{
	cout << "右值引用" << endl;
}
void Func(const int&& x)
{
	cout << "const 右值引用" << endl;
}
template<class T>
void PerfectForward(T&& t)
{
	Func(t);
}
int main()
{
	int a = 10;
	PerfectForward(a);       //左值
	PerfectForward(move(a)); //右值

	const int b = 20;
	PerfectForward(b);       //const 左值
	PerfectForward(move(b)); //const 右值

	return 0;
}

PerfectForward函数时传入左值、右值、const左值、const右值,结果输出的全是左值,为什么呢?

  • 根本原因是:右值被引用后会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改,所以在PerfectForward函数中调用Func函数时会将t识别成左值

在这里插入图片描述

就是说,右值经过一次参数传递后其属性会退化成左值,如果想要在这个过程中保持右值的属性,就需要用到完美转发

✨完美转发保持值的属性

要想在参数传递过程中保持其原有的属性,需要在传参时调用forward函数

template<class T>
void PerfectForward(T&& t)
{
	//完美转发:保持t引用的属性
	Func(std::forward<T>(t));
}

在这里插入图片描述

✨完美转发的使用场景

一个简化版的list类,类当中分别提供了左值引用版本和右值引用版本的push_back和insert函数

namespace ljj
{
	template<class T>
	struct ListNode
	{
		T _data;
		ListNode* _next = nullptr;
		ListNode* _prev = nullptr;
	};
	template<class T>
	class list
	{
		typedef ListNode<T> node;
	public:
		//构造函数
		list()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		//左值引用版本的push_back
		void push_back(const T& x)
		{
			insert(_head, x);
		}
		//右值引用版本的push_back
		void push_back(T&& x)
		{
			insert(_head, std::forward<T>(x)); //完美转发
		}
		//左值引用版本的insert
		void insert(node* pos, const T& x)
		{
			node* prev = pos->_prev;
			node* newnode = new node;
			newnode->_data = x;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pos;
			pos->_prev = newnode;
		}
		//右值引用版本的insert
		void insert(node* pos, T&& x)
		{
			node* prev = pos->_prev;
			node* newnode = new node;
			newnode->_data = std::forward<T>(x); //完美转发

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pos;
			pos->_prev = newnode;
		}
	private:
		node* _head; //指向链表头结点的指针
	};
}

只要右值每往下一层传,都要完美转发,否则统统变成左值

分别传入左值和右值调用不同版本的push_back

int main()
{
	ljj::list<ljj::string> lt;
	ljj::string s("1111"); 
	lt.push_back(s);      //调用左值引用

	lt.push_back("2222"); //调用右值引用
	return 0;
}

ps:代码中push_back和insert函数的参数T&&是右值引用,而不是万能引用,因为在list对象创建时这个类就被实例化了,后续调用push_back和insert函数时,参数T&&中的T已经是一个确定的类型了

📢写在最后

中国奇谭真不戳

请添加图片描述

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

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

相关文章

mongodb 使用密钥文件身份验证部署副本集

一 副本集介绍 集群中每个节点有心跳检测 如果由于资源限制&#xff0c;可以部署一主一从一仲裁 副本集集群可以实现主从的自动切换 Read Preference 在客户端连接中&#xff0c;可以实现读取优先&#xff0c;就是连接器会自动判断&#xff0c;把读取请求发送到副本集中的…

whois命令常见用法

whois命令常见用法whois命令简介安装whoisWindows使用whoisLinux安装whoiswhois常见用法Linux下whois查询域名注册信息whois命令简介 whois就是一个用来查询域名是否已经被注册&#xff0c;以及注册域名的详细信息的数据库&#xff08;如域名所有人、域名注册商&#xff09;。…

分析第一个安卓项目

整体分析 .gradle和.idea 这两个目录下放置的都是Android Studio自动生成的一些文件&#xff0c;我们无须关心&#xff0c;也不要去手动编辑。 app 项目中的代码、资源等内容几乎都是放置在这个目录下的。 gradle 这个目录下包含了gradle wrapper的配置文件&#xff0c;使…

flowable流程设计器的几个bug修改记录

今天根据客户反馈开源项目宁波阿成 (nbacheng) - Gitee.com 有一些bug&#xff0c;主要是前端的&#xff0c;所以今天修正一下 1、对于第一个节点是发起人&#xff0c;后面是多人选择的时候&#xff0c;approval数据被清空了 就是上面的流程&#xff0c;发现有问题 对这种情…

实例13:体育竞技分析

高手过招&#xff0c;胜负只在毫厘之间 计算思维&#xff1a;抽象自动化 模拟&#xff1a;抽象比赛过程自动化执行N场比赛 当N越大时&#xff0c;比赛结果分析会越科学 自顶向下&#xff1a;解决复杂问题的有效方法&#xff0c;将总问题拆分为小问题&#xff0c;分而治之自底向…

【微服务】分布式搜索引擎elasticsearch(3)

分布式搜索引擎elasticsearch&#xff08;3&#xff09;1.数据聚合1.1.聚合的种类1.2.DSL实现聚合1.2.1.Bucket聚合语法1.2.2.聚合结果排序1.2.3.限定聚合范围1.2.4.Metric聚合语法1.2.5.小结1.3.RestAPI实现聚合1.3.1.API语法1.3.2.业务需求1.3.3.业务实现2.自动补全2.1.拼音分…

微信小程序 java php校园快递物流取件及上门服务

系统分为用户和管理员两个角色 用户的主要功能有&#xff1a; 1.用户注册和登陆系统 2.用户查看系统的公告信息 3.用户在线快递下单&#xff0c;支付订单&#xff0c;在线订购快递取件 4.用户在线预约快递&#xff0c;填写快递预约信息 5.用户个人中心在线充值 6.用户个人中心修…

若依配置教程(七)Excel预览功能实现

实现效果及源码 实现效果如下图所示&#xff1a; 实现思路&#xff1a; 1.动态表格&#xff1a;定义表头数组&#xff0c;表格遍历表头生成表格列 2.读取excel文件内容&#xff0c;封装表头&#xff0c;绑定表格数据 代码修改 首先参考若依官网&#xff0c;先实现excel导入功…

浅析综合型大厦视频监控平台建设的必要性和重点功能

一、方案背景随着现代科学技术的发展&#xff0c;监控系统已成为综合型大厦安全防范必不可少的一部分。为了保障整个大厦的安全管理&#xff0c;借助安防监控系统能够迅速而有效地全面管理、禁止或处理突发性事件。因此建设一套优良的监控系统对于大厦各方面的管理都显得尤为重…

从零开始配置vim(31)——git 配置

很抱歉又拖更了这么久了&#xff0c;在这个新公司我想快速度过试用期&#xff0c;所以大部分的精力主要花在日常工作上面。但是这个系列还是得更新下去&#xff0c;平时只能抽有限的业余时间来准备。这就导致我写这些文章就慢了一些。 废话不多说&#xff0c;咱们正式开始有关g…

【Leetcode】面试题 01.06. 字符串压缩、面试题 05.07. 配对交换

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 面试题 01.06. 字符串压缩&#xff1a; 面试题 05.07. 配对交换 面试题…

【贪心数学困难题】1739. 放置盒子

⭐️前面的话⭐️ 本篇文章介绍【贪心数学困难题】1739. 放置盒子题解&#xff0c;展示语言java。 &#x1f4d2;博客主页&#xff1a;未见花闻的博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4cc;本文由未见花闻原创&am…

20份可视化大屏模板,直接套用真香(文末附源码)

最近有不少小伙伴问我&#xff1a;有没有数据可视化大屏模板&#xff0c;而且要B格很高的。 这不&#xff0c;立马安排。特地给大家准备了20张精美、炫酷而且十分实用的可视化大屏模板&#xff0c;涉及机械、加工、零售、银行、交通等行业。 只要你有数据就能够迅速套用到模板…

Spring-IOC/DI配置管理第三方bean

Spring-IOC/DI配置管理第三方bean 1&#xff0c;IOC/DI配置管理第三方bean 1.1 案例:数据源对象管理 本次案例将使用咱们前面提到过的数据源Druid(德鲁伊)和C3P0来配置学习下。 1.1.1 环境准备 学习之前&#xff0c;先来准备下案例环境: 创建一个Maven项目 pom.xml添加依…

代码随想录day52 动态规划

代码随想录day52 动态规划 题1143.最长公共子序列 1&#xff0c;本题和最长重复子数组的区别在于本题不要求连续&#xff0c;那么当遇到元素不相同时不能重头开始累计&#xff0c;而应该取前面情况中的最大值。 2&#xff0c;dp数组依然为dp[i][j]&#xff1a;长度为[0, i -…

提取页码去重再编号

实例需求&#xff1a;A列为档号数据&#xff0c;由多段数字组成&#xff0c;使用减号作为分隔符&#xff0c;最后一段数字代表页数&#xff0c;注意页数是不连续的&#xff0c;倒数第二段数字是代表档案中的第几本&#xff0c;每个档案都是从1开始。现在需要在B列创建“卷内顺序…

A股level2行情数据接口可以获取可转债数据吗?

可以的&#xff0c;究竟如何通过level2行情获取可转债数据&#xff1f; level2行情数据接口数据包括每只股票每3秒的快照数据&#xff0c;每10秒的快照数据毫秒级差别推送数据&#xff0c;收集多个逐笔成交数据和逐笔委托数据。 通过数据提供商获取即时收集数据和盘后数据。数…

七、请求和响应

请求和响应 web框架本质就是处理用户发起的请求&#xff0c;然后返回响应结果。请求&#xff0c;和响应就是框架中的数据流。 请求 当页面被请求时&#xff0c;django会创建一个HttpRequest对象&#xff0c;该对象包含关于请求的元数据。然后django加载适当的视图&#xff0…

[NOI Online 2021 入门组] 切蛋糕

题目描述: Alice、Bob 和 Cindy 三个好朋友得到了一个圆形蛋糕&#xff0c;他们打算分享这个蛋糕。 三个人的需求量分别为 a,b,c现在请你帮他们切蛋糕&#xff0c;规则如下&#xff1a; 每次切蛋糕可以选择蛋糕的任意一条直径&#xff0c;并沿这条直径切一刀&#xff08;注意…

【C++】模板进阶:非类型的模板参数与模板的特化

一、非类型模板参数 模板参数分类&#xff1a;类型形参与非类型形参。 类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之后的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将…