模板进阶 | 非类型模板参数 | 类模板的特化 | 模板的分离编译 | 模板的优缺点

  非类型模板参数

我们可以认为非类型模板参数就是一个常量,在我们的类里面我们是不能对它进行改造

为什么会有这样的场景,其次就是C语言那里我们一般使用什么。

场景1

#include<iostream>
using namespace std;#define N 10
template<class T>
class Array
{
public:T& operator()(size_t index){return _arr[index];}const T& operator[](size_t index){return _arr[index];}size_t size(){return _size;}bool empty(){return _size == 0;}private:T _arr[N];size_t _size;};int main()
{Array<int> a;return 0;
}

 在我们上面这个场景中,我们要开辟这样一个数组,通过宏来控制我们静态数组的大小,这个也是C语言经常使用的场景,但是假如我们需要开辟另一个空间很大的数组,现在面对这种情况有两种方法,一种是改变宏的大小,这样就可以改变了,但是这个方法还是有一定的缺陷,比如我们一个数组的空间需要很大的时候,但是这个时候还有一个数组的空间是很小的时候,那这个时候两边都是需要进行满足的,所以就会在存在有一个数组的空间是会存在浪费的,第二个方法就是我们在写一个模板,需要一个数组就去再写一个模板,ctrl c + ctrl v

就能解决

所以面对上面的问题,我们也是有两种选法,但是都不能从根本上解决问题,所以就有了我们现在C++用到的非类型模板参数可以解决

改变代码

template<class T,size_t N = 10>
class Array
{
public:T& operator()(size_t index){return _arr[index];}const T& operator[](size_t index){return _arr[index];}size_t size(){return _size;}bool empty(){return _size == 0;}private:T _arr[N];size_t _size;};int main()
{Array<int> a;Array<int,20> b;return 0;
}

这样就可以解决我们的问题,这里需要记住的是非类型模板参数其实是个常量,而且必须是整型

总结使用场景

我们需要定义一个静态数组的时候就可以是用的到,我们的库里面其实就是有一个array,但是实际上非类型模板参数其实是苦了我们的编译器,我们的模板这样写之后,编译器就能根据你的需求去进行实例化              

                                                                                   --------很像到家思想的 死道友不死贫道

注意: 当前编译器只支持整型,非类型模板也是支持缺省参数(类型也是支持的,比如优先级队列就是这样的)

区别函数传参数和模板传参数

 函数传参数

是我们在运行的时候来确定参数的,就比如我们之前写的一些函数,这里用 add函数来说

int Add(int x, int y)
{return x + y;
}

比如在这个场景下我们要进行函数传参的时候,如果我们不去调用其实就没有传参数的过程,所以也就是只有在我们调用的时候才是会出现传参数

模板传参数

模板传参数就是在我们实例化的时候会进行,虽然有我们的按需实例化(后面讲),但是模板传参数的时候一定是在我们编译的时候编译器就是得知道的事

array真的很好吗

上面讲的array我们库里面其实是有的,但是array这个东西真的有这么好用吗,其实不是的,我们的vector可以完全替代array,array这个类是有点鸡肋的功能,而且他是静态的数组,在我们进行扩容的时候或者空间满的时候就不能进行调整了,所以array煤油vector好

但是这个发明肯定不是区别于我们的vector,是区别于我们的普通数组,因为它的检查更加严格,在我们的数组的时候对越界的检查其实是抽查,就和酒驾是一样的,而且越界读一般是不能检查出来的,越界写是抽查,但是array这些越界访问的操作都是能检查出来, 这就是array的唯一的优点了。但是还是想吐槽它的优点太有限了,我们vector也能进行检查访问,因为检查其实就是一个断言而已,这样就是一个小小的函数调用而已。

它还有缺陷 : 会导致栈溢出,因为我们栈的空间不是很大,堆的空间才很大。所以我们能用vector绝对不用array

按需实例化

演示按需实例化


template<class T,size_t N = 10>
class Array
{
public:T& operator()(size_t index){return _arr[index];}const T& operator[](size_t index){size(1);//有问题return _arr[index];}size_t size(){return _size;}bool empty(){return _size == 0;}private:T _arr[N];size_t _size;};int Add(int x, int y)
{return x + y;
}
int main()
{Array<int> a;Array<int,20> b;return 0;
}

这个地方我注释的地方语法是不是有问题的,但是我们如果编译的时候是没有报错误的。

我们模板在预处理的时候和编译过程中增加了一个环节 

根据模板实例化 - > 半成品模板 - > 实例化具体的类或者函数 - > 语法进行编译

所以我们可以猜测一下这里我们应该是 没有进行实例化!!!

哪怕我们这里写了实例化,也是没有检查出这个语法,所以结论就是我们的编译器是按需实例化的

也就是我们的成员函数调用哪个就进行哪个的实例化,我们这里没有调用这个operator[]函数,所以就不会进行报错,因为我们的编译就没有对他进行调用,那只有我们去调用的时候就会去实例化,这样就能检查出来这个问题了。

 模板的特化

函数模板的特化

场景1: 如果我们要对我们的日期进行比较大小 所以需要先有一个日期类的实现的代码

class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}

如果正常比较日期的话我们的代码是没有问题的,比如比较两个日期的大侠

class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 函数模板
template<class T>
bool Less(T left, T right)
{cout << "bool Less(T left, T right)" << endl;return left < right;
}
int main()
{Date d1(2024, 1, 1);Date d2(2023, 2, 2);cout << Less(d1, d2) << endl;return 0;
}

这样我们就是可以正常的进行比较的,但是现在我们给出场景2,如果遇到的是Date* 的指针呢,我们这个时候就是需要模板的特化

场景2 ------ 引出特化

我们现在要进行比较的是我们的Date* 指针的大小,我们可以写一个这样的模板解决。

class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 函数模板
template<class T>
bool Less(T left, T right)
{cout << "bool Less(T left, T right)" << endl;return left < right;
}
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
int main()
{Date d1(2024, 1, 1);Date d2(2023, 2, 2);cout << Less(d1, d2) << endl;Date* pd1 = new Date(2023, 12, 1);Date* pd2 = new Date(2023, 12, 3);cout << Less(pd1, pd2) << endl;return 0;
}

这样就对我们的Date* 进行比较了。

但是这个只是特化的最基础的使用。这是对于某种特殊类型的特殊处理

场景3

我们上面只是解决了我们Date* 的比较,如果我们还是需要对我们所有的指针进行比较的时候,我们的代码需要怎么写呢????

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

只需要这样写就能解决,这样可以测试不同的指针了。

注意 : 上面写的都是全特化 ,建议 : 函数模板不建议使用特化,使用重载能解决大部分的问题。

类模板的特化

全特化

template<class T1, class T2>
class Date
{public:Date(){cout << "Date<T1,T2>" << endl;}
private:T1 _d1;T2 _d2;
};//全透化template<>
class Date<int, char>
{
public:Date(){cout << "Date<int,char>" << endl;}
};
int main()
{Date<int, char> d1;Date<int, int> d2;return 0;
}

其实看代码就是能看出来,我们给出了他们的具体类型,这个就是全透化。

半特化/偏特化
template<class T1>
class Date<T1, char>
{
public:Date(){cout << "Date<T1,char>" << endl;}
};

上面的这个就是我们的特化的全部语法,但是要知道半特化不一定是特化部分的参数,只是对一些参数的限制,我们这里也能使用指针和引用(list的迭代和反向迭代器就是这样的)。就不演示了

template<class T>
class Date<T*,T*>
{
public:bool operator()(T* x, T* y){return *x < *y;}
};

总结 : 想针对某种类型进行特殊处理的时候就可以考虑使用特化。

模板的分离编译

模板是不支持声明和定义分离进行编译的,因为我们的编译器是不知道我们需要实例化成什么类,这里不支持分离编译是指在两个不同的文件下,而不是在同一个文件,我们来举出一个例子,然后进行分析,首先就是我们需要创建一个头文件,一个就是Fun.h,然后就是fun.cpp,还有就是test.cpp

Fun.h

#pragma once#include<iostream>
using namespace std;template<class T>
T Add(const T& x, const T& y);void Fun();

fun.cpp


#include"Fun.h"template<class T>
T Add(const T& x, const T& y)
{return x + y;
}void Fun()
{cout << "void Fun()" << endl;}

test.cpp

     

#include"Fun.h"int main()
{int ret = Add(1, 3);return 0;
}

   然后进行编译就会出现这样的报错信息

但是我们的普通函数就是可以通过,而且要注意这里的报错不是编译问题,而是链接问题

分析程序在预处理,编译,汇编以及链接做的事情

 

预处理

预处理这一部分需要做的是条件编译,宏替换,去注释,头文件展开,所以我们的每个.cpp文件里的头文件都会进行展开,因为编译器是不会对我们写的.h文件进行编译的。

编译

编译这一步很重要,会进行的事情很多,比如要语法分析,语意分析,还会生成语法树,就是在检出我们写的代码语法是不是正确,最后就是生成汇编代码。

汇编

生成二进制代码,形成目标文件

链接

合并生成可执行文件

注意: 这里只是讲个大概,具体的之前的文章也是讲过的。

两个文件都进行编译之后生成的目标文件,然后进行链接之后,我们的fun函数是有它的地址的,这个地址相当于一个跳转的指令,和我们之前所得call地址可以认为是一样的,fun函数是能成功生生call地址的,但是我们的函数模板,它会按需实例化,我们也不知道T要变成什么类型,这也就导致了最后链接时候找不到地址,所以才会链接错误

解决办法

1.模板的声明和定义放在同一个头文件,不要进行分离编译

2.声明类型,让编译器能实例化。也就是显示实例化

结论就是我们还是最好模板的声明和定义放在一起,这才是最好的解决方法。

模板的优缺点

【优点】
1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
今天的分享就到这里,下次分享的是oo语言的三大特性之一继承

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

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

相关文章

三天做完pandas数据分析50题第一天

三天做完pandas数据分析50题第一天 第1题 将python的list转换为Series第2题 将字典转换为Series第3题 将Series转换成python的list第4题 使用numpy创建series。第5题 如何为Series添加新的元素&#xff1f;第6题 使用字典创建DataFrame第7题 给DataFrame设置索引列第8题 生成一…

初学python记录:力扣2923. 找到冠军 I

题目&#xff1a; 一场比赛中共有 n 支队伍&#xff0c;按从 0 到 n - 1 编号。 给你一个下标从 0 开始、大小为 n * n 的二维布尔矩阵 grid 。对于满足 0 < i, j < n - 1 且 i ! j 的所有 i, j &#xff1a;如果 grid[i][j] 1&#xff0c;那么 i 队比 j 队 强 &…

windows 之 redis非安装版,启动与初始化密码

1、下载redis 免安装版 2、解压后&#xff0c;启动服务 3、双击客服端 4、设置密码 config set requirepass root123456成功后&#xff0c;退出服务再次双击 5、登录 再次执行命名时已经没权限了 使用 auth password 登录 成功后&#xff0c;就可以了 auth root123456 …

第三次作业

创建了一个教务管理系统的登录页面&#xff0c;其中包含左侧的图片以及右侧的表单容器&#xff0c;当鼠标悬停在表单容器上时&#xff0c;会稍微变大&#xff0c;并且图片容器会相应的缩小&#xff0c;是通过css的transition以及fiex属性实现。 表单容器包含用户名和密码的输入…

LeetCode-62. 不同路径【数学 动态规划 组合数学】

LeetCode-62. 不同路径【数学 动态规划 组合数学】 题目描述&#xff1a;解题思路一&#xff1a;动态规划&#xff0c;动规五部曲解题思路二&#xff1a;动态规划&#xff08;版本二&#xff09;解题思路三&#xff1a;数论 题目描述&#xff1a; 一个机器人位于一个 m x n 网…

30元腾讯云服务器搭建幻兽帕鲁Palworld多人联机游戏,畅玩

幻兽帕鲁太火了&#xff0c;官方palworld服务器不稳定&#xff1f;不如自建服务器&#xff0c;基于腾讯云幻兽帕鲁服务器成本32元全自动部署幻兽帕鲁服务器&#xff0c;超简单有手就行&#xff0c;全程自动化一键部署10秒钟即可搞定&#xff0c;无需玩家手动部署幻兽帕鲁游戏程…

逆向案例十六——简单webpack逆向,财联社信息

网址链接&#xff1a;财联社A股24小时电报-上市公司动态-今日股市行情报道 数据包sign参数为加密&#xff0c;可以直接搜索找参数的位置&#xff0c;搜索不到的情况下&#xff0c;在断点跟栈&#xff1a; 确定js文件所在位置&#xff0c;并打上断点。 点击加载刷新页面。可以发…

服务器代理

服务器代理 配置&#xff1a;64G内存1 3090&#xff08;24g&#xff09;1P4000&#xff08;8g&#xff09; SSH连接 工作路径&#xff1a;/home/ubuntu/workspace/python Anaconda路径&#xff1a;/home/Ubuntu 1.在工作路径下创建自己的文件夹作为workspace 2.以用户ubunbtu登…

AIGC的崛起:定义未来内容创作的新纪元

&#x1f31f;文章目录 &#x1f31f;AIGC简介&#x1f31f; AIGC的相关技术与特点&#x1f31f;AIGC有哪些应用场景&#xff1f;&#x1f31f;AIGC对其他行业影响&#x1f31f;面临的挑战与问题&#x1f31f;AIGC未来发展 &#x1f31f;AIGC十大热门网站推荐&#xff1a; 文心…

【前缀合】Leetcode 连续数组

题目解析 525. 连续数组 寻找一个子数组&#xff0c;这个子数组中包含相同数目的0和1&#xff0c;但是这个子数组需要最长的 算法讲解 只需在[0,i]寻找一段区间使得这一段区间的和也等于sum即可 细节问题&#xff1a;1. 这里的哈希表的value存的是下标&#xff0c;因为需要找…

【论文笔记】PointMamba: A Simple State Space Model for Point Cloud Analysis

原文链接&#xff1a;https://arxiv.org/abs/2402.10739 1. 引言 基于Transformer的点云分析方法有二次时空复杂度&#xff0c;一些方法通过限制感受野降低计算。这引出了一个问题&#xff1a;如何设计方法实现线性复杂度并有全局感受野。 状态空间模型&#xff08;SSM&…

Axios网络请求

Axios网络请求主要用于前后端请求&#xff0c;前后端分离时前端需要通过url请求后端的接口&#xff0c;并且处理后端传过来的数据。 Axios官网教程 安装 npm install axios在main.js导入 import axios from axios;//声明一个http变量&#xff01;&#xff01;&#xff01…

Vue 移动端(H5)项目怎么实现页面缓存(即列表页面进入详情返回后列表页面缓存且还原页面滚动条位置)keep-alive缓存及清除keep-alive缓存

一、需求 产品要求&#xff1a;Vue移动端项目进入列表页&#xff0c;列表页需要刷新&#xff0c;而从详情页返回列表页&#xff0c;列表页则需要缓存并且还原页面滚动条位置 二、实现思路 1、使用Vue中的keep-alive组件&#xff0c;keep-alive提供了路由缓存功能 2、因为我项…

VS2022MSDN(离线帮助文档)的下载安装与使用

哈喽,你好啊,我是雷工! 现如今最重要的能力莫过于自学能力,在工控项目或编程中遇到问题,想快速解决的最快的方法莫过于查看手册。 MSDN是VS中的一个帮助手册,可以用来查找各种函数,查看各种API文档,对初学者来说是个不可缺少的手册,非常方便。 01在线查看 当我们在联…

针式打印机左边距偏离太多,通过边距无法调节

如果如下已经设置正确的打印边距&#xff0c;还是打印异常&#xff0c;偏离太多的话 需要在打印机属性里面把纸张左边距设置为默认值&#xff0c;即0

Web前端-HTML

黑马程序员JavaWeb开发教程 一、初识web前端 1、 标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C负责指定 2、 三个部分组成 HTML&#xff1a;负责网页的结构&#xff08;页面元素和内容&#xff09;CSS&#xff1a;负责网页的表现&#xff08;页…

jenkins+gitlab配置

汉化 1、安装Localization: Chinese (Simplified)插件 &#xff08;此处我已安装&#xff09; &#xff08;安装完成后重启jenkins服务即可实现汉化&#xff09; 新增用户权限配置 1、安装插件 Role-based Authorization Strategy 2、全局安全配置 3、配置角色权限 4、新建…

再谈C语言——理解指针(一)

内存和地址 内存 在讲内存和地址之前&#xff0c;我们想有个⽣活中的案例&#xff1a; 假设有⼀栋宿舍楼&#xff0c;把你放在楼⾥&#xff0c;楼上有100个房间&#xff0c;但是房间没有编号&#xff0c;你的⼀个朋友来找你玩&#xff0c; 如果想找到你&#xff0c;就得挨个房…

C++11的更新介绍(新的类功能、可变参数模板)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 新的类功能 默认成员函数&…

蓝桥杯嵌入式(G431)备赛笔记——PWM+LCD+按键

目录 题目要求&#xff08;真题&#xff09;&#xff1a; cubeMX配置&#xff1a; 小试牛刀&#xff1a; Keil代码&#xff1a; 效果演示&#xff1a; 题目要求&#xff08;真题&#xff09;&#xff1a; 使用第十一届第二场真题&#xff0c;练习PWM波部分的代码&#xff0c…