模版进阶(template)

1.非类型模版参数

模版参数分类类型形参与非类型形参。

① 类型形参:出现在在模板参数列表中,跟在class或者typename之类的参数类型名称

② 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

(1) 概念

● C++20以前非类型模版参数只支持整形常量浮点数、类对象以及字符串是不允许作为非类型模板参数的 bool类型也可以作非类型模版参数

C++20以后才开始支持更多类型作非类型模版参数。(double等)

● 非类型的模板参数必须在编译期就能确认结果

● 可以给缺省值,但是调用缺省值时,老版本要带上<>,新版本不用,建议用老版本:

namespace zyt
{template<size_t N = 10>class Stack{public://...private:size_t _arr[N];};
}
int main()
{zyt::Stack<> a0;//老版本调用要用<>,建议用老版本//zyt::array a1;//C++20以后调用不用<>zyt::Stack<10> a2;return 0;
}

(2) 模版类型的静态数组:

普通静态数组的越界检查采用抽查的形式,用标识符判断,越界读取一般不能被查出来。

● 模版定义的数组在越界检查上更精准,array越界读写都能检查,调用了operator[ ]。

● 模版定义的动态数组可以动态调整数组的大小,但空间是在堆上开辟的,模版定义数组是在栈上开的,而栈上开空间比在堆上开空间要效率高

namespace zyt
{// 定义一个模板类型的静态数组template<class T, size_t N = 10>class array{public:T& operator[](size_t index) {assert(index < N);return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}void Testarray()
{//数据存在栈上zyt::array<int, 10> a1;zyt::array<int, 100> a2;int a3[10];//越界检查//静态数组,抽查。标志位判断(程序进行完后检查标志位是否被修改,若是修改了就会报错)//越界读一般不能被查出来//cout << a3[10] << endl;//底层是指针解引用//a3[11] = 10;//a3[12] = 10;//array越界读写都能检查,会调用operator[]//error: Assertion failed: index < N//a1[10]; //a1[12] = 10;//在堆上开空间,栈上开空间比在堆上开空间要效率高vector<int> v(100, 1);//都是100个字节,但占用内存大小却不一样cout << sizeof(a2) << endl;//408cout << sizeof(v) << endl;//32
}

(3) 思考 

在示例代码最后两行我们发现同样对模版数组和模版动态数组开100字节的空间,为什么sizeof之后的结果差距大?

在C++中,sizeof 运算符返回的是对象在内存中占用的字节数,但是它并不总是反映对象实际分配的内存大小。array会存储100个int类型的元素,并且并且 sizeof 通常会返回正好是100个 int 大小的总和加上可能的填充字节。vector<int> 是一个动态数组,它存储了一个指向动态分配内存的指针,vectorsizeof 不包括它动态分配的内存,只包括它在栈上存储的元数据

2.模版的特化

(1) 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
// 函数模板 -- 参数匹配
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;
}
可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
此时,就 需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特化中分为 函数模板特化 类模板特化

(2) 函数模版特化

函数模版特化的步骤:

① 必须先有一个基础的函数模版

② 关键字template后面接一对空的尖括号<>

③ 函数名后跟一对尖括号,尖括号中指定需要特化的类型

④ 函数形参表:必须要和模版函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

// 函数模板 -- 参数匹配
template<class T>
bool LessFunc(const T& left, const T& right)//优化,当T是类类型时,避免拷贝构造
{return left < right;
}
// 特化两个版本
template<>
bool LessFunc<const Date*>(const Date* const & left, const Date* const& right)
{//*右用const是对指针修饰//*左用const修饰的是指针所指物是const Date类型return *left < *right;
}
template<>
bool LessFunc<Date*>(Date* const& left, Date* const& right)
{//*右用const是对指针修饰return *left < *right;
}int main()
{cout << LessFunc(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << LessFunc(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << LessFunc(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了return 0;
}
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该 函数直接给出。
//推荐直接实现一个函数,编译器调用时也会优先调用函数而不是特化
bool LessFunc(Date* left, Date* right)//*后用const才是对指针修饰
{return *left < *right;
}
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

(3) 类模版特化

<1> 全特化

全特化即是将模板参数列表中所有的参数都确定化。
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; }
private:int _d1;char _d2;
};
void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}

<2> 偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};

偏特化有以下表现形式:

① 部分特化 :将模板参数类表中的一部分参数特化。

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};

② 参数更近一步的限制:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

//两个参数偏特化为指针类型
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;
};
void test2()
{Data<double, int> d1; // 调用特化的int版本Data<int, double> d2; // 调用基础的模板Data<int*, int*> d3; // 调用特化的指针版本Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}

<3> 应用示例

#include<vector>
#include<algorithm>
template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};
int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vector<Date> v1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);// 可以直接排序,结果是日期升序sort(v1.begin(), v1.end(), Less<Date>());vector<Date*> v2;v2.push_back(&d1);v2.push_back(&d2);v2.push_back(&d3);// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期sort(v2.begin(), v2.end(), Less<Date*>());return 0;
}
对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:sort最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题:
特化之后,在运行下述代码,就可以得到正确的结果
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* y) const{return *x < *y;}
};

3.模版分离编译

(1) 什么是分离编译?

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

(2) 模版的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

分析:

C/C++程序要运行的一般步骤:预处理 ---> 编译 ---> 汇编 ---> 链接

总结来说:在预处理阶段,在main.cpp文件中的 a.h头文件展开后,只有模版的声明,无法调用实例化的模版,所以发生编译报错。

(3) 解决方法

● 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
● 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

4.模版总结

优点:

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

 缺陷:

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

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

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

相关文章

如何在算家云搭建RVC-WebUI(语音转换)

一、Retrieval-based-Voice-Conversion-WebUI简介 Retrieval-based-Voice-Conversion-WebUI&#xff08;简称 RVC&#xff09;模型是一个基于 VITS&#xff08;Variational Inference with adversarial learning for end-to-end Text-to-Speech&#xff09;的简单易用的语音转…

系统分析师11:企业信息化战略与实施

1 内容概要 信息与信息化的概念信息系统战略规划政府信息化与电子政务企业信息化与电子商务企业应用集成 2 信息与信息化的概念 2.1 信息的概念及特点 2.1.1 信息的定义 香农&#xff1a;信息就是不确定性的减少。维纳&#xff1a;信息就是信息&#xff0c;既不是物质&…

企业导师面对面,产教融合实训基地搭建人才成长快车道

产教融合实训基地是指以特定专业群为服务对象&#xff0c;由高职院校和企业双方共同投入、共同利用&#xff0c;并由若干实验实训室和模块产线组成的综合性实训基地。这一基地不仅具备工学结合的功能&#xff0c;还融合了教育、产业和科技创新的要素&#xff0c;形成了一种特殊…

大田上的路径跟踪论文汇总

文章目录 2022Path tracking control method and performance test based on agricultural machinery pose correction&#xff08;Computers and Electronics in Agriculture&#xff09;An optimal goal point determination algorithm for automatic navigation of agricult…

最新Kali Linux超详细安装教程(附镜像包)

一、镜像下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1BfiyAMW6E1u9fhfyv8oH5Q 提取码&#xff1a;tft5 二、配置虚拟机 这里我们以最新的vm17.5为例。进行配置 1.创建新的虚拟机&#xff1a;选择自定义 2.下一步 3.选择稍后安装操作系统 4.选择Debian版本 因…

Mastering Qt 番外 —— 添加源码调试

笔者最近正在尝试深入的学习Qt框架&#xff0c;经常需要明确我经常使用的类底下发生了什么&#xff0c;因此笔者决定仔细研究一下如何进行源码级别的调试 此篇文章将会介绍如何使用Qt Creator这个IDE进行调试。最终效果如下 EasyWay 笔者采用的是这个最简单明了的方式&#xff…

springboot调用python脚本实现ocr图片文字识别功能

OCR简介: OCR(Optical Character Recognition),即光学字符识别,是一种利用计算机自动识别和解析图像中的文字信息的技术。它能够将纸质文档、图片、照片等载体上的文字信息转化为计算机可编辑和处理的文本数据。 一,准备工作 1,java环境 2,springboot项目 3,python环境 …

如何增强水凝胶湿粘附力?藤壶启发与溶剂交换策略相结合!

大家好&#xff0c;今天我们来聊聊一项关于水凝胶的有趣研究——《Barnacle inspired strategy combined with solvent exchange for enhancing wet adhesion of hydrogels to promote seawater-immersed wound healing》发表于《Bioactive Materials》。在湿或水下环境中&…

rem与px转换工具

rem与px都是css样式单位,通常px是我们比较熟悉的单位,所以根据设计稿转换rem的话,一般都需要根据html的设置的px字号来转换,本工具可以实现rem与px之间的转换

50个必须知道的VS代码扩展

我们即将浏览50个必须知道的VS Code扩展&#xff0c;这些扩展将大幅提高您的生产力&#xff0c;并帮助您像专业人士一样编码&#xff01; 1. TabNine TabNine 是一个基于AI的自动完成工具&#xff0c;它可以根据您的代码上下文和模式建议完成&#xff0c;通过智能自动完成提高…

从源码到应用:互联网医院系统与预约问诊APP开发实战指南

对于开发者来说&#xff0c;如何从源码出发构建一个高效、稳定的互联网医院系统和预约问诊APP&#xff0c;是一个具有挑战性但又充满潜力的任务。本文将深入探讨互联网医院系统源码的核心技术和开发要点&#xff0c;结合预约问诊APP的实现路径&#xff0c;分享从源码到应用的开…

大工程师客户端下载 官方地址

https://download.3dsource.cn/3DSource_Client.exe

原装二手MS2724C安立(Anritsu)MS2724B手持式频谱分析仪

安立ANRITSU MS2724C MS2724B手持式频谱分析仪 Anritsu MS2724C 高性能手持式频谱分析仪为无线专业人士提供了在恶劣的 RF 和物理环境中进行最苛刻的测量所需的性能。使用主要仪器功能可以轻松进行测量&#xff1a;频率、跨度、幅度和带宽。专用的常用功能键和熟悉的计算器型键…

vs code 跳转很慢

查看结构体、接口等非常之慢。c/c语言服务功能使用了智能引擎所致&#xff0c;设置为模糊检索即可。 修改如下&#xff1a; 1.打开"文件"&#xff0c;选"首选项"&#xff0c;"设置" 2.弹出的窗口中搜索 "C_Cpp.intelliSenseEngine" …

青柠视频云——视频丢包(卡顿、花屏、绿屏)排查

一、问题说明 近期有客户反馈&#xff0c;接入平台的设备经常出来卡顿、花屏、录屏的情况&#xff0c;出现这样的场景很是尴尬。 客户是私有化部署在公网环境&#xff0c;于是我们联系客户&#xff0c;对问题进行追踪排查。 二、场景复现 我们现场情况确认的过程中&#xff0c;…

纷享销客“子流程”创新——企业流程管理的革新之选

Jack是一家电子商务企业的销售经理&#xff0c;近期他的公司订单量激增&#xff0c;但随之而来的退货流程却成了一个棘手的问题。每天&#xff0c;成百上千的退货请求涌向客服部门&#xff0c;而退货流程的每一步&#xff1a;从接收退货申请、物流派单、商品检验入库&#xff0…

数据结构之“队列”

文章目录 1.队列的概念2. 队列的操作2.1 队列的定义2.2 队列的初始化Init2.3 入队列2.4 出队列2.5 取队头数据2.6 取队尾数据2.7 获取队列有效元素的个数2.8 销毁队列 1.队列的概念 1.1 栈也是一种特殊的线性表。 1.2 栈和队列的区别&#xff1a; &#xff08;1&#xff09;几端…

ASR1602 CAT1无线通讯模块在电动车电池中的应用

随着城市交通拥堵问题的加剧和环保意识的提升&#xff0c;两轮电动车成为越来越多人的首选出行工具。作为其核心部件&#xff0c;电池的性能和安全性直接影响到两轮电动车的整体使用体验。为了提升电池管理系统的智能化水平&#xff0c;CAT1模组正逐渐被引入到两轮车电池中&…

超声波清洗机哪个品牌好用又实惠?精选业内四款优质清洗机推荐

超声波清洗机作为一种创新的清洁解决方案&#xff0c;凭借其深入微观的清洁效能、简便的操作方式以及对物品的细腻呵护&#xff0c;正逐渐成为广受喜爱的清洁良品。不过&#xff0c;市面上品牌林立、型号多样&#xff0c;价格亦波动不一&#xff0c;这无疑为消费者选购时平添了…