模板[C++]

目录

1.🚀泛型编程🚀

2.🚀函数模板🚀

2.1 ✈️函数模板概念✈️

2.2 ✈️函数模板格式✈️

2.3✈️函数模板的原理✈️

2.4 ✈️函数模板的实例化✈️

2.5 ✈️模板参数的匹配原则✈️

3.🚀类模板🚀

3.1 ✈️类模板的定义格式✈️

3.2 ✈️类模板的实例化✈️

4.🚀非类型模板参数🚀

5.🚀模版的特化🚀

5.1 ✈️概念✈️

5.2✈️函数模板特化✈️

5.3 ✈️类模板特化✈️

5.3.1 🚁全特化🚁

5.3.2 🚁偏特化🚁

5.3.3 🚁类模板特化应用实例🚁

刹国(结束)!!!!


1.🚀泛型编程🚀

        首先,假如我们要实现int,double,char等多种类型的交换函数该如何编写代码呢?我想大家的想法肯定是重载三个不同类型的函数,如下:

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}
void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}
void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}

那么如果再加几个类型呢?继续重载吗?使用函数重载虽然可以实现,但是有以下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

没错,就是使用模板!

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

模板分为函数模版与类模板


2.🚀函数模板🚀

2.1 ✈️函数模板概念✈️

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

2.2 ✈️函数模板格式✈️

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}

例如:

//template<typename T>
template<class T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

2.3✈️函数模板的原理✈️

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。 

2.4 ✈️函数模板的实例化✈️

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。

1. 隐式实例化:让编译器根据实参推演模板参数的实际类型

template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.0, d2 = 20.0;Add(a1, a2);Add(d1, d2);/*该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅Add(a1, d1);*/// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化Add(a1, (int)d1);Add<int>(a1,d1);//显示实例化return 0;
}

2. 显式实例化:在函数名后的<>中指定模板参数的实际类型

int main(void)
{
int a = 10;
double b = 20.0;
// 显式实例化
Add<int>(a, b);
return 0;
}

2.5 ✈️模板参数的匹配原则✈️

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2);
// 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}

2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
void Test()
{Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换(隐式类型转换)


3.🚀类模板🚀

3.1 ✈️类模板的定义格式✈️

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
class Vector
{
public:Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}// 使用析构函数演示:在类中声明,在类外定义。~Vector();void PushBack(const T& data);void PopBack();// ...size_t Size() { return _size; }T& operator[](size_t pos){assert(pos < _size);return _pData[pos];}
private:T* _pData;size_t _size;size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表(比如静态成员函数的定义)
template <class T>
Vector<T>::~Vector()
{if (_pData)delete[] _pData;_size = _capacity = 0;}

3.2 ✈️类模板的实例化✈️

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

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

所以,vector<int>与vector<double>是两个完全不同的类类型。


4.🚀非类型模板参数🚀

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

namespace bite
{// 定义一个模板类型的静态数组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. 非类型的模板参数必须在编译期就能确认结果


5.🚀模版的特化🚀

5.1 ✈️概念✈️

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl;// 可以比较,结果正确//Date为日期类(自定义的类)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指针的地址,这就无法达到预期而错误
此时,就需要对模板进行特化即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。


5.2✈️函数模板特化✈️

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

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* 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;
}

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

bool Less(Date* left, Date* right)
{return *left < *right;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。


5.3 ✈️类模板特化✈️

5.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;
}

5.3.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); // 调用特化的指针版本
}

5.3.3 🚁类模板特化应用实例🚁

有如下专门用来按照小于比较的类模板Less:

#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;}
};

本篇以模板(C++)为主题的博客到此就要结束了,希望本篇博客可以帮助到各位小伙伴,博主码字不易,如果本篇博客对你有所帮助,还望各位小伙伴点赞👍,收藏⭐+关注,感谢各位的支持!如果有什么不明白的地方或是另有其他的高见,也可以在评论区留言,博主也会及时回复或进行更改,欢迎指正,谢谢!

刹国(结束)!!!!

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

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

相关文章

文件中找TopK问题 的详细讲解

一&#xff1a;问题&#xff1a; 从一个包含10000整数的文件中找出最大的前10个数。 二&#xff1a;方法&#xff1a; 1&#xff1a;先直接拿文件的前10个数&#xff0c;建造一个小堆 2&#xff1a;再依次读取文件中&#xff0c;剩下的数&#xff0c;比堆顶大&#xff0c;则…

学习记录第二十九天

信号量————来描述可使用资源的个数 信号量&#xff08;Semaphore&#xff09;是一种用于控制多个进程或线程对共享资源访问的同步机制。在C语言中&#xff0c;通常我们会使用POSIX线程&#xff08;pthread&#xff09;库来实现信号量的操作 信号量有两个主要操作&#xf…

C语言 ——— 位段(位域)

目录 什么是位段 位段的内存分配 什么是位段 位段的声明和结构体是类似的 但有两个不同&#xff1a; 1. 位段的成员必须是整型家族&#xff1a; int&#xff08;整型&#xff09; &#xff0c;unsigend int &#xff08;无符号整型&#xff09;&#xff0c;sigend int&…

【初阶数据结构题目】32. 希尔排序

文章目录 希尔排序希尔排序的时间复杂度计算 希尔排序 希尔排序法又称缩小增量法。希尔排序法的基本思想是&#xff1a;先选定一个整数&#xff08;通常是gap n/31&#xff09;&#xff0c;把待排序文件所有记录分成各组&#xff0c;所有的距离相等的记录分在同一组内&#x…

歌曲爬虫下载

本次编写一个程序要爬取歌曲音乐榜https://www.onenzb.com/ 里面歌曲。有帮到铁子的可以收藏和关注起来&#xff01;&#xff01;&#xff01;废话不多说直接上代码。 1 必要的包 import requests from lxml import html,etree from bs4 import BeautifulSoup import re impo…

Qt作业合集

8.14作业 设置窗口&#xff0c;按钮&#xff0c;标签&#xff0c;行编辑器&#xff0c;实现快递速运登录页面 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//窗口//设置窗口的标题this->setWindowTitle("邮递系统")…

蚂蚁AL1 15.6T 创新科技的新典范

● 哈希率&#xff1a;算力达到15.6T&#xff08;相当于15600G&#xff09;&#xff0c;即每秒能够进行15.6万亿次哈希计算&#xff0c;在同类产品中算力较为出色&#xff0c;能提高WA掘效率。 ● 功耗&#xff1a;功耗为3510W&#xff0c;虽然数值看似不低&#xff0c;但结合其…

内存泄漏之如何使用Visual Studio的调试工具跟踪内存泄漏?

使用Visual Studio的调试工具跟踪内存泄漏是一个系统性的过程&#xff0c;主要包括启用内存泄漏检测、运行程序、分析内存使用情况以及定位泄漏源等步骤。 Visual Studio提供了多种方式来检测内存泄漏&#xff0c;你可以根据自己的需求选择合适的方法。 注意&#xff1a;下面…

【TiDB】10-对 TiDB 进行 TPC-C 测试

目录 1、安装bench工具 2、插入数据 3、运行测试 4、测试结果分析 4.1、总体性能概览 4.2、事务类型详细性能 4.3、错误事务分析 4.4、结论与建议 5、清理测试数据 TPC-C 是一个对 OLTP&#xff08;联机交易处理&#xff09;系统进行测试的规范&#xff0c;使用一个商…

大数据技术—— Clickhouse安装

目录 第一章 ClickHouse入门 1.1 ClickHouse的特点 1.1.1 列式存储 1.1.2 DBMS的功能 1.1.3 多样化引擎 1.1.4 高吞吐写入能力 1.1.5 数据分区与线程级并行 1.1.6 性能对比 第二章 ClickHouse的安装 2.1 准备工作 2.1.1 确定防火墙处于关闭状态 2.1.2 CentOS取消…

论文阅读笔记:ST-MetaNet-1

目录 前言 摘要 CCS 关键词 介绍 时空相关性的复杂组合 空间相关性 时间相关性 时空相关性的多样性 本篇博客结语 前言 读这篇论文边读边学&#xff0c;每天坚持发博客&#xff0c;看到哪学到哪&#xff0c;这系列文章既有翻译&#xff0c;又有深度详细解释&#xff…

2024开源资产管理系统推荐 8款免费开源IT资产管理系统/软件

开源资产管理系统 开源资产管理系统是帮助企业管理、跟踪和优化其资产的强大工具。这些系统能够自动记录资产的详细信息&#xff0c;如采购日期、使用情况、维护记录等&#xff0c;从而实现资产的全生命周期管理。企业可以通过这些系统优化资产使用效率&#xff0c;减少资产闲…

【瑞芯微RV1126(深度学习模型部署)】部署自己训练的yolov8-seg,实现足型检测!

前言 如果按照本系列第一篇博客那样交叉编译了opencv&#xff0c;那本文有些步骤就不用了&#xff0c;比如交叉编译工具链的下载&#xff0c;所以自己斟酌步骤。 本系列第一篇&#xff1a;https://blog.csdn.net/m0_71523511/article/details/139636367 本系列第二篇&#xff…

数字化转型底座-盘古信息IMS OS,可支撑构建MES/WMS/QCS/IoT等工业软件

在当今这个数字化浪潮汹涌的时代&#xff0c;众多企业纷纷踏上数字化转型之路。对于部分想自研工业软件的企业来说&#xff0c;一个强大、灵活且可扩展的数字化底座显得尤为重要。盘古信息IMS OS&#xff0c;&#xff0c;正是这样一款能够支撑构建MES&#xff08;制造执行系统&…

井字棋游戏(HTML+CSS+JavaScript)

&#x1f30f;个人博客主页&#xff1a;心.c 前言&#xff1a;这两天在写植物大战僵尸&#xff0c;写不动了&#xff0c;现在和大家分享一下之前我写的一个很简单的小游戏井字棋&#xff0c;这个没有AI&#xff0c;可以两个人一起玩&#xff0c;如果大家觉得我哪里写的有一些问…

BQ27441初始化配置程序,电压、SOC等参数读取程序

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 前言一、模拟IIC二、BQ27441初始化配置程序三、学习资料 前言 送给大学毕业后找不到奋斗方向的你&#xff08;每周不定…

html+css+js网页制作 自定义电商10个页面

htmlcssjs网页制作 自定义电商10个页面 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#…

机器学习 第11章-特征选择与稀疏学习

机器学习 第11章-特征选择与稀疏学习 11.1 子集搜索与评价 我们将属性称为“特征”(feature)&#xff0c;对当前学习任务有用的属性称为“相关特征”(relevant feature)、没什么用的属性称为“无关特征”(irrelevant feature)。从给定的特征集合中选择出相关特征子集的过程&a…

UART通信实现与验证(RS485)

前言 UART是一种常用的串行通信协议&#xff0c;RS485则是一种用于长距离和抗干扰的物理层标准。结合UART和RS485可以实现可靠的数据传输&#xff0c;特别是在多点通信和长距离应用中。通过合适的硬件连接、软件配置和验证测试&#xff0c;可以确保这一通信系统的稳定性和数据完…

【刷题笔记】二叉树2

1 二叉树的层序遍历 上一期我们讲了关于二叉树的前序、中序以及后序遍历的相关内容。然而&#xff0c;还存在一种遍历方式&#xff0c;这种方式非常符合我们人类的正常思维&#xff0c;可以求解很多树相关的问题&#xff0c;比较暴力——二叉树的层序遍历。 二叉树的层序遍历与…