模版进阶 非类型模版参数

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

类型形参即:出现在模板参数列表中,跟在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.xdnf.cn/news/1560171.html

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

相关文章

机器学习项目——运用机器学习洞察青年消费趋势

1. 项目背景 在21世纪的第三个十年&#xff0c;全球经济和技术的飞速发展正深刻影响着各个领域&#xff0c;尤其是青年消费市场。随着数字化进程的加速&#xff0c;尤其是移动互联网的广泛普及&#xff0c;青年的消费行为和生活方式发生了前所未有的转变。 然而&#xff0c;面对…

VMware vCenter Server 8.0U3c 发布下载,修复 U3b 更新停止响应的问题

VMware vCenter Server 8.0U3c 发布下载 - 集中式管理 vSphere 环境 Server Management Software | vCenter 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-8-u3/ 查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org VMw…

如何在全平台启用 IPv6 网络?(路由器、Windows、Linux、Docker)

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 如今 IPv6 网络越来越普及&#xff0c;也是时候开启 IPv6 网络了&#xff0c;特别是对于 NAS 玩家&#xff0c;开启 IPv6 后&#xff0c;NAS 可以获取到公网 IPv6 用于外网访问&#xff0c;通过 ZeroTier …

利用Matlab处理回声信号(利用卷积运算产生一个带回声的信号)

一、.首先调用一个声音信号&#xff0c;以下是各种MATLAB自带的声音&#xff1b; 1. 鸟叫声 load chirp sound(y,Fs) 2. 敲锣声 load gong sound(y,Fs) 3. 哈利路亚 load handel sound(y,Fs) 4. 欢笑声 load laughter sound(y,Fs) 5. 啪哒声 load splat sound(y,Fs) 6. 火车声 …

seL4 Faults(八)

Faults 学习什么是线程错误理解线程错误和处理器硬件错误是不同的理解什么是错误处理器理解内核对于一个有错误的线程做了什么了解如何设置内核将在其上传递故障消息的端点&#xff08;master与 MCS&#xff09;。在错误故障后学习如何恢复线程。 Background: What is a faul…

SROP验证

文章目录 SROPsignal机制 SROP的利用原理&#xff1a;获取shellsystem call chains条件&#xff1a;sigreturn 测试 例题&#xff1a; SROP signal机制 signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般&#xff0c;我们也称其为软中断信号&#xff0c;或者软…

【onnx模型转kmodel】记录和踩坑——nncase-v1.9使用

最近几天一直在找相关资料&#xff0c;坑太多&#xff0c;也可能我菜的成分更多一点吧&#xff01;记录下来&#xff0c;以观后用&#xff1b; 背景 我手里有一个正点原子的K210的开发板&#xff1b; 刚刚安装了wsl2下的ubuntu22.04 我手里有正点原子的源码&#xff0c;但是…

项目管理监控难题解析:为何监控至关重要

项目管理中的监控环节常常被视为一项艰巨的任务&#xff0c;但它却是确保项目成功的关键所在。为何监控在项目管理中如此重要呢&#xff1f;让我们一同深入解析这个难题。 一、目前项目管理监控存在的问题 在项目管理的实践中&#xff0c;监控环节常常暴露出一系列问题&#x…

西门子S7-SMART运动控制向导

打开“运动控制”向导&#xff0c;“工具”->“向导”->“运动控制” 图 1.打开“运动控制”向导 选择需要配置的轴 图 2.选择需要配置的轴 为所选择的轴命名 图 3.为所选择的轴命名 输入系统的测量系统&#xff08;“工程量”或者“脉冲数/转”&#xff…

u_boot内核编译-生成uImage

内核编译 顶层目录&#xff0c;都在这个目录下进行操作 这是我们芯片的默认配置文件 第一步 拷贝一个默认的配置 以下两张是def文件内容 第二步 &#xff1a;打开可视化界面&#xff0c;前面的* 需要用空格点击两下&#xff0c;不改变原设置&#xff0c;大那是需要进行编辑操…

PDF全能免费转换 3.15 | 多功能PDF处理工具

主打就是免费好用&#xff01;1. PDF转Word/PPT/Excel/txt、图片等&#xff1b;2. PDF压缩、合并&#xff1b;3. 多图合并成长图、合并成PDF&#xff1b;4. 身份证扫描、文件扫描、证件扫描等&#xff1b;5. 证件照换底色&#xff1b;6. 热门tab页&#xff1b;7. 美化照片。 大…

【LLM论文日更】| BGE-M3E embedding模型

论文&#xff1a;https://arxiv.org/pdf/2402.03216代码&#xff1a;GitHub - FlagOpen/FlagEmbedding: Retrieval and Retrieval-augmented LLMs机构&#xff1a;BAAI领域&#xff1a;embedding model发表&#xff1a; ​ 研究背景 研究问题&#xff1a;这篇文章要解决的问…

(JAVA)熟悉队列的进阶结构 - 优先队列

1. 优先队列 ​ 普通队列是一种先进先出的数据结构&#xff0c;元素在队列尾追加&#xff0c;而从队列头删除。 ​ 在某些情况下&#xff0c;我们可能需要找出队列中的最大值或者最小值&#xff0c;例如使用一个队列保存计算机的任务&#xff0c;一般情况下计算机的任务都是有…

pytest框架之fixture测试夹具详解

前言 大家下午好呀&#xff0c;今天呢来和大家唠唠pytest中的fixtures夹具的详解&#xff0c;废话就不多说了咱们直接进入主题哈。 一、fixture的优势 ​ pytest框架的fixture测试夹具就相当于unittest框架的setup、teardown&#xff0c;但相对之下它的功能更加强大和灵活。 …

谁说电商选品找货源没有捷径,只要你用对工具!

最近跟很多同行聊&#xff0c;都在抱怨选品难的问题&#xff0c;都说7分靠选品&#xff0c;3分靠运营&#xff0c;对于选品来说&#xff0c;并没有捷径可走&#xff0c;但其实是有很多不同的角度的。 现在市面上大部分开发做的选品&#xff0c;“选品方法”或“产品分析方法”…

【含文档】基于Springboot+Android的校园论坛系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

前端省市区组件

官网 Element UI 级联 中国省市区数据. Latest version: 5.0.2, last published: 2 years ago. Start using element-china-area-data in your project by running npm i element-china-area-data. There are 114 other projects in the npm registry using element-china-are…

SpringBoot开发——SpringSecurity安全框架17个业务场景案例(三)

文章目录 一、Spring Security 常用应用场景介绍二、Spring Security场景案例12 表达式支持(Expression-Based)12.1 Spring Security 配置12.2 业务逻辑代码12.3 控制器13、安全上下文(Security Context)13.1 Spring Security 配置13.2 业务逻辑代码13.3 控制器14、安全过滤…

Python入门笔记(四)

文章目录 第九章 集合set9.1 创建集合&#xff1a;set()、集合生成式9.2 集合性质9.3 一些函数&#xff1a;issubset()、issuperset()、isdisjoint()9.4 集合增加元素&#xff1a;add()、update()9.5 集合删除元素&#xff1a;remove()、discard()、pop()、clear()9.6 创建不能…

用IntStream生成0到n的流,并找出不在numSet中的数字列表

这是用IntStream生成0到n的流&#xff0c;并找出不在numSet中的数字-CSDN博客的升级版 给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 先看这题低…