模板讲解之进阶

在之前的C++入门的博客中我们就学习到了模板初阶,今天我们来学习模板的进阶,以便于更好地将模板运用到代码中

非类型模板参数

模板参数分类类型形参与非类型形参

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

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

请看下面的代码:
template的<>里面加了一个N=10,它是一个常量,作为模板的一个参数进行使用

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. 非类型的模板参数必须在编译期就能确认结果。

模板特化

模板特化的概念

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

废话不多说,请看代码:

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
int main()
{
	cout << Less(1, 2) << endl; // 代码段1
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 代码段2
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 代码段3
	return 0;
}

上面代码中的布尔函数Less是一个仿函数,它可以直接用()使用,很方便,在优先级队列中的greater就是用仿函数实现的

代码段1和代码段2都可以进行比较,并且结果符合我们的预期,但是代码段3比较后的结果时错误的,就是因为date*是一个特殊的类型,无法进行正确的比较,这个时候就可以用到模板特化了!

可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。

模板特化中分为函数模板特化与类模板特化。

函数模板特化

函数模板的特化步骤如下:

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

函数模板特化最基础的前提就是需要有一个基础的函数模板,否则不能进行特化,针对于上面的代码我们特化后就可以解决掉代码段3结果不正确的情况:

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

我们对Less函数进行模板特化后,代码段3就调用特化之后的模板,输出的结果就是正确的了!

但是函数模板特化实际上用的地方不多,一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

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

类模板特化

类模板特化分为两类:
全特化和偏特化

全特化

全特化即是将模板参数列表中所有的参数都确定化。
例如:
下面代码中的代码段1就是类模板的全特化

template<class T1, class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};
//代码段1
template<>
class Data<int, char>
{
public:
	Data() { cout << "Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};
void TestVector()
{
	Data<int, int> d1;//执行语句1
	Data<int, char> d2;//执行语句2
}

他的运行结果为:
执行语句1直接调用了类模板,执行语句2是调用了类模板的全特化,他的参数类型为int和char,符合全特化的参数类型
在这里插入图片描述

偏特化

偏特化是任何针对模版参数进一步进行条件限制设计的特化版本。

例如对于以下的类模板:

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); // 调用特化的指针版本
}
模板分离编译
什么是分离编译

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

模板的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

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

一般一个程序要进行运行:有以下几个步骤:
预处理–>编译–>汇编–>链接
具体的讲解请看图(偷的图)
在这里插入图片描述

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

大家想要深入地了解一下分离编译扩展阅读:
http://blog.csdn.net/pongba/article/details/19130

模板总结

【优点】

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

【缺陷】

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

好了,本篇博客的分享到这里就结束了,感谢大家的支持!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java基础 集合(五)Set详解

目录 简介 set种类 AbstractSet 抽象类 SortedSet 接口 HashSet LinkedHashSet TreeSet 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡&#xff…

【2024.2.2练习】方格取数

题目描述 题目思路 如果从A到B只走一次的话可以用动态规划轻松解决。问题在于会走两次&#xff0c;第二次显然要走获取数字最多的路径&#xff0c;但第一次走哪条路径需要抉择。 错误的思路是以为这道题适合贪心&#xff0c;两次都选择最优路线。可以举出反例。 A2112112B …

Vim编辑器

1.文件复制 拷贝/etc/profile 数据到/root 目录下 cp /etc/profile /root如果root文件夹在上一目录下 cp /etc/profile ../root 2.打开文件 vim etc/profile 打开ect文件夹中的profile文件 3.文件编辑 文件编辑分为一般模式 与编辑模式。打开文件为一般模式&#xff0c;按…

【c语言】strcpy()和strncpy():字符串复制详解

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;c语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&a…

C++ copy()函数详细介绍

copy() 是一个标准库函数&#xff0c;位于 头文件中。它用于将一个容器中的元素复制到另一个容器中&#xff0c;或者将一个范围内的元素复制到另一个范围中。 函数参数介绍 copy( first, last, d_first );first 和 last&#xff1a;表示输入范围的迭代器。 first 指向要复制的…

操作系统基础:内存管理概述【中】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 &#x1f3dd;️1 基本分页存储管理&#x1f3d6;️1.1 总览&#x1f3d6;️1.2 什么是分页存储&#x1f3f0;1.2.1 将物理空间分页&#x1f3f0;1.2.2 将逻辑空间分页&…

Android搭建python环境

通过wifi连接adb&#xff1a; 首先下载无线abd工具&#xff1a; https://www.downkuai.com/android/170494.html 运行效果图&#xff1a; 然后开启后根据自身ip即可连接&#xff1a; adb connect ip:5555 安装busybox: 首先执行如下命令查看手机架构&#xff1a; adb sh…

分布式ID介绍实现方案总结

分布式 ID 介绍 什么是 ID&#xff1f; 日常开发中&#xff0c;我们需要对系统中的各种数据使用 ID 唯一表示&#xff0c;比如用户 ID 对应且仅对应一个人&#xff0c;商品 ID 对应且仅对应一件商品&#xff0c;订单 ID 对应且仅对应一个订单。 我们现实生活中也有各种 ID&…

[C++]继承(续)

一、基类和派生类对象赋值转换 在public继承时&#xff0c;父类和子类是一个“is - a”的关系。 子类对象赋值给父类对象/父类指针/父类引用&#xff0c;我们认为是天然的&#xff0c;中间不产生临时对象&#xff0c;也叫作父子类赋值兼容规则&#xff08;切割/切片&#xff…

Spring-mybatis

怎样通过Spring整合Mybatis来实现业务 目录 1.导入依赖 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency>&l…

化工企业能源在线监测管理系统,能源管理新利器

化工企业在开展化工生产活动时&#xff0c;能源消耗量较大&#xff0c;其节能潜力空间也较大&#xff0c;因此必须控制能耗强度&#xff0c;促进能效水平的稳步提升。化工企业通过能源现状的分析&#xff0c;能够实现能源使用情况的实时反馈与监管&#xff0c;从而达到节能减排…

MobPush:Android SDK 集成指南

开发工具&#xff1a;Android Studio 集成方式&#xff1a;Gradle在线集成 安卓版本支持&#xff1a;minSdkVersion 19 集成前准备 注册账号 使用PushSDK之前&#xff0c;需要先在MobTech官网注册开发者账号&#xff0c;并获取MobTech提供的AppKey和AppSecret&#xff0c;…

搜狗开源框架Workflow网络模型分析

workflow是一个比较轻量化的后端服务框架&#xff0c;支持Linux/Mac/Windows主流平台&#xff0c;其网络模块是框架的核心。在workflow-windows分支上可以看到对windows的IOCP的封装&#xff0c;对于学习windows IOCP网络编程有很好的启发意义。因此&#xff0c;有必要对该网络…

第二十一回 阎婆大闹郓城县 朱仝义释宋公明-FreeBSD Linux 使用Rsync备份

阎婆状告宋江杀死她女儿阎婆惜&#xff0c;知县有意偏袒宋江&#xff0c;只是一味的拷打唐牛儿&#xff0c;但无奈张三张文远说刀子是宋江的&#xff0c;知县不得已差人拿宋江来审问。第一次没见到人&#xff0c;第二次派朱仝雷横两个人去。 朱仝到地窖里找到了躲藏的宋江&…

数字电源环路补偿(2)

上一篇数字电源环路补偿&#xff08;1&#xff09;-CSDN博客介绍了数字电源的环路设计的基本原理&#xff0c;并用了一个一型补偿器作为例子把LLC控得还行。 那么问题来了&#xff0c;一型补偿器好是好&#xff0c;它设计方便&#xff0c;结构简单&#xff0c;高效粗暴&#x…

2024美赛数学建模E题思路源码

赛题目的 可以将其拆解为以下主要问题&#xff0c;并为每个问题提出解决方案&#xff1a; 如何在极端天气事件越来越多的地区部署财产保险&#xff1f; 保险公司应在何时何地承保保单&#xff1f; 业主如何影响保险公司的承保决定&#xff1f; 如何建立能够评估未来房地产决…

基于Springboot的高校心理教育辅导设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的高校心理教育辅导设计与实现(有报告)。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;…

C# 引用同一个dll不同版本的程序集

因为项目需要所以必须在项目中引用不同版本的同一程序集 我要引用的文件是newtonsoft.json.dll 两个版本为12.0.0.0 和4.0.0.0 1.如果已经先引入了newtonsoft.json 12.0.0.0版本的程序集&#xff0c;如果直接引入另一个版本的程序集的话会提示不成功&#xff0c;所以先将另一个…

PyTorch基础-Tensors属性、Tensor的运算

PyTorch的基本概念 Tensor的基本概念 张量高于标量、向量、矩阵 标量说零维的张量&#xff0c;向量是一维的张量&#xff0c;矩阵是二维的张量 Tensor与机器学习的关系 Tensor的创建 函数功能Tensor(*size)基础构造函数Tensor(data)类似np.arrayones(*size)全1Tensorzeros(…

VPP学习-startup.conf配置文件

背景 VPP&#xff08;Vector Packet Processing&#xff0c;矢量报文处理&#xff09;&#xff0c;作为一个开源的高性能数据包处理框架&#xff0c;旨在提供可扩展、灵活且高效的网络数据包处理能力&#xff1b;由于传统Linux 内核协议栈整体网络吞吐性能的局限性&#xff0c;…