模版进阶 非类型模版参数

news2024/11/24 16:20:02

一.模板参数分类类型形参与非类型形参。

类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。

非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

#include<iostream>
using namespace std;
#define N 100
template<class T>//静态的栈 也就是要控制大小固定
class stack
{
private:
	int _a[N];
	int _top;
};
int main()
{
	stack<int> s1;
	return 0;
}

这里我们是通过定义宏来固定栈的大小的 但是如果我们想要同时实现两个大小不同的栈要如何实现

#include<iostream>
using namespace std;
//#define N 100
template<class T,size_t N >//静态的栈 也就是要控制大小固定
class stack
{
private:
	int _a[N];
	int _top;
};
int main()
{
	stack<int,10> s1;//10
	stack<int,100>s2;//100
	return 0;
}

这里通过非类型的模版参数来实现同时定义不同大小的栈

这里的s1和s2两个栈 在本质上还是两个不同的类实现的  是两个不同的类型  只不过是将写不同类的工作交给了编译器去实现

这里需要注意的是 在c++20之前只允许整型作非类型模版的参数 

 c++20之后也支持double等内置类型 内置类型的指针也是可以的 

这里N已经是常量了 无法修改 但是这里没有报错是因为按需实例化

#include<iostream>
using namespace std;
//#define N 100
template<class T,size_t N >//静态的栈 也就是要控制大小固定
class stack//这里N已经是常量了 无法修改 但是这里没有报错是因为按需实例化
{
public:
	void func()
	{
		N++;
	}
private:
	int _a[N];
	int _top;
};
int main()
{
	stack<int,10> s1;//10
	stack<int,100>s2;//100
	//s1.func();
	return 0;
}

这里没有报错是因为按需实例化的原因 因为这里系统为了节约资源 不调用

是不会去检查很多的细节的  只有调用了 才会进行具体检查 

int main()
{
	stack<int,10> s1;//10
	stack<int,100>s2;//100
	s1.func();
	return 0;
}

类型模版可以给缺省值 非类型模版也可以给缺省值

template<class T,size_t N =1000 >//静态的栈 也就是要控制大小固定
class stack//这里N已经是常量了 无法修改 但是这里没有报错是因为按需实例化
{
public:
	void func()
	{
		N++;
	}
private:
	int _a[N];
	int _top;
};
int main()
{
	stack<int> s3;//这里使用了缺省值 1000大小
	//stack<int,10> s1;//10
	//stack<int,100>s2;//100
	//s1.func();
	return 0;
}

在我们的std库中也是有使用了非类型模版参数的结构的  可以看做用一个类去封装了静态数组

使用array去与普通的数组去做对比

int main()
{
	array<int,15> a1;
	int a2[15];
	return 0;
}

他们两者最大的区别是什么? 最大的区别是检查是否越界的能力 

对于普通的数组 他的越界检查能力是非常敷衍的 

int main()
{
	array<int,15> a1;
	int a2[15];

	a2[15];
	a2[16];
	a2[17];
	return 0;
}

这里对普通数组进行只读检查是无法检查出越界的 即便是第一个越界的位置15也无法检测

只有检查写时才能检查出来

并且在稍微远一点的越界位置无论读写都是无法检查出来的

而如果是array数组的话 则无论远近无论读写 都可以检查出来

这就是array与普通的数组最大的区别

 那么为什么array能够检查的这么仔细呢 因为array是自定义类型 在调用operator[]时 可以设立很多的检查

array的缺点 1.不负责自动初始化  2.他的开空间是不在堆上面 而是开在栈针上面    堆很大但是 栈针不大  所以array的设计也是非常鸡肋的 不如用vector

#include<iostream>
#include<array>
#include<vector>
using namespace std;
int main()
{
	vector<int> v = {1,2,3,4,5,6,7};
	vector<double> v1 = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
	int i = 1;
	int j = { 1 };
	int k  { 1};

	return 0;
}

这里的初始化方法是多样性的 这里有好有坏 是被使用者诟病的地方 这里会导致别人的误解

这里我们想要打印vector的内容 制作了一个遍历器函数 

void printvector(vector<int> v)
{
	std::vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++  ;
	}
	cout << endl;
}
int main()
{
	vector<int> v = {1,2,3,4,5,6,7};
	vector<double> v1 = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
	int i = 1;
	int j = { 1 };
	int k  { 1};
	printvector(v);
	//printvector(v1);
	return 0;
}

这里单个的类型的遍历是没有问题的 如果想要进行多个类型的遍历 就需要用到模版

using namespace std;
template <class T>
void printvector(vector<T> v)
{
	std::vector<T>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++  ;
	}
	cout << endl;
}
int main()
{
	vector<int> v = {1,2,3,4,5,6,7};
	vector<double> v1 = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
	int i = 1;
	int j = { 1 };
	int k  { 1};
	printvector(v);
	printvector(v1);
	return 0;
}

这里却会发生错误  这里正确写法需要再迭代器之前写一个typename

oid printvector(vector<T> v)
{
	typename std::vector<T>::iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++  ;
	}
	cout << endl;
}
int main()
{
	vector<int> v = {1,2,3,4,5,6,7};
	vector<double> v1 = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
	int i = 1;
	int j = { 1 };
	int k  { 1};
	printvector(v);
	printvector(v1);
	return 0;
}

那么这里的报错的原因是什么呢 而typename的作用又是什么呢?

这里的报错是因为编译器从上向下检查 检查到这个函数内部时 由于vector::iterator 这个还没有实例化 所以系统不会进行 仔细检查 所以iterator对于编译器来说现在是未知的 而vector类域指定下 可能会取到静态变量 类型  无法确定 所以报错 

而typename的作用就是给编译器声明一下 这里是一个类型 先跳过这里等到实例化之后再次进行检查编译  

还有另一种不加typename的方式 那就是直接使用auto

void printvector(vector<T> v)
{
	//typename std::vector<T>::iterator it = v.begin();
	auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++  ;
	}
	cout << endl;
}
int main()
{
	vector<int> v = {1,2,3,4,5,6,7};
	vector<double> v1 = {1.1,2.2,3.3,4.4,5.5,6.6,7.7};
	int i = 1;
	int j = { 1 };
	int k  { 1};
	printvector(v);
	printvector(v1);
	return 0;
}

这时我们的代码也是可以正常运行的

二.模版的特化

函数模版的特化 特化就是进行特殊化处理

template<class T>
bool Less(T left, T right)
{
	return left < right;
}
 int main()
 {
	 cout << Less(1, 2) << endl;

	 Date d1(2022, 7, 7);
	 Date d2(2022, 7, 8);
	 cout << Less(d1, d2) << endl;

	 Date* p1 = &d1;
	 Date* p2 = &d2;
	 cout << Less(p1, p2) << endl;  // 调用特化之后的版本,而不走模板生成了
	 return 0;
 }

这里是对日期类的比较大小

这里的第三个比较使用的是指针  那么日期类的指针比较的大小不在是日期的的大小比较 而是变成了比较指针地址的大小 这是我们不想要的结果 这时就需要用到函数模版特化去解决

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

 //对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
 }

 int main()
 {
	 cout << Less(1, 2) << endl;

	 Date d1(2022, 7, 7);
	 Date d2(2022, 7, 8);
	 cout << Less(d1, d2) << endl;

	 Date* p1 = &d1;
	 Date* p2 = &d2;
	 cout << Less(p1, p2) << endl;  // 调用特化之后的版本,而不走模板生成了
	 return 0;
 }

这时通过特化版本专门为指针设计了一个版本来比较大小实现

函数模版特化也是有缺点的

template<class T>
bool Less(const T &left,const T & right)
{
	return left < right;
}

 //对Less函数模板进行特化
template<>
bool Less<Date*>(const Date* & left,const Date* &right)
{
return *left < *right;
 }

这时const修饰下 对于特化模版的处理是要不同的 

template<class T>
bool Less(const T &left,const T & right)
{
	return left < right;
}

 //对Less函数模板进行特化
template<>
bool Less<Date*>(Date* const  & left, Date* const  &right)
{
return *left < *right;
 }

这才是正确的写法 

原因是原模版中const修饰的T类型的内容 也就是指针指向的内容    而将const放在data*前面 则会修饰到指针 并没有修饰到指针指向的内容 所以要将data*放在const之前 让const修饰到指针指向的内容

最好是不使用特化模版 而是直接去创建一个解决日期类指针比较大小的函数

template<class T>
bool Less(const T &left,const T & right)
{
	return left < right;
}

 //对Less函数模板进行特化
//template<>
//bool Less<Date*>(Date* const  & left, Date* const  &right)
//{
//return *left < *right;
// }
bool Less(Date* left, Date* right)
{
	return *left < *right;
}
 int main()
 {
	 cout << Less(1, 2) << endl;

	 Date d1(2022, 7, 7);
	 Date d2(2022, 7, 8);
	 cout << Less(d1, d2) << endl;

	 Date* p1 = &d1;
	 Date* p2 = &d2;
	 cout << Less(p1, p2) << endl;  // 调用特化之后的版本,而不走模板生成了
	 return 0;
 }

这样也可以正常使用

 编译器的调用规则时有现成就用现成的之后在使用模版

所以一个模版一个特化模版 一个函数的情况下会优先使用 函数

接下来介绍类的特化模版

// 类模板
template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>-原模板" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

// 特化:针对某些特殊类型,进行特殊化处理
// 全特化
template<>
class Data<int, char>
{
public:
	Data() { cout << "Data<int, char>- 全特化" << endl; }
};

 int main()
 {
	 Data<int, int>d1;
	 Data<int, char>d2;

	 return 0;
 }

在原模版中写过私有成员之后 在特化模版的类中就不需要在写一次了

这里的特化模版是通过不同的参数类型去调用的 

还有偏特化 或者称为半特化

template<class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>- 半特化" << endl; }
};

二级指针也算是指针

template<>
class Data<int, char>
{
public:
	Data() { cout << "Data<int, char>- 全特化" << endl; }
};
//偏特化 /半特化
template<class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>- 半特化" << endl; }
};
//两个参数偏特化为指针类型 
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }

private:
	T1 _d1; T2 _d2;
};
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1&d1,const T2& d2)
		:_d1(d1)
		,_d2(d2)
	{ cout << "Data<T1&, T2&>" << endl; }

private:
	const T1 &_d1;const T2& _d2;
};
 int main()
 {
	 Data<int, int>d1;
	 Data<int, char>d2;
	 Data<double, char>d3;
	 Data<int*, int*>d4;
	 Data<int&, int&> d5(1,2);
	 return 0;
 }

这里不仅可以特化普通的类型还可以特化指针和引用 指针中的特化是一个大类 其中的二级指针三级指针都可以使用这个大的特化类

3.模版分离编译 

//func.h
template<class T>
T Add(const T& left, const T& right);
//func.cpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
// test.cpp
#include"func.h"
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);

	return 0;
}


这里我们采用类比法来进行比对

//func.h
void func();
//func.cpp
void func()
{
	cout << "func" << endl;
}
// test.cpp
#include"func.h"
int main()
{
	//Add(1, 2);
	//Add(1.0, 2.0);
	func();
	return 0;
}

这时我们可以发现普通的函数声明和定义分离是可以正常使用的

但是对于函数模版来说声明和定义分离却无法正常使用 

在test.cpp中调用add函数 这里有声明知道要将参数定义为什么样的类型 但是对于func.cpp中只有定义却没有声明不知道要将其参数设置为什么类型 因此add不会被编译 生成指令也就没有将add的地址放入到符号表当中去 所以链接也就找不到

解决方法 显示实例化

//func.cpp
#include<iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
template
int Add(const int& left, const int& right);

template
double Add(const double & left , const double &right);

这时候代码就可以正常使用了

但是函数模版声明和定义分离也是有弊端的 需要不停的去加入显示实例化 所以最好的解决方法就是不要去进行声明和定义分离

对于类模版来说 长点的成员函数 声明和定义分离 写到当前文件类外面 短的可以直接定义在类里面 默认就是inline

   模板总结 【优点】

1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生

2. 增强了代码的灵活性

【缺陷】

1. 模板会导致代码膨胀问题,也会导致编译时间变长

2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误 

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

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

相关文章

打印1000年到2000年之间的闰年

我们要打印1000年到2000年之间的闰年&#xff0c;首先我们先输出1000年到2000年之间的所有的年份&#xff0c;同时我们将闰年的判断方法输入到其中 闰年需要满足下列两个条件的其中之一&#xff1a; 1.能被4整除但不能被100整除 2.能被400整除 打印1000年到2000年之间的闰年…

PCL 贪婪三角投影三角化

目录 一、概述二、代码三、结果 一、概述 PCL中贪婪三角投影三角化的简单使用案例 二、代码 greedy_projection.cpp #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/search/kdtree.h> // for KdTree #include <pcl/features/…

【软件系统架构设计师-案例-1】架构风格

1. 请用200字以内说明系统可靠性的定义及包含的4个子特性&#xff0c;并简要指出提高系统可靠性一般采用哪些技术&#xff1f; &#xff08;1&#xff09;可靠性定义&#xff1a;系统在规定的时间或环境条件下&#xff0c;完成规定功能的能力&#xff0c;就是系统无故障运行的…

【AD速成】半小时入门AltiumDesigner(速通基础)

一.创建工程 1.工程 文件->新的->项目 PCB选择<Default>Project Name填入自己的工程名称Folder选择工程保存的路径 创建后如图&#xff1a; 这里的.prjPcb的文件即为AD的工程文件。 如果没有Project栏可以在视图->面板->Projects中勾选Projects CtrlS保存工…

Java学习-JVM调优

目录 1. JDK工具包 1.1 jps 1.2 jstat 1.3 jinfo 1.4 jmap 1.5 jhat 1.6 jstack 1.7 VisualVM 2. 第三方工具 2.1 GCEasy 2.2 MAT 2.3 Arthas 3. JVM参数 3.1 标准参数 3.2 非标准参数 3.3 不稳定参数 4. 调优 4.1 什么时候调优 4.2 调优调什么 4.3 调优原…

LINUX 系统管理操作

基础编辑 Tab 单击一次补全 双击列举候选 CTRL U 删除光标前 K 删除光标后 L 清屏&#xff08;只剩新命令行&#xff09; C 取消当前操作 反斜杠“\” 在需要转行的时候输入反斜杠 “\”回车 在>后继续输入 帮助命令 help 命令 大部分内建命令 格式&#xff1a;h…

直播预告 | 药品安全与合规保障难?智能温度监测助您领先制药工业4.0!

您是否在为温度敏感药品的运输和存储合规而苦恼&#xff1f; 是否担心冷链物流中的温度监控漏洞导致药品质量下降&#xff1f; 制药环境中的温湿度监控是否让您无从下手&#xff1f; 这些问题不仅影响药品的安全性&#xff0c;也直接影响企业的合规性和市场竞争力。如何确保环…

Android 保存图片到相册却不在“照片”中显示,只在相簿中显示

背景 需要从网络上下载图片到本地&#xff0c; 并显示在相册中 问题 将图片保存到内存中&#xff0c; 通过媒体API插入到媒体库后&#xff0c;图片只在“相簿”中的“所有项目”中显示&#xff0c;第一个页面的“照片”却不显示 解决办法 图片被保存到 Pictures/AppName 目录…

Linux系统通过编辑crontab来设置定时任务---定时关机

在Linux系统中&#xff0c;crontab 是用来设置周期性被执行的指令的守护进程。通过编辑 crontab&#xff0c;您可以安排定时任务&#xff0c;比如定时关机、定时备份文件、定时运行脚本等。以下是如何编辑 crontab 来设置定时任务的步骤&#xff1a; 打开终端&#xff1a;您可以…

基于springboot+vue的在线宠物用品交易网站

一、系统架构 前端&#xff1a;vue | element-ui | html 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven | nodejs 二、代码及数据库 三、功能介绍 01. web端-首页1 02. web端-首页2 03. web端-注册 04. web端-登录 05. w…

“万万没想到”,“人工智能”获得2024年诺贝尔物理学奖

近日&#xff0c;2024年诺贝尔物理学奖颁发给了机器学习与神经网络领域的研究者&#xff0c;这是历史上首次出现这样的情况。这项奖项原本只授予对自然现象和物质的物理学研究作出重大贡献的科学家&#xff0c;如今却将全球范围内对机器学习和神经网络的研究和开发作为了一种能…

SQLite Developer使用说明

1.SQLite Developer下载 SQLite Developer官方版是SharpPlus出品的一款数据库管理工具。支持对sqlite3数据库的管理&#xff0c;能够自动完成窗口显示和执行数据库命令等多种特色。并且支持打开.db文件&#xff0c;适用于Android的开发。另外&#xff0c;使用Sqlite Developer…

压缩包格式未知或损坏怎么办?四个简单修复步骤

压缩文件是我们日常工作中常用的工具&#xff0c;但有时在解压时会遇到提示“格式未知”或“压缩包已损坏”的情况。 这可能是由于下载不完整、文件传输错误、存储介质损坏等原因导致的。这种情况会影响到我们正常获取文件&#xff0c;尤其是当压缩包内含重要数据时更让人头疼…

跨境电商独立站||代码建站和SaaS建站的区别

代码建站和SaaS建站是两种不同的网站搭建方式&#xff0c;它们各有特点和适用场景&#xff1a; 1. 代码建站&#xff1a; 定义&#xff1a;指的是从零开始&#xff0c;使用HTML、CSS、JavaScript等编程语言编写代码来构建网站的过程。 技术要求&#xff1a;需要具备一定的编程知…

vue-自定义加载界面v-loading

在网络请求中&#xff0c;页面会出现空白&#xff0c;要使页面好看点&#xff0c;通常页面会出现一些加载页面 1.准备一个伪类元素需要&#xff0c;用伪类元素给加载界面装上蒙层 .loading:before{content:;position: absolute;left:0;top:0;width: 100%;height: 100%;backgrou…

360度评估与绩效考核的深度融合,助力员工提升自我

客户背景 该零售业企业是一家集水果采购、种植支持、采后保鲜、物流仓储、标准分级、营销拓展、品牌运营、门店零售、信息科技、金融资本、科研教育于一体的大型连锁企业。 在当今快速变化的商业环境中&#xff0c;企业对于人才管理的要求日益提高&#xff0c;传统的绩效考核方…

Windows环境下CTRL+C信号处理函数的执行线程

1. 捕获CTRLC 有时候我们希望自己的程序被CTRLC以后&#xff0c;可以先执行一些收尾的工作才结束&#xff0c;比如释放动态内存&#xff0c;关闭网络端口、保存一些状态日志等等&#xff0c;可以用到C的signal的机制。 例程如下&#xff1a; #include <iostream> #inc…

前端视角下的状态机范式

状态机范式及状态管理 前言 随着产品迭代、业务量和需求量的增加&#xff0c;通常系统平台会出现代码逻辑复杂、状态混乱、维护成本增加等现象。 做同一个需求&#xff0c;最开始可能只需一天完成&#xff0c;经过长期迭代后&#xff0c;修改一个小点就可能花上两三天甚至更…

‌美国加勒比烈火波本混合威士忌新品发布会盛大举行‌

近日&#xff0c;美国知名威士忌品牌GARBBEN BLAZE加勒比烈火在北京举行了一场别开生面的新品发布会&#xff0c;正式推出了其最新力作——加勒比烈火波本混合威士忌。此次发布会吸引了众多威士忌爱好者、行业专家代表前来见证这一重要时刻。 加勒比烈火波本威士忌作为GARBBEN …

SSH 公钥认证:从gitlab clone项目repo到本地

这篇文章的分割线以下文字内容由 ChatGPT 生成&#xff08;我稍微做了一些文字上的调整和截图的补充&#xff09;&#xff0c;我review并实践后觉得内容没有什么问题&#xff0c;由此和大家分享。 假如你想通过 git clone git10.12.5.19:your_project.git 命令将 git 服务器上…