[C++进阶]模板进阶

此篇是学完stl后对于模板的补充

建议先看看这个[C++初阶]模板初阶-CSDN博客

一、类模板

此处是对初阶讲过的

1. 类模板的定义格式

template<class T1, class T2, …, class Tn>
class 类模板名 {};

例如我们之前学习过的vector类:

template<class T>
class vector
{
private:T* _start;T* _finish;T* _end_of_storage;
};

2.类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

void test()
{vector<int> v1;vector<double> v2;
}

二、非类型模板参数

模板参数分类类型形参与非类型形参:
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
比如,定义一个模板类型的静态数组。
template<class T, size_t N = 10>
class array
{
public:T& operator[](size_t index){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;
};
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的
2. 非类型的模板参数必须在编译期就能确认结果

三、模板特化

概念 :通常,使用模板可以支持与类型无关的代码,但是在特殊情况下,对于某些特殊类型需要特殊处理,所以这就是模板的特化。

比如:实现了一个专门用来进行小于比较的函数模板

template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{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 指 针的地址,这就无法达到预期而错误。
此时,就 需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特 化中分为函数模板特化 类模板特化

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* left, Date* right)
{return *left < *right;
}

函数重载的方式,反而代码简洁,可读性高,所以函数模板不建议特化。遇到不能处理的类型,还是直接重载一份对应类型的函数吧。

2.类模板特化

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 test()
{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;
};

针对以上基础模板,我们有两种偏特化方式:

  1. 部分特化:将模板参数类表中的一部分参数特化
    template<class T1>
    class Data<T1, int>
    {
    public:Data() {cout<<"Data<T1, int>" <<endl;}
    private:T1 _d1;int _d2;
    };
    

  2. 参数进一步限制:
    偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
    template<class T1, class T2>
    class Data<T1*, T2*>
    {
    public:Data() {cout<<"Data<T1*, T2*>" <<endl;}
    private:T1* _d1;T2* _d2;
    };template<class T1, class 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;
    };
    

注意:

  • 引用必须初始化
  • 传递常引用

下面我们将特化测试:

#include<iostream>
#include<string>
#include<array>
#include<vector>
using namespace std;//类模板
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; }
};// 偏特化/半特化
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 << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;cout << "Data<T1*, T2*>-偏特化" << endl << endl;//T1 x1;//T1* p1;}
};template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;cout << "Data<T1&, T2&>" << endl << endl;}
private:
};template <typename T1, typename T2>
class Data <T1&, T2*>
{
public:Data(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;cout << "Data<T1&, T2*>" << endl << endl;}
private:
};int main()
{Data<int, int> d1;Data<int, char> d2;Data<int, double> d3;Data<int*, double*> d4;Data<int*, int**> d5;Data<int&, int&> d6;Data<int&, int*> d7;return 0;
}

运行结果:

3)  类模板特化应用示例
有如下专门用来按照小于比较的类模板 Less
#include<vector>
#include <algorithm>
template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};

我们正常使用没有问题

但是,我们写过一个日期类是吧,如果我们想用这个模板去直接使用比较我们的日期类然后排序会怎么样呢?

我们写过的日期类:

class Date
{
public: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);}friend ostream& operator<<(ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}

测试的代码(我们分两种方式传入):

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>());for (auto& e : v1){cout << e<<endl;}cout << endl;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*>());for (auto& e : v2){cout << *e << endl;}cout << endl;return 0;
}

运行一下:

我们发现v1可以直接排序,结果是日期升序,v2可以直接排序,结果错误日期还不是升序

然后我们主要来看看什么情况

我们把v2的地址也打印出来

	for (auto& e : v2){cout << e << endl;}

运行结果:

我们可以发现: v2中放的地址是升序
所以,我们需要在排序过程中,让sort比较v2中存放地址指向的日期对象,但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期。
总结:对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:sort最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* y) const{return *x < *y;}
};

我们把它加进去,测试一下:

emplate<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* 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>());for (auto& e : v1){cout << e<<endl;}cout << endl;vector<Date*> v2;v2.push_back(&d1);v2.push_back(&d2);v2.push_back(&d3);sort(v2.begin(), v2.end(), Less<Date*>());for (auto& e : v2){cout << *e << endl;}cout << endl;return 0;
}

运行结果:

可见v2的排序结果正确了,因此我们写的对Less类模板按照指针方式特化正确。

四、模板分离编译

1. 分离编译的概念

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

2 模板的分离编译

假设模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义

// a.h
template<class T>
T Add(const T& left, const T& right);// a.cpp
#include"a.h"
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;
}

分析:

  1. 头文件不参与编译,各源文件分别编译,生成目标文件。
  2. a.cpp编译时,没有检查到Add的实例化,因此没有生成具体的加法函数,也就没有具体的函数地址。
  3. main.cpp编译时,对于Add的实例化,转换为汇编call指令,但是具体地址还没有确定,等待链接时确定。
  4. 链接时,编译器找不到Add的具体地址,所以报错。

 3.解决方法

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

对于为什么C++编译器不能支持对模板的分离式编译大家可以在站内看看其他大佬的讲解

五、模板总结

优点:

1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生
2. 增强了代码的灵活性
缺点:
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

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

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

相关文章

C++中的多路转接技术之epoll

epoll 是干什么的&#xff1f;举个简单的例子 epoll的相关系统调用**epoll_create**和epoll_create1区别 epoll_ctl参数解释 **epoll_wait**参数说明返回值 epoll的使用 **epoll**工作原理epoll的优点(和 **select** 的缺点对应)epoll工作方式**水平触发**Level Triggered 工作…

Springboot 启动时Bean的创建与注入(一)-面试热点-springboot源码解读-xunznux

Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读 文章目录 Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读构建Web项目流程图&#xff1a;堆栈信息&#xff1a;堆栈信息简介堆栈信息源码详解1、main:10, DemoApplication (com.xun.demo)2…

HashMap与ConcurrentHashMap

文章目录 HashMap1.1 HashMap 的数据结构&#xff1f;1.2 HashMap 的动态扩容1.3 Hash实现方法1.4 如何解决Hash冲突 ConcurrentHashMap HashMap 1.1 HashMap 的数据结构&#xff1f; 哈希表结构&#xff08;链表散列&#xff1a;数组链表&#xff09;实现&#xff0c;结合数…

详细分析Springboot自定义启动界面(附Demo)

目录 前言1. banner.text1.1 配置文件关闭1.2 启动类关闭1.3 命令行关闭 2. 自定义Banner类3. 自动配置类4. 总结 前言 实现自定义启动动画是一项有趣的任务&#xff0c;虽然Spring Boot本身不提供内置的动画功能&#xff0c;但可以通过一些技巧来实现 以下主要以Demo的形式展…

三字棋游戏(C语言详细解释)

hello&#xff0c;小伙伴们大家好&#xff0c;算是失踪人口回归了哈&#xff0c;主要原因是期末考试完学校组织实训&#xff0c;做了俄罗斯方块&#xff0c;后续也会更新&#xff0c;不过今天先从简单的三字棋说起 话不多说&#xff0c;开始今天的内容 一、大体思路 我们都知…

MongoDB教程(十三):MongoDB覆盖索引

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言什么是覆盖…

数据结构(栈及其实现)

栈 概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。 进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出 LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&…

PyCharm创建一个空的python项目

1.设置项目路径 2.配置python解释器 右下角可以选择always

【Linux】线程——生产者消费者模型、基于阻塞队列的生产消费者模型、基于环形队列的生产消费者模型、POSIX信号量的概念和使用

文章目录 Linux线程6. 生产消费者模型6.1 基于阻塞队列的生产消费者模型6.1.1 阻塞队列模型实现 6.2 基于环形队列的生产消费者模型6.2.1 POSIX信号量的概念6.2.2 POSIX信号量的使用6.2.3 环形队列模型实现 Linux线程 6. 生产消费者模型 生产消费者模型的概念 生产者消费者模…

Jackson详解

文章目录 一、Jackson介绍二、基础序列化和反序列化1、快速入门2、序列化API3、反序列化API4、常用配置 三、常用注解1、JsonProperty2、JsonAlias3、JsonIgnore4、JsonIgnoreProperties5、JsonFormat6、JsonPropertyOrder 四、高级特性1、处理泛型1.1、反序列化List泛型1.2、反…

Java 写一个可以持续发送消息的socket服务端

前言 最近在学习flink, 为了模仿一个持续的无界的数据源, 所以需要一个可以持续发送消息的socket服务端. 先上效果图 效果图 socket服务端可以持续的发送消息, flink端是一个统计单词出现总数的消费端,效果图如下 源代码 flink的消费端就不展示了, 需要引入一些依赖和版本…

Linux系统编程基础

Linux操作系统 Linux不是一个具体的操作系统&#xff0c;而是一类操作系统的总称&#xff0c;具体版本成为发行版。 Red Hat&#xff1a;目前被IBM收购&#xff0c;收费版&#xff0c;目前最大的Linux供应商CentOS&#xff1a; Red Hat退出的免费版Ubuntu&#xff1a;界面比较友…

二十一、【机器学习】【非监督学习】- 谱聚类 (Spectral Clustering)​​

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

hung 之 Android llkd

目录 1. llkd 简介 2. 原理 2.1 内核活锁 2.2 检测机制 2.3 为什么 persistent stack signature 检测机制不执行 ABA 检查&#xff1f; 2.4 为什么 kill 进程后&#xff0c;进程还存在就能判定发生了内核 live-lock&#xff1f; 3. 代码 3.1 内核 live-lock 检查 3.2 …

摸鱼大数据——用户画像——如何给用户“画像”

2、如何给用户“画像” 2.1 什么是标签体系 标签: 是某一种用户特征的符号表示 标签体系: 把用户分到多少类别里面去, 这些类是什么, 彼此之间有什么关系, 就构成了标签体系 标签解决的问题: 解决描述(或命名)问题以及解决数据之间的关联 2.2.1 标签的分类 用户画像标签一…

【附源码】IMX6U嵌入式Linux开发板连接阿里云--MQTT协议

演示 IMX6U嵌入式Linux开发板连接阿里云 阿里云创建设备&&获取LinkSDK 如果还不知道怎么在阿里云创建设备和获取连接阿里云的LinkSDK的话&#xff0c;先看这篇文章&#xff0c;再到这里。看这篇文章的时候&#xff0c;麻烦将下方文章打开对照着看&#xff0c;因为一些…

重测序数据处理得到vcf文件

重测序数据处理得到vcf文件 文章目录 重测序数据处理前言1. 数据是rawdata&#xff0c;需用fastp对数据进行质控和过滤2. 利用getorganelle软件组装叶绿体基因组3. 检查基因组大小&#xff0c;确认是否完整&#xff0c;然后和已知的红毛菜科叶绿体基因组一起构树4. 根据树形结果…

微积分-微分应用2(平均值定理)

要得出平均值定理&#xff0c;我们首先需要以下结果。 罗尔定理 设函数 f f f 满足以下三个假设&#xff1a; f f f 在闭区间 [ a , b ] [a, b] [a,b] 上连续。 f f f 在开区间 ( a , b ) (a, b) (a,b) 上可导。 f ( a ) f ( b ) f(a) f(b) f(a)f(b) 则在开区间 ( a , b …

CTFHUB-SQL注入-UA注入

目录 判断是否存在注入 判断字段数量 判断回显位置 查询数据库名 查询数据库下的表名 查询表中的字段名 查询字段名下的数据 由于本关是UA注入&#xff0c;就不浪费时间判断是什么注入了&#xff0c;在该页面使用 burp工具 抓包&#xff0c;修改User-Agent&#xff0c;加…

JavaScript之Web APIs-DOM

目录 DOM获取元素一、Web API 基本认知1.1 变量声明1.2 作用和分类1.3 DOM树1.4 DOM对象 二、获取DOM对象2.1 通过CSS选择器来获取DOM元素2.2 通过其他方式来获取DOM元素 三、操作元素内容3.1 元素.innerTest属性3.2 元素.innerHTML属性 四、操作元素属性4.1 操作元素常用属性4…