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

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者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.mfbz.cn/a/42534.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 8.0 OCP (1Z0-908) 考点精析-性能优化考点6:MySQL Enterprise Monitor之Query Analyzer

文章目录 MySQL 8.0 OCP (1Z0-908) 考点精析-性能优化考点6&#xff1a;MySQL Enterprise Monitor之Query AnalyzerMySQL Enterprise Monitor之Query AnalyzerQuery Response Time index (QRTi)例题例题1: Query Analyzer答案与解析1 参考 【免责声明】文章仅供学习交流&#x…

【本地电脑搭建Web服务器并用cpolar发布至公网

本地电脑搭建Web服务器并用cpolar发布至公网访问 随着互联网的快速发展&#xff0c;网络也成为我们生活中不可缺少的必要条件&#xff0c;为了能在互联网世界中有自己的一片天地&#xff0c;建立一个属于自己的网页就成为很多人的选择。但互联网行业作为资本密集的行业&#x…

一)Stable Diffusion使用教程:安装

目前AI绘画最火的当属Midjorney和Stable Diffusion&#xff0c;但是由于Midjourney没有开源&#xff0c;因此我们主要分享下Stable Diffusion&#xff0c;后面有望补上Midjourney教程。 本节主要讲解Stable Diffusion&#xff08;以下简述SD&#xff09;的下载和安装。 1&…

呼吸灯——FPGA

文章目录 前言一、呼吸灯是什么&#xff1f;1、介绍2、占空比调节示意图 二、系统设计1、系统框图2、RTL视图 三、源码四、效果五、总结六、参考资料 前言 环境&#xff1a; 1、Quartus18.0 2、vscode 3、板子型号&#xff1a;EP4CE6F17C8 要求&#xff1a; 将四个LED灯实现循环…

Redis源码篇 - Ziplist数据结构

Ziplist是一种内存优化的list存储结构&#xff0c;通过使用连续的内存空间存储&#xff0c;来减少内存碎片化&#xff0c;同时和链表的不同还有&#xff0c;它不存储前后指针&#xff0c;而是通过变长的字节存储前节点元素长度&#xff0c;通过计算长度来实现节点的查找。它是一…

Google 登录支付,Firebase 相关设置

登录sdk: https://developers.google.com/identity/sign-in/android/start?hlzh-cn 支付sdk: https://developers.google.com/pay/api/android/overview?hlzh-cn Firebase sdk: https://firebase.google.com/docs/android/setup?hlzh-cn 登录设置&#xff1a; 创建凭据&…

机器学习-线性代数-5-空间中的向量投影与最小二乘法

空间中的向量投影与最小二乘法 文章目录 空间中的向量投影与最小二乘法一、引入二、投影和投影的描述1、投影描述最近2、利用矩阵描述投影(1)向一维直线投影(2)向二维平面投影(3)向n维子空间投影的一般情况 三、最小二乘法1、重要的子空间(1)互补的子空间(2)正交的子空间(3)相互…

12.面板问题

面板问题 html部分 <h1>Lorem ipsum dolor sit, amet consectetur adipisicing.</h1><div class"container"><div class"faq"><div class"title-box"><h3 class"title">Lorem, ipsum dolor.<…

(转载)神经网络遗传算法函数极值寻优(matlab实现)

本博客的完整代码获取&#xff1a; https://www.mathworks.com/academia/books/book106283.html 1案例背景 对于未知的非线性函数,仅通过函数的输入输出数据难以准确寻找函数极值。这类问题可以通过神经网络结合遗传算法求解,利用神经网络的非线性拟合能力和遗传算法的非线性…

make/makefile的使用

make/makefile 文章目录 make/makefile初步认识makefile的工作流程依赖关系和依赖方法make的使用 总结 make是一个命令&#xff0c;是一个解释makefile中指令的命令工具&#xff0c;makefile是一个文件&#xff0c;当前目录下的文件&#xff0c;两者搭配使用&#xff0c;完成项…

数据预处理matlab

matlab数据的获取、预处理、统计、可视化、降维 数据的预处理 - MATLAB & Simulink - MathWorks 中国https://ww2.mathworks.cn/help/matlab/preprocessing-data.html 一、数据的获取 1.1 从Excel中获取 使用readtable() 例1&#xff1a; 使用spreadsheetImportOption…

【AutoSAR 架构介绍】

AutoSAR简介 AUTOSAR是Automotive Open System Architecture&#xff08;汽车开放系统架构&#xff09;的首字母缩写&#xff0c;是一家致力于制定汽车电子软件标准的联盟。 AUTOSAR是由全球汽车制造商、部件供应商及其他电子、半导体和软件系统公司联合建立&#xff0c;各成…

npm link 实现全局运行package.json中的指令

packages.json "name":"testcli","bin": {"itRun": "index.js"},执行命令 npm link如果要解绑定 npm unlink testcli 现在你可以输入 itRun试一下

MySQL高阶语句之二

目录 一、子查询 1.1语法 1.2select 1.3insert 1.3update 1.4delete 1.5 exists 1.6别名as 二、MySQL视图 2.1功能 2.2区别 2.3联系 2.4 创建视图(单表) 2.5 创建视图(多表) 2.6修改原表数据 2.7修改视图数据 三、NULL值 四、连接查询 4.1内连接 4.1.1语法 4.1.…

LangChain+LLM大模型问答能力搭建与思考

1. 背景 最近&#xff0c;大模型&#xff08;LLMs&#xff0c;Large Language Models&#xff09;可谓是NLP领域&#xff0c;甚至整个科技领域最火热的技术了。凑巧的是&#xff0c;我本人恰好就是NLP算法工程师&#xff0c;面临着被LLMs浪潮淘汰的窘境&#xff0c;决定在焦虑…

配置jenkins 服务器与目标服务器自动化部署

在配置完远程构建后可以通过添加post-build step 执行shell脚本的方式将包传到远程服务器等一系列操作。 通过scp传输打包好的项目到目标服务器 按照链接 方式配置免密操作&#xff0c;需要注意的是要在jenkins 用户目录下配置生成私钥密钥&#xff0c;配置jenkins 的免密&…

我的踩坑记录!!!积累中......

bug记录&#xff1a; 解决 nodejs安装后&#xff0c;在安装目录下【nodejs】创建两个文件夹【node_global】及【node_cache】用来配置全局环境变量。 之后&#xff0c;打开cmd命令窗口&#xff0c;输入 npm config set prefix ”D:\Program Files\nodejs\node_global” npm con…

YOLO-V5分类实战系列 —— 调优自己的数据集+RK1808部署

YOLO-V5分类实战系列 —— 调优自己的数据集 1、保存训练和测试图片2、数据归一化3、数据增强3.1、数据增强库&#xff1a;albumentations3.2、数据增强库&#xff1a;torchvision 4、ONNX CPU 推理4.1、Pt 模型转为 ONNX4.2、ONNX 推理验证4.3、 ONNX CPU推理&#xff08;C&am…

蓝桥杯专题-真题版含答案-【生命之树】【消除尾一】【密码脱落】【生日蜡烛】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

使用GGML和LangChain在CPU上运行量化的llama2

Meta AI 在本周二发布了最新一代开源大模型 Llama 2。对比于今年 2 月发布的 Llama 1&#xff0c;训练所用的 token 翻了一倍&#xff0c;已经达到了 2 万亿&#xff0c;对于使用大模型最重要的上下文长度限制&#xff0c;Llama 2 也翻了一倍。 在本文&#xff0c;我们将紧跟趋…
最新文章