【C++】-模板进阶(让你更好的使用模板创建无限可能)

news2024/12/21 19:18:30

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、非类型模板参数
  • 二、类模板的特化
    • 2.1 函数模板特化
    • 2.2类模板特化
      • 2.2.1 全特化
      • 2.2.2 偏特化
  • 三、模板的分离编译
  • 四、模板总结
  • 五、总结


前言

今天我们来学习模板的进阶知识,在初阶的时候我们讲解到的就是定义模板参数,使用的就是class,那时候学习这些就够学习STL了,但是通过仿函数我们知道了,就之前的模板知识显然是不够的,所以我们今天学习的模板进阶就是为了补充一些其他场景下该如何定义模板,每个知识点都是一个语法,所以这篇信息量还是特别大的,话不多说,我们开始进入正文


本章重点:

  1. 非类型模板参数
  2. 类模板的特化
  3. 模板的分离编译

一、非类型模板参数

我们来看一个案例:

//静态的栈
template<class T>
class stack
{
private:
	int _a[10];
};

stack<int> s1;//向存储10个元素
stack<int> s2;//向存储100个元素怎么办??

如果我们想要两个不同大小的栈,应该怎么办??我们不可能会再定义一个类,这样太冗余了,所以这个时候我们的非类型参数模板就出来了

template<class T,size_t N>//使用非模板类型形参
class Stack
{
private:
	int _a[N];
};

Stack<int,10> s3;
Stack<int,100> s4;

这样就可以了,我们再来准确的说明一下什么是非类型模板参数,什么是类型模板参数

类型形参即: 出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参: 就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

注意:

  1. 再上面的概念中说到非类型形参是作为类的模板常量进行使用的,所以不可以修改他的值
  2. 必须是整型家族的(char,int ,size_t),其余的像double,string都不行,这个再C++20行,目前我们学的是C++98

库里面使用的场景:
我们再库里面有一个容器叫array
在这里插入图片描述
我们在之前,要是定义一个数组int a[10];
在这里插入图片描述
有了这个array我们可以这么定义array<int,10> a
在这里插入图片描述

我们发现非类型模板在这个场景进行了应用,但是就这个设计就有点鸡肋,和普通的数组效果是一样的,有一点不一样,普通数组在读数据的时候越界不能检查出来,而array可以,相信大家学了STL的一部分模拟实现应该都知道,在实现[]的时候进行了下标的断言检查

二、类模板的特化

类模板的特化,其实就是对一些特殊情况单独处理

2.1 函数模板特化

我们来看一个例子:

template<class T>
bool Less(T left, T right)
{
	return left < right;
}
	
int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	return 0;
}
     我们传任意两个相同的类型都可以进行比较,
     但是如果我传指针进去,会出现什么情况:

在这里插入图片描述


我们发现比较结果不正确,因为比较的是指针,它没有按照指向的内容进行比较,我们按照指针的比较毕竟是少数,这就是一种特殊情况,我们就要特殊处理。

我们要写一个函数模板的特化:(前面哪个函数模板不能删除,知识有特殊情况我们才走这个特化)

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
template<>
bool Less<int *>(int* left, int* right)
{
	return *left < *right;
}

//这种是不对的,这就不符合特殊化了,就是模板化了,所以不行
template<class T>
bool Less<T*>(T* left, T* right)
{
	return *left < *right;
}

在这里插入图片描述


其实我们完全可以使用函数重载来写:

//函数重载
bool Less(int* left, int* right)
{
	return *left < *right;
}

//函数重载,适用于任何指针类型
template<class T>
bool Less(T* left, T* right)
{
	return *left < *right;
}

在这里插入图片描述
第一个肯定匹配第一个函数模板,第二个有现成的int*,第三个只能走函数重载的那个

总结:
我们的函数模板特化比较局限,而且看起来比较复杂,所以我们这时候使用函数重载实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

2.2类模板特化

2.2.1 全特化

全特化即是将模板参数列表中所有的参数都确定化。
我们来看一个例子:

template<class T1,class T2>
class Date
{
public:
	Date() { cout << "Date<T1, T2> " << endl; }
private:
	T1 a;
	T2 b;
};

Date<int, int> d;

在这里插入图片描述


我们来假设一下传int,char或者int* ,double*这是两个特殊情况,对类进行一个特化

template<>
class Date<int,char>
{
public:
	Date() { cout << "Date<int, char> " << endl; }
private:
	int a;
	char b;
};

template<>
class Date<int*, double*>
{
public:
	Date() { cout << "Date<int*, double*> " << endl; }
private:
	int* a;
	double* b;
};

在这里插入图片描述

使用方法和函数特化一样,而且找现成的匹配,第二个或者第三个不会去找第一个去匹配的,有现成的特化

2.2.2 偏特化

偏特化也分两种:部分偏特化,限制偏特化

  1. 部分偏特化,对其中一部分类型进行特化
template<class T>
class Date<T, double>//只有第二个参数是double的才能匹配
{
public:
	Date() { cout << "Date<T, double> " << endl; }
private:
	int* a;
	double* b;
};

在这里插入图片描述

  1. 限制偏特化:对参数进行限制,其他限制也是可以的
template<class T>
class Date<T*, T*>//限制只能传相同的指针类型
{
public:
	Date() { cout << "Date<T*, T*> " << endl; }
private:
	int* a;
	double* b;
};

在这里插入图片描述

总结:

  1. 我们的类模板特化就是在写一个类,这个类指定特殊的类型,那么我们传的特殊类型的参数,就会匹配现成的,而且新写的类里面不需要按照原类模板一样,把所有的函数和变量都写进去,根据功能需求写进去就行
  2. 我们新写的特化类,其实也不叫一个新的类,因为它不能独立存在,还需要依赖原始模板类,但是可以理解为新类。

类模板特化的应用:仿函数的简单介绍
看一下这篇博客最后说的仿函数,我们大部分都是日期对象本身传进去进行比较,但是万一要是传引用进去比较呢??
在这里插入图片描述

在那一篇我们提到,我们可以重新写两个类,来达到传引用也可以进行比较的目的,但是这个时候不管是排升序还是降序的指针,都需要传下面两个类进去.

例如:

Priority_queue<Date*,vector<Date*>, LessPNode<Date*>> pq;
Priority_queue<Date*, vector<Date*>, GreaterPNode<Date*>> pq;

我们不想要这么多名字的类模板,我们就像使用Less和Greater来实现,这时候就必须使用类模板,我们可以这样去实现:
在这里插入图片描述
这样我们就可以像普通的比较大小一样去传参比较了:

Priority_queue<int> pq;
		Priority_queue<int, vector<int>, Greater<int>> pq1;
		Priority_queue<Date*> pq2;
		Priority_queue<Date*, vector<Date*>, Greater<Date*>> pq3;
		
		//就不需要像这样写的麻烦,大部分用的都是less,所以在写的时候也希望简单一些,类模板特化就可以很好的解决这个问题
		Priority_queue<Date*,vector<Date*>, LessPNode<Date*>> pq2;
		Priority_queue<Date*, vector<Date*>,GreaterPNode<Date*>> pq3;

那篇没有介绍到类模板的特化,所以还不能使用最好的办法解决我们那个时候遇到的问题,今天为大家解决了问题,希望大家可以很好的理解

三、模板的分离编译

细心的同学应该发现博主在之前的模拟实现的时候只写了一个.h文件,没有把类中函数的定义和声明分离,但是说到是模板定义的函数,定义和声明不能分离,这小节带大家分析为什么不能分离,有没有分离的办法,我们一起来看

看一个例子:
在这里插入图片描述


我们在来回顾一下什么情况下会发生链接性错误:
在程序预处理阶段说过,我们进行汇编之后就会进行链接,链接的步骤是,在编译的时候就形成符号表,是通过声明来获得定义地址的符号表,在链接的时候通过符号表上的地址去找定义的地方,没有找到就会报错,而最重要的一点是定义位置的地址什么时候有点,答案是预处理的时候就已经存在,如果没有定义就没有地址。

讲解完成这个我们再来看看类模板参数如果定义和声明分离会发生什么:
在这里插入图片描述
通过此案例来看,我们类模板如果讲定义和声明分离就会出现链接性错误,就是找不到定义的地方,而我们看到的是确实定义了啊,为什么会出现这种情况??

答案:我们在启动一个程序的时候,是对每个先将头文件进行展开到源文件当中,然后头文件不参与编译,对单独的每个源文件进行编译,此时我们单独对test.cpp和stack.cpp进行单独编译,此时的stack.cpp里面的定义是模板,不知道具体是什么类型,所以就没有具体的地址,只有是确定的类型才可以进行编译形成地址,因为是单独,也检查不到Test.cpp里面创建的类型,此时形成符号表,就没有刚才定义的地址,所以就会发生链接性错误,而把定义和分离都放在头文件里面,在预处理的是,第一和声明一起展开,把类型替换成创建对象时候的类型,这时候类型就确定,这样就不会报错。

此处的定义和分离是在不同的文件当中,有两种解决办法

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。
    在这里插入图片描述

  2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
    在这里插入图片描述

进行显式实例化之后,就明确类型了,就要地址,这样就不会报错

注意:
我们在进行分离和定于的时候,不能指定模板参数的内容
在这里插入图片描述

至此我们模板的分离编译就讲解完毕了。

四、模板总结

【优点】

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

【缺陷】
4. 模板会导致代码膨胀问题,也会导致编译时间变长
5. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

五、总结

我们关于模板的进阶也讲解完毕了,至此我们的C++初阶知识也就讲解完毕了,不知道同学们是否有所收获呢??接下来博主将讲解C++进阶相关的知识,比如继承多态,map和set等有难度的知识,也是提升大家水平的东西,希望大家能来支持博主,我们下一个专栏再见
请添加图片描述

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

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

相关文章

删除主表 子表外键没有索引的性能优化

整个表147M&#xff0c;执行时一个CPU耗尽&#xff0c; buffer gets 超过1个G&#xff0c; 启用并行也没有用 今天开发的同事问有个表上的数据为什么删不掉&#xff1f;我看了一下&#xff0c;也就不到100000条数据&#xff0c;表上有外键&#xff0c;等了5分钟hang在那里&…

固态硬盘种类

有三个层次&#xff0c;同一个词可能有多层意思&#xff0c;要注意区分。 一、接口 也叫插槽&#xff0c;插口。就是连接设备的地方。 能够插固态硬盘的插槽有&#xff1a;sata插槽&#xff0c;pcie插槽&#xff0c;m.2插槽&#xff0c;u.2插槽 &#xff08;一&#xff09;sat…

模拟量输出FC S_RTI(信捷C语言源代码)

模拟量输出FC SCL源代码请查看下面博客: PLC模拟量输出 模拟量转换FC S_RTI_博途模拟量转换指令_RXXW_Dor的博客-CSDN博客1、本文主要展示西门子博途模拟量输出转换的几种方法, 方法1:先展示下自编FC:计算公式如下:intput intput Real ISH Real //工程量上限 ISL Real //工…

Java中的equals方法详解:比较方法

1、equals方法的背景 在Java中&#xff0c;equals方法是Object类的一个方法&#xff0c;用于比较两个对象是否相等。 Java中有两种比较对象的方法&#xff1a;运算符和equals方法。 运算符用于比较两个对象的引用&#xff0c;如果它们指向的是同一个对象&#xff0c;则返回t…

继承-菱形继承

继承 继承是类设计层次的复用 继承方式与访问限定符 限定了啥&#xff1f; 1.根据表中我们可以看到 基类的私有成员在子类不可见&#xff0c;但还是被继承了下来 2.根据继承方式和成员在基类的访问限定符小的那个来决定了子类访问基类成员的访问方式 例如如果是public继承&a…

甄云库存管理解决方案 ,助力企业库存高效运转起来

导语 近年来&#xff0c;在降低成本、提高工作效率和满足用户需求等多重压力下&#xff0c;许多企业也开始重视非生产物资的库存管理&#xff0c;如办公设备、劳保用品、电子设备、维修工具、实验耗材。这些物资往往品类繁多、采购频率较高&#xff0c;占用了企业大量的管理时…

quartus工具篇——PLL IP核的使用

quartus工具篇——PLL IP核的使用 1、PLL简介 PLL(Phase-Locked Loop,相位锁环)是FPGA中非常重要的时钟管理单元,其主要功能包括: 频率合成 - PLL可以生成比输入时钟频率高的时钟信号。频率分频 - PLL也可以输出分频后的较低频率时钟。减小时钟抖动 - PLL可以过滤输入时钟中…

内存映射学习笔记

文章目录 内存映射原理函数定义mmap函数munmap函数 注意事项应用进程间通信文件复制 匿名映射 内存映射原理 将磁盘中的文件&#xff0c;映射到内存&#xff0c;通过内存修改文件。 函数定义 mmap函数 操作映射区必须要有 读权限 munmap函数 首地址 长度一致。 注意事项 可…

第一章:STC:一种用于弱监督语义分割的简单到复杂框架

0.摘要 近年来&#xff0c;由于深度卷积神经网络&#xff08;DCNNs&#xff09;的发展&#xff0c;语义目标分割取得了显著的改进。训练这样一个DCNN通常依赖于大量具有像素级分割掩码的图像&#xff0c;并且在财务和人力方面标记这些图像非常昂贵。在本文中&#xff0c;我们提…

javascript 7种继承-- 原型式继承分析(4)

文章目录 概要继承的进化史技术名词解释原型式继承原型式继承1原型式继承2对比图 原型链继承 vs 原型式继承案列分析源代码解析效果图小结优点缺点 概要 这阵子在整理JS的7种继承方式&#xff0c;发现很多文章跟视频&#xff0c;讲解后都不能让自己理解清晰&#xff0c;索性自…

【前端学java】JAVA中的packge与import

packge与import示例 Java中&#xff0c;使用package关键字来声明一个类所属的包&#xff1a; package myapp;public class MyClass {// 类的实现... }上述代码中&#xff0c;MyClass类被声明为属于myapp的包。在其他的代码中使用该类时&#xff0c;需要使用完整的包名来引用它…

学习DT材质基础

Lambert材质和常用颜色属性 Maya材质的发光属性 Maya材质的光线跟踪属性 看不见阴影是因为背景用错材质了 MAYA矢量渲染 各向异性材料&#xff08;看高光&#xff09; 渐变材质 开启光线跟踪 表面着色器材质

微服务——Docker

docker与虚拟机的区别 首先要知道三个层次 硬件层:计算机硬件 内核层:与硬件交互&#xff0c;提供操作硬件的指令 应用层: 系统应用封装内核指令为函数&#xff0c;便于程序员调用。用户程序基于系统函数库实现功能。 docker在打包的时候直接把应用层的函数库也进行打包&a…

【GeoDa实用技巧100例】015:Geoda构建箱线图

文章目录 一、箱线图介绍二、Geoda制作箱形图三、箱形图与箱形地图的链接一、箱线图介绍 箱形图,也称箱线图(Box and Whisker Diagram)、箱图、盒须图、盒式图和盒形图等,是一种用作显示一组数据分散情况资料的统计图。因形状如箱子而得名。箱形图是由美国著名统计学家图基在…

vue3-组件中的变化

1. 路由 1. 安装指令&#xff1a;npm i vue-routernext 2. 创建路由&#xff1a;createRouter2. 异步组件&#xff08;defineAsyncComponent&#xff09; defineAsyncComponent 是用于定义异步组件的函数。defineAsyncComponent 接受一个工厂函数作为参数&#xff0c;这个工厂…

opencv-22 图像几何变换01-缩放-cv2.resize()(图像增强,图像变形,图像拼接)

什么是几何变换&#xff1f; 几何变换是计算机图形学中的一种图像处理技术&#xff0c;用于对图像进行空间上的变换&#xff0c;而不改变图像的内容。这些变换可以通过对图像中的像素位置进行调整来实现。 常见的几何变换包括&#xff1a; 平移&#xff08;Translation&#x…

力扣热门100题之无重复字符的连续子串【中等】

题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子…

Java8实战-总结6

Java8实战-总结6 通过行为参数化传递代码对付啰嗦匿名类第五次尝试&#xff1a;使用匿名类第六次尝试&#xff1a;使用Lambda表达式第七次尝试&#xff1a;将List类型抽象化 真实的例子用Comparator来排序 通过行为参数化传递代码 对付啰嗦 人们不愿意用那些很麻烦的功能或概…

个微API,微信机器人开发

简要描述&#xff1a; 退出群聊 请求URL&#xff1a; http://域名地址/quitChatRoom 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明wI…

Redis实战案例24-关注推送

1. Feed流实现方案 拉模式主要缺点&#xff0c;延迟问题&#xff0c;极端情况某个用户关注了成千上万的up主&#xff0c;每位up主又发布了十几条博客&#xff0c;此时拉模式的延迟就会很高&#xff1b; 推模式缺点也很明显&#xff0c;内存消耗太大&#xff0c;假设up主是千万级…