模版进阶 非类型模版参数

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

类型形参即:出现在模板参数列表中,跟在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;//10stack<int,100>s2;//100return 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;//10stack<int,100>s2;//100//s1.func();return 0;
}

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

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

int main()
{stack<int,10> s1;//10stack<int,100>s2;//100s1.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.mzph.cn/web/54834.shtml

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

相关文章

乌班图基础设施安装之Mysql8.0+Redis6.X安装

简介&#xff1a;云服务器基础设施安装之 Mysql8.0Redis6.X 安装 Docker安装 # 按照依赖 yum install -y yum-utils device-mapper-persistent data lvm2 Docker Mirror 从去年开始. hub.docker.com[1] 在国内的访问速度极慢. 当时大家主要还是依赖国内的一些镜像源: 如中科…

操作系统 | 学习笔记 | 王道 | 4.3 文件系统

4.3 文件系统 4.3.1 文件系统结构 文件系统(File system)提供高效和便捷的磁盘访问&#xff0c;以便允许存储、定位、提取数据。 用一个例子来辅助记忆文件系统的层次结构&#xff1a; 假设某用户请求删除文件"D:/工作目录/学生信息.xIsx"的最后100条记录。 用户需…

在 Windows 11 安卓子系统中安装 APK 的操作指南

这个软件好像不可以在纯android系统中使用&#xff08;不知道是缺了什么&#xff09;&#xff0c;其他对于android的虚拟机要不缺少必要功能组件&#xff0c;要不性能过于低下。本方法致力于在带有谷歌框架WSA中运行该APK 在 Windows 11 安卓子系统中安装 APK 的操作指南 本指…

消息摘要算法

算法特点 a) 消息摘要算法/单向散列函数/哈希函数 b) 不同长度的输入&#xff0c;产生固定长度的输出 c) 散列后的密文不可逆 d) 散列后的结果唯一 e) 哈希碰撞 f) 一般用于校验数据完整性、签名sign 由于密文不可逆&#xff0c;所以服务端也无法解密 想要验证&#xf…

前端 | Uncaught (in promise) undefined

前端 | Uncaught (in promise) undefined 最近开发运行前端项目时&#xff0c;经常预计控制台报错 &#xff0c;如下图&#xff1a; 这里我总结下&#xff0c;这种报错的场景和原因&#xff0c;并通过实际代码案例帮助小伙伴更好理解下 。 文章目录 前端 | Uncaught (in promi…

若依前端后打成一个JAR包部署

客户需要将项目前后端作为一个整体打包成jar&#xff0c;不使用nginx方式转发。使用框架是若依前后端分离&#xff0c;后端springboot&#xff0c;前端vue&#xff0c;目的就是把vue打入jar。 一、前端修改 ruoyi-ui/src/router/index.js文件 &#xff0c;将 mode: ‘history’…

vue-jsonp的使用和腾讯地图当前经纬度和位置详情的获取

1.下载&#xff1a; npm install –save vue-jsonp2.main.js中引入&#xff1a; //腾讯逆地址解析会用到jsonp import {VueJsonp} from vue-jsonp; Vue.use(VueJsonp);3.腾讯地图中使用 uniapp中获取*经纬度*和通过经纬度获取当前**位置详情** //获取当前经纬度 getLocation…

职场上的人情世故你知多少

1.发微信找人帮忙&#xff0c;半天不回&#xff0c;那基本没戏了&#xff0c;不要再打扰了&#xff0c;懂得都懂。 2.能力越大&#xff0c;事情越多&#xff0c;要懂得张弛有度&#xff0c;不要把自己全抛出去&#xff0c;给自己留点余地&#xff0c;毕竟你不知道别人如何暗地…

Windows电脑本地安装AI文生音乐软件结合内网穿透远程访问制作

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 今天和大家分享一下在Windows系统电脑上本地快速部署一个文字生成音乐的AI创作服务MusicGPT&#xff0c;并结合cpolar内网穿透工具实现随时随地远程访问使用进行AI音…

光伏“地图导航”:光照、政策、电价一目了然

当代的快节奏生活中&#xff0c;地图导航的精准定位技术如同一盏照明灯&#xff0c;为我们照亮了前方的道路。许多光伏人纷纷反映&#xff0c;如果光伏也有这样的地图就好了&#xff0c;能够结合各种建设光伏的因素快速完成选址&#xff0c;能够极大地提高效率。今天小编就来分…

PAT甲级-1013 Battle Over Cities

题目 题目大意 给定一个城市图&#xff0c;如果攻陷一个城市&#xff0c;该城市连接的所有路都要被销毁。要求计算出连通剩余的城市最少需要修建几条路。该图有n个顶点&#xff0c;m条边&#xff0c;k个重点城市。分别求出每个重点城市被攻陷&#xff0c;连通剩余城市需要修建…

[面试] java开发面经-1

前言 目录 1.看到你的简历里说使用Redis缓存高频数据&#xff0c;说一下Redis的操作 2.说一下Redis的缓存击穿、缓存穿透、缓存雪崩 3.你的项目中使用了ThreadLocal&#xff0c;那么当有两个请求同时发出时&#xff0c;会怎么处理&#xff0c;可以同时处理两个请求吗 4.使用…

【GESP】C++一级练习BCQM3037,简单计算,国庆七天乐收官

又回到了简单计算的题目&#xff0c;继续巩固练习。 题解详见&#xff1a;https://www.coderli.com/gesp-1-bcqm3037/ 【GESP】C一级练习BCQM3037&#xff0c;简单计算&#xff0c;国庆七天乐收官 | OneCoder又回到了简单计算的题目&#xff0c;继续巩固练习。https://www.cod…

内网渗透-隧道代理转发

文章目录 前言环境搭建工具清单工具使用Frp命令执行实验 Lcx命令执行实验 reGeorg命令执行实验Proxifier ew(EarthWorm)正向代理命令执行实验 反向代理命令执行实验SocksCap netsh命令执行 pingtunnel命令执行实验 ngrok命令执行&&实验 cs命令执行实验 前言 本文章介绍…

10/11

一、ARM课程大纲 二、ARM课程学习的目的 2.1 为了找到一个薪资水平达标的工作&#xff08;单片机岗位、驱动开发岗位&#xff09; 应用层(APP) 在用户层调用驱动层封装好的API接口&#xff0c;编写对应的API接口 ----------------------------------------------------…

Redis:通用命令 数据类型

Redis&#xff1a;通用命令 & 数据类型 通用命令SETGETKEYSEXISTSDELEXPIRETTLTYPEFLUSHALL 数据类型 Redis的客户端提供了很多命令用于操控Redis&#xff0c;在Redis中&#xff0c;key的类型都是字符串&#xff0c;而value有多种类型&#xff0c;每种类型都有自己的操作命…

pytorch 与 pytorch lightning, pytorch geometric 各个版本之间的关系

主要参考 官方的给出的意见&#xff1b; 1. pytorch 与 pytorch lightning 各个版本之间的关系 lightning 主要可以 适配多个版本的 torch; https://lightning.ai/docs/pytorch/latest/versioning.html#compatibility-matrix&#xff1b; 2. pytorch 与 pytorch geometric 各…

ComfyUI | 5分钟部署最新Flux大模型

Midjourney 和 Stable Diffusion 都是目前流行的 AI 图像生成工具&#xff0c;它们能够根据文本描述生成高质量的图像。都是基于深度学习技术的文本到图像生成模型&#xff0c;但它们各自基于不同的大模型。 但最近推出了一款比前两者更强大&#xff0c;生成图像更加逼真&…

windows端口被占用但是查不到进程的问题排查

在开发环境上经常遇到端口被占用&#xff0c;但是 netstat -ano|findstr 3306 查不到进程号&#xff0c;没法强杀解决。 这种情况&#xff0c;很有可能端口被排除了&#xff0c;可用命令&#xff1a; netsh interface ipv4 show excludedportrange protocoltcp 可以看到mysql的…

一、制作UI自适应

当前分辨率 更改分辨率 一、原因 一款游戏的UI&#xff0c;可能会根据玩家的分辨率和屏幕尺寸&#xff0c;产生不同的变化 例如&#xff1a;某一个Image位移到了摄像机外面 因此需要通过锚点和屏幕自适应来制作完美的效果 二、解决方法 1、锚点 作用是&#xff1a;根据当…