模板(c++)part2

news2025/3/9 9:22:46

目录

1.非类型模板参数

2.特化 

2.1函数模板特化

2.2类模板特化

 2.2.1全特化

2.2.2偏特化

3.模板分离编译


1.非类型模板参数

注意,假如

#define N 10
template<class T>
class A
{
private:
	T a[N];
};
这样的一个类模板,a数组的大小是定死的
而为了解决这个问题,引入了一个新的定义
template<class T,size_t N>
class A
{
private:
	T a[N];
};
这样在类模板构造类的时候,可以通过自己传的常量来控制一些数据大小等

注意,只能是整型常量(看编译器,越新,可能支持的常量类型越多)

2.特化 

针对一些特殊情况我们需要对类模板或函数模板进行特化,比如我们构造了一个用来比较大小的函数模板,但是如果传参数的时候,传了指针,这样就不会出现正确的结果了(跟自定义和内置无关,自定义类型比较的时候是依靠的相应类里面的运算符重载),因为这时候比较的是地址大小了

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

2.1函数模板特化

特化步骤:

1.有基础的模板

2.特化的模板,关键字template后面<>里面是空的

3.函数名后面跟一个<特化需要的类型名>

4.注意,基础模板和特化模板,函数的形参要保持一致,类型可以不一样,其他方面尽量一致,否则编译器会给我们很多惊喜

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

template<>
bool Less<Date*>(Date* left, Date* right)
{
     return *left < *right;
}
这就是对上面函数模板的一个特化,就是一种特殊处理
针对特殊情况特殊处理
但是这里其实意义不大,因为这种特化是针对已知自定义类型或内置类型
对未知类型,还是要自己手动写一个函数
平时更多的还不如直接写一个
bool Less<Date*>(Date* left, Date* right)
{
     return *left < *right;
}
直接走函数匹配,不用走模板

但是有一种场景我们需要注意,比如优先队列中,我们是priority_queue<Date*>,这样的话,我们就需要考虑自己来写了(按理来说是两种自己写的方式,但是我们平时都是用库里的容器,这样less也都是库里的,我们不能特化库里的,如果是自己写的比较模板,可以自己特化,具体方式参考下面的类模板特化。排除这个只剩下一种了)自己写一个新的比较模板,再传入优先队列的模板里

template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

struct kp {
	bool operator()(const mm<int, int>* a, const mm<int, int>* b)
	{
		return *a < *b;
	}
};
注意,我这里没写解引用的重载,直接放编译器会报错,我这里只是举例

int main()
{
	priority_queue<mm<int, int>*, vector<mm<int, int>*>, kp>a;
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
}

2.2类模板特化

 2.2.1全特化

即,将类模板的参数全部确定的特化方式

特化方式很简单,我们知道类模板是根据模板参数来生成类的,全特化就是针对其中的某种情况进行特化,比如上面当我们传入long long和double类型的,会走下面的特化,而不是依靠基础模板生成类,特化的类里面的内容也可以依据我们需要进行修改

2.2.2偏特化

第一种部分偏特化

 

#include<iostream>
#include<queue>
using namespace std;


template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

//全特化
template<>
class mm<long long, double>
{
public:
	mm() {
		cout << 222 << endl;
	}
};
//偏特化
template<class t1>
class mm<t1,double>
{
public:
	mm() {
		cout << 333;
	}
};


int main()
{
	mm<long long, double>a;
	mm<int, double>b;
偏特化就是限定了一部分类模板的一部分参数,比如这里,一般情况下遇到第二个参数是double的
都会走偏特化的部分,生成类的时候只有一部分是要生成的,剩下都是现成的

根据结果,我们可以发现,全特化是优先于偏特化的,毕竟有现成的成品,何必自己点份原料外卖再自己做呢

}

第二种,是对参数类型做了进一步的限制,比如之前我们一直讲的传指针的情况

#include<iostream>
#include<queue>
using namespace std;


template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

//全特化
template<>
class mm<long long, double>
{
public:
	mm() {
		cout << 222 << endl;
	}
};
//偏特化
template<class t1>
class mm<t1,double>
{
public:
	mm() {
		cout << 333 << endl;
	}
};
//进一步的偏特化
template<class t1,class t2>
class mm<t1, t2*>
{
public:
	mm() { cout << 444 << endl; }
};
template<class t1, class t2>
class mm<t1*, t2*>
{
public:
	mm() { cout << 555 << endl; }
};

int main()
{
	mm<long long, double>a;
	mm<int, double>b;
	mm<int*, double*>c;
	mm<int, double*>d;
显而易见,我们可以针对指针,引用,const等进行特化

}

这个时候,

​
template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

struct kp {
	bool operator()(const mm<int, int>* a, const mm<int, int>* b)
	{
		return *a < *b;
	}
};
注意,我这里没写解引用的重载,直接放编译器会报错,我这里只是举例

int main()
{
	priority_queue<mm<int, int>*, vector<mm<int, int>*>, kp>a;
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
}


​有新的解决方法
​
template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};
​template<class t>
class kp {
	bool operator()(const t& a, const t &b)
	{
		return a < b;
	}
};
template<class t>
class kp<t*> {
	bool operator()(const t*const& a, const t*const& b)
	{
		return *a < *b;
	}
注意,这里如果为了也想用引用,比如在引用前加入const修饰
因为我们如果是将一个非const类型传进去,会进行类型转换,权限缩小
类型转换之后会产生一个新的临时变量,再将临时变量传进去,而形参接受的时候,如果
加了引用,这时候引用的是临时变量,而临时变量是常量,如果要引用必须加const修饰
但这里其实意义不大,因为指针就算直接传值,消耗也不大的
};
这样针对原比较函数进行特化就方便许多(本来我们要指定类型,像上面写函数模板特化的时候,
我们还要指定哪种指针类型,而现在我们只需要记得把自己新写的类加上相应的解引用大于小于
等运算符重载即可),
虽然还是有问题,因为我们平时习惯用库里的了,不过问题不大,库里也是类似的设计。

3.模板分离编译

 在这之前,先复习下关于编译器的问题

我们知道,编译器在预处理阶段,.cpp文件是执行展开头文件操作的,.h文件只是用来展开,其他没什么用。然后.cpp->.i,然后再对.i文件进行语法检查等操作,.i->.s,再然后对.s文件进行汇编(变成机器能识别的语言)在这过程中,对调用函数会汇编成相应的语句,.s->.o,最后将整个工程里的所有.o文件链接起来。

我们可以注意到,在过程中,除了同流程的文件外,整个工程的文件只有在最后链接的时候才会有所关联。

这个时候,函数分离编译和模板分离编译就出现了差异,分离编译就是定义和声明分离在.cpp和.h文件中,而函数分离编译之所以可以成功,是因为函数定义出现之后,会产生相应的地址放在函数表里,这样另外一个文件如果调用了该函数,就可以依靠地址直接在内存中找到相应的函数,但是模板不同。

模板是只有在生成具体的函数或类的时候,才会在内存中留下地址,因此,关联上面的流程,当链接的时候调用的.o文件只有调用模板生成的函数或类(头文件展开的),这样就完全不知道模板在哪,更不知道怎么让模板生成相应的类或函数,更何况是实例化类、调用里面的函数了。而另一个只有定义的.cpp文件生成的.o文件,没有收到类型参数,就完全不知道要生成什么样的类或函数了,因此调用的时候,更不会在内存中留下地址供其他文件调用。

因此,为了解决这个问题,有2个方法

一个是将声明和定义放在同一个.h文件或.hpp文件里。

一个就是显示实例化(既然是在定义类或函数的.cpp里找不到类或函数,那干脆,就手动写一个)

.h文件

template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}
显示实例化
template
int Add<int>(const int&, const int&);
因为模板生成类或函数是在链接前的,所以我们只能手动在源文件里,
增加一个显示实例化,在链接前就让模板生成一个指定的类或函数
这样当另一个文件链接起来的时候,调用相应函数或实例化类,就不会
出现找不到地址的问题了

.cpp文件

#include<iostream>
using namespace std;
#include"s.h"
int main(){
    Add(1,2);
}

4. 总结模板

优:复用代码,节省资源(时间),加快迭代开发,增强代码灵活性

缺:容易代码膨胀,增加编译时间,模板编译错误时信息混乱,不易定位错误

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

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

相关文章

canvas绘制表格

canvas绘制表格 最近在为公司产品做技术预研&#xff0c;经理让用canvas做一个表格&#xff0c;于是就有了这篇博客。 我们的数据是后端通过MQTT推送过来的 我在代码中也直接使用了 具体MQTT的实现代码&#xff0c;可见博客 在vue使用MQTT 在这里为了方便实用我直接封装成组件…

【中项第三版】系统集成项目管理工程师 | 第 11 章 规划过程组⑥ | 11.15 - 11.17

前言 第11章对应的内容选择题和案例分析都会进行考查&#xff0c;这一章节属于10大管理的内容&#xff0c;学习要以教材为准。本章上午题分值预计在15分。 目录 11.15 规划资源管理 11.15.1 主要输入 11.15.2 主要工具与技术 11.15.3 主要输出 11.16 估算活动资源 11.1…

安装jdk和tomcat

安装nodejs 1.安装nodejs&#xff0c;这是一个jdk一样的软件运行环境 yum -y list installed|grep epel yum -y install nodejs node -v 2.下载对应的nodejs软件npm yum -y install npm npm -v npm set config .....淘宝镜像 3.安装vue/cli command line interface 命令行接…

轻松搞定 Nginx 在 CentOS 和 Ubuntu 上的安装与配置

注&#xff1a;这是对我以前博客进行优化后再次发布的&#xff0c;博客中的截图为以前的。原博客已删除。 如何安装nginx nginx是一款开源、高性能的Web和反向代理服务器&#xff0c;支持HTTP、HTTPS、SMTP、POP3和IMAP协议。由于其轻量级、资源占用少和强大的并发能力&#…

时空预测又爆火了!新SOTA实现零样本精准预测

时空预测又有新突破啦&#xff01;港大、华南理工等提出了时空大模型UrbanGPT&#xff0c;在性能上猛超现有SOTA&#xff0c;实现零样本即可时空预测&#xff01; 另外还有清华的首个通用城市时空预测模型UniST、能即插即用快速适配的时空提示调整机制FlashST...这些效果非常ni…

探索计算器存储器的奥秘:数字记忆的科学

在日常生活中&#xff0c;我们经常使用计算器来执行各种数学运算。但你是否曾想过&#xff0c;当按下每个按键时&#xff0c;计算器是如何记住数字和运算符的&#xff1f;本文将深入探讨计算器存储器的工作原理&#xff0c;揭示其背后的科学原理。 引言&#xff1a;数字世界的…

家庭出游新风尚!格瑞维亚改装大赛创意实用并存

在创新浪潮翻涌的当下&#xff0c;汽车已蜕变为个性化生活的璀璨舞台&#xff0c;格瑞维亚改装共创大赛便是这一变革的推动者。这场大赛&#xff0c;不仅汇聚了400余支创意团队的心血结晶&#xff0c;更将汽车改装的魅力推向了新的高度。它不仅仅是对机械与美学的重塑&#xff…

STM32——EXIT外部中断

一、中断系统 以上就是中断的概念&#xff0c;简单理解就是&#xff1a; 当程序运行过程中&#xff0c;如果有中断源向CPU打报告&#xff0c;CPU就会暂停手下的事情去处理中断源提交的事情&#xff0c;然后处理完了在返回到CPU原来的位置继续处理手上的事情。如果同时有多个中…

浏览器指纹技术:如何更改浏览器指纹?

“指纹信息”是一个人独有的身份象征&#xff0c;而“浏览器指纹”&#xff0c;就是网站和在线平台使用浏览器指纹来收集有关您的浏览器、设备和网络的详细信息&#xff0c;它可以说是你上网的身份象征&#xff0c;可让网站跟踪您的在线行为。 下面我们简单科普浏览器指纹的工…

tomato 靶场

1.主机发现 扫描ip及端口 2.端口扫描 nmap192.168.233.131 有三个开放的端口nmap -sC -sV -O 192.168.233.131 -sC常见漏洞脚本扫描 -sV开放端口服务/版本号 -O操作系统探测 3.目录扫描 DIRECTORY: http://192.168.233.131/antibot_image/ http://192.168.233.131/index.h…

单调队列与单调栈<1>——单调队列

单调队列&#xff0c;即队列里的元素单调递增或递减。一般用于求区间内的最值问题。 模板题:P1886 暴力的话很简单&#xff0c;搞定。但是对于来说肯定TLE。所以我们要用单调队列来解决这道题。因为单调队列中元素大小单调递增或递减&#xff0c;所以&#xff0c;队首必定是最…

TwinCAT3 新建项目教程

文章目录 打开TwinCAT 新建项目&#xff08;通过TcXaeShell&#xff09; 新建项目&#xff08;通过VS 2019&#xff09;

案例精选 | 聚铭网络助力石家庄市栾城区中医院防护体系焕新升级

石家庄市栾城区中医院&#xff0c;坐落于栾城这块历史悠久的热土上&#xff0c;占地广阔&#xff0c;达4200平方米&#xff0c;作为一家享有盛誉的二级甲等综合性中医医疗机构&#xff0c;它不仅是传统医学与现代科技融合的典范&#xff0c;更是区域医疗卫生服务的重要支柱。 …

【JavaScript】详解数组方法 fill()

文章目录 一、fill()方法简介二、fill()方法的基本用法三、fill()方法的进阶用法四、实际应用案例五、注意事项六、总结 在JavaScript中&#xff0c;fill()方法是数组操作中一个非常有用的方法。它可以快速地用特定值填充数组的全部或部分内容。理解fill()方法的工作机制和使用…

深度对话安天王小丰:我们应该从微软“蓝屏”事件学到什么?

ITValue 国内厂商和CrowdStrike的差距&#xff0c;相比于国外厂商和CrowdStrike的差距更大。 作者&#xff5c;张帅 编辑&#xff5c;盖虹达 首发&#xff5c;钛媒体APP ITValue 我们赖以生存的数字世界&#xff0c;可能比想象得还要脆弱。 近日&#xff0c;全球范围内的微软Wi…

基于Gitlab CI+Argo CD的Gitops实践

项目简介 项目说明 本项目构建了一个基于GitOps理念的完整CI/CD管道&#xff0c;旨在实现软件开发与运维的高度自动化和一致性。通过GitLab、GitLab Runner&#xff08;部署于Kubernetes&#xff09;、Maven、Java、SonarQube、Harbor以及Argo CD等工具的紧密协作&#xff0c…

二叉树的存储

二叉树的存储 满二叉树或者完全二叉树可以采用顺序存储&#xff0c;普通二叉树一般采用链式存储 节点的结构体原型 typedef int DataType typedef struct node { DataType data&#xff1b; struct node *L&#xff1b; struct node *R&#xff1b; }twotree&#xff…

【数值计算方法】数值积分微分-python实现-p3

原文链接&#xff1a;https://www.cnblogs.com/aksoam/p/18332123 更多精彩&#xff0c;关注博客园主页&#xff0c;不断学习&#xff01;不断进步&#xff01; 我的主页 csdn很少看私信&#xff0c;有事请b站私信 博客园主页-发文字笔记-常用 有限元鹰的主页 内容&#xf…

【阅读笔记】红外sensor的ITR、IWR读出模式分析

一、ITR、IWR读出模式分析 InGaAs短波红外探测器具有ITR和IWR两种工作模式。两种工作模式都包括三个相同的工作过程&#xff0c;即复位、积分和读出。每个工作过程的开始与结束都由配置指令码控制&#xff0c;配置指令码包括复位指令、开始积分指令、开始读出指令和读出结束指…