【C++】模板

前言

        在我们平时的代码中经常会有不同类型的变量去执行效果差不多的函数。比如:swap(交换),sort(排序)。这些函数里其实会有大部分重复的段落,在这种情况下我们会使用重载函数,但是函数重载会有如下的问题:

1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
2. 代码的可维护性比较低,一个出错可能所有的重载均出错。
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码

一、函数模板

1.1概念

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

 1.2函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
​template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}
//可识别不同的同种类型交换(例:char与char,int与int,double与double)
注意:typename是用来定义模板参数关键字也可以使用class(切记:不能使用struct代替class)

1.3函数模板的实例化

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

隐式实例化编译器根据实参推断模板参数的实际类型

适用环境:交换类型为同一类

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

不适用情况:一个模板传两种类型

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);return 0;
}
分析:该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。
Add(a1, d1);
此时有两种处理方式: 1. 用户自己来强制转化 2. 使用显式实例化。

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

int main(void)
{int a = 10;double b = 20.0;// 显式实例化Add<int>(a, b);return 0;
}
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

1.4函数模板的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

简单来说就是:函数先找现成的然后再去模板匹配。


二、类模板

2.1类模板的格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};

2.2类模板的实例化

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

注意区分:

  • 在类中类名等同于类型
  • 在类模板中:类型是类型,类名是类名

例如:在下面代码中,类模板中函数放在类外进行定义时,需要加模板参数列表;在访问类模板时,要用Vector<T>(类型),而不是Vector(类名)

 三、非类型模板参数

在c语言中我们通常会使用宏,而常用类的c++中推出了非类型模板参数。比如我们在创建一个array时会在创建时确定他的大小等等。


模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在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. 非类型的模板参数必须在编译期就能确认结果

四、模板的特化

4.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指针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化.

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

这时,就遵循优先匹配原则。而且该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

 4.3类模板的特化

全特化

全特化即是将模板参数列表中所有的参数都确定化。
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;
}

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
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.1什么是分离编译

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

5.2 分离编译的方法

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
【分离编译扩展阅读】 http://blog.csdn.net/pongba/article/details/19130

六、模板总结

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

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

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

相关文章

测试开源C#人脸识别模块ViewFaceCore(4:口罩检测、性别预测、年龄预测)

ViewFaceCore模块中的MaskDetector类支持识别人脸是否戴了口罩或有遮挡&#xff0c;主要调用PlotMask函数执行口罩检测操作&#xff0c;其函数原型如下所示&#xff1a; PlotMaskResult PlotMask<T>(T image, FaceInfo info)public class PlotMaskResult{//// 摘要:// …

RabbitMQ 教程 | 第2章 RabbitMQ 入门

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

VMware Linux 可视化增加磁盘

1、VMware 增加磁盘 2、disks挂载磁盘 此处我挂载的是20G磁盘&#xff0c;截图只是用5G的做过程演示例子。 3、验证挂载磁盘

动手学深度学习v2笔记 —— 线性回归 + 基础优化算法

二 动手学深度学习v2 —— 线性回归 基础优化算法 目录: 线性回归基础优化方法 1. 线性回归 总结 线性回归是对n维输入的加权&#xff0c;外加偏差使用平方损失来衡量预测值和真实值的差异线性回归有显示解线性回归可以看作是单层神经网络 2. 基础优化方法 梯度下降 小批量…

Spring的创建及使用

文章目录 什么是SpringSpring项目的创建存储Bean对象读取Bean对象getBean()方法 更简单的读取和存储对象的方式路径配置使用类注解存储Bean对象关于五大类注解使用方法注解Bean存储对象Bean重命名 Bean对象的读取 使用Resource注入对象Resource VS Autowired同一类型多个bean对…

echart折线图,调节折线点和y轴的间距(亲测可用)

options代码&#xff1a; options {tooltip: {trigger: axis, //坐标轴触发&#xff0c;主要在柱状图&#xff0c;折线图等会使用类目轴的图表中使用。},xAxis: {type: category,//类目轴&#xff0c;适用于离散的类目数据&#xff0c;为该类型时必须通过 data 设置类目数据。…

iOS开发-启动页广告实现

iOS开发-启动页广告实现 启动页广告实现是一个非常常见的广告展示模式。 就是在启动时候显示广告&#xff0c;之后点击跳转到广告页面或者其他APP。 一、实现启动页广告 启动页广告控件实现&#xff0c;将View放置在keyWindow上&#xff0c;显示广告图片&#xff0c;点击广告…

Pytorch(二)

一、分类任务 构建分类网络模型 必须继承nn.Module且在其构造函数中需调用nn.Module的构造函数无需写反向传播函数&#xff0c;nn.Module能够利用autograd自动实现反向传播Module中的可学习参数可以通过named_parameters()返回迭代器 from torch import nn import torch.nn.f…

C++部署学习

gcc -E src/main.c -o src/main.i gcc -S src/main.c -o src/main.s gcc -C src/main.c -o src/main.o gcc src/main.c -o exec ./exec

RabbitMQ 教程 | 第3章 客户端开发向导

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

排序算法汇总

每日一句&#xff1a;你的日积月累终会成为别人的望尘莫及 目录 常数时间的操作 选择排列 冒泡排列 【异或运算】 面试题&#xff1a; 1&#xff09;在一个整形数组中&#xff0c;已知只有一种数出现了奇数次&#xff0c;其他的所有数都出现了偶数次&#xff0c;怎么找到…

面试之CurrentHashMap的底层原理

首先回答HashMap的底层原理? HashMap是数组链表组成。数字组是HashMap的主体&#xff0c;链表则是主要为了解决哈希冲突而存在的。要将key 存储到&#xff08;put&#xff09;HashMap中&#xff0c;key类型实现必须计算hashcode方法&#xff0c;默认这个方法是对象的地址。接…

【应用层】Http协议总结

文章目录 一、续->Http协议的学习 1.http请求中的get方法和post方法 2.http的状态码 3.http的报头 4.长链接 5.cookie&#xff08;会话保持&#xff09;总结 继续上一篇的内容&#xff1a; 上一篇的最后我们讲到了web根目录&#xff0c;知道…

使用Docker部署EMQX

原文链接&#xff1a;http://www.ibearzmblog.com/#/technology/info?id9dd5bf4159d07f6a4e69a6b379ce4244 前言 在物联网中&#xff0c;大多通信协议使用的都是MQTT&#xff0c;而EMQX是基于 Erlang/OTP 平台开发的 MQTT 消息服务器&#xff0c;它的优点很多&#xff0c;我…

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(12)-Fiddler设置IOS手机抓包,你知多少???

1.简介 Fiddler不但能截获各种浏览器发出的 HTTP 请求&#xff0c;也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Fiddler 能捕获Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。同理也可以截获iOS设备发出的请求&#xff0c;比如 iPhone、iPad 和 MacBook 等苹…

【BMC】OpenBMC使用基础(WSL2版本)

代码准备 OpenBMC是一个开源的项目&#xff0c;用于开发BMC固件。官网是https://www.openbmc.org/&#xff0c;不过里面似乎没有什么内容&#xff0c;所以还需要依赖其它的网站&#xff0c;https://github.com/openbmc&#xff0c;在这里可以下载到需要的代码和文档。其主体部…

C#,数值计算——对数正态分布(logarithmic normal distribution)的计算方法与源程序

对数正态分布&#xff08;logarithmic normal distribution&#xff09;是指一个随机变量的对数服从正态分布&#xff0c;则该随机变量服从对数正态分布。对数正态分布从短期来看&#xff0c;与正态分布非常接近。但长期来看&#xff0c;对数正态分布向上分布的数值更多一些。 …

Tailwind CSS:基础使用/vue3+ts+Tailwind

一、理解Tailwind 安装 - TailwindCSS中文文档 | TailwindCSS中文网 Installation - Tailwind CSS 1.1、词义 我们简单理解就是搭上CSS的顺风车&#xff0c;事半功倍。 1.2、Tailwind CSS有以下优势 1. 快速开发&#xff1a;Tailwind CSS 提供了一些现成的 class / 可复用…

ARM裸机-4

1、什么是交叉编译 1.1、两种开发模式 非嵌入式开发&#xff0c;A&#xff08;类&#xff09;机编写&#xff08;源代码&#xff09;、编译得到可执行程序&#xff0c;发布给A&#xff08;类&#xff09;机运行。 嵌入式开发&#xff0c;A&#xff08;类&#xff09;机编写&am…