【C++初阶】C++模版(进阶)

news2024/11/25 0:41:36

文章目录

  • 前言
  • 一、非类型模板参数
  • 二、模板的特化
    • 概念
    • 函数模板特化
    • 类模板特化
      • 1.全特化
      • 3.偏特化
  • 三、模板分离编译
    • 什么是分离编译
    • 模板的分离编译
    • 解决方法
  • 模板总结

前言

前边我们讲解了模版初阶的内容,对泛型编程,函数模版,类模板有了一定的认识,今天我们来学习模版的进阶操作。

一、非类型模板参数

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

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

我们在C++模版进阶学到的都是类型模版,例如下边的代码:

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

类型模版参数跟在class或者typename之后,通过代表一个数据类型,用某一个数据类型来实例化。
但是在STL中有一个容器叫做array,是一个静态的数组,他的模版参数确实一个size_t 类型的N,这叫做非类型模版参数。
在这里插入图片描述
我们先来看下边一段代码:

namespace tmt
{
	// 定义一个模板类型的静态数组
	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;
	};
}

实现了一个简版的静态数组,我们发现他有非类型模版N,我们在使用时需要传入数组的大小,这样就有一个好处,在创建一个静态数组时,不仅可以控制静态数组的类型,也可以控制静态数组的大小,这不就更加符合泛型编程吗?

int main()
{
	tmt::array<int, 10> a1;
	tmt::array<double, 20> a2;
	return 0;
}

注意:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
    所以非类型模版参数必须是整形家族的,例如int char
  2. 非类型的模板参数必须在编译期就能确认结果。
    所以模版类不支持分文件编写,因为分文件编写是在编译完成后才会链接。

二、模板的特化

概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。
例如下边的代码:
先来实现一个date类方便后续的特化讲解:

struct Date
{
	Date(int year, int month, int day)
	:_year(year)
	, _month(month)
	, _day(day)
	{}

	bool operator>(const Date& d) const
	{
		if ((_year > d._year)
			|| (_year == d._year && _month > d._month)
			|| (_year == d._year && _month == d._month && _day > d._day))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator<(const Date& d) const
	{
		if ((_year < d._year)
			|| (_year == d._year && _month < d._month)
			|| (_year == d._year && _month == d._month && _day < d._day))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	int _year;
	int _month;
	int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	Date d1(2023, 5, 12);
	Date d2(2023, 5, 11);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

在这里插入图片描述
我们发现使用内置类型以及自定义date类都可以比较,但是使用指针类型比较久出错了,这是因为当使用指针类型时,比较的并不是指针指向数据的大小,而是地址的大小,那么可能比较就是会出错的。

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

函数模板特化

函数模板的特化步骤:

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

再次观察结果,就正确了。
在这里插入图片描述
注意:
一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

类模板特化

在进行类模板特化前,必须加上原有的类模板。

1.全特化

全特化即是将模板参数列表中所有的参数都确定化。

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;
}
int main()
{
	TestVector();
	return 0;
}

在这里插入图片描述

3.偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
在偏特化中又分为部分特化和类型特化(限制T*,T&等)。

部分特化:

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<class T1,class T2>
class Data
{
public:
	Data() { cout << "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }

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

我们来看一幅图分析一下:
在这里插入图片描述
由于在编译阶段,编译器没有看到模版函数的实例化,所以在a.cpp文件中的符号表中并不会加载这个函数,因为他还没有实例化,所以在后边链接,a.h文件在a.cpp中根据修饰后的函数名去找时,找不到,所以会发生链接错误。

解决方法

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

例如在实现vector的.cpp文件后显式的加上:

	template
	vector<int>;
	template
	vector<double>;

模板总结

【优点】

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

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

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

相关文章

上海亚商投顾:沪指失守3300点关口 AI应用方向大幅调整

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日集体调整&#xff0c;尾盘均跌超1%&#xff0c;沪指失守3300点关口&#xff0c;日线录得4连阴走势。游…

IS210AEPSG1AFC磁场由串联励磁绕组和并联励磁的组合提供

​ IS210AEPSG1AFC磁场由串联励磁绕组和并联励磁的组合提供 复合发生器 在这种类型中&#xff0c;磁场由串联励磁绕组和并联励磁的组合提供&#xff0c;并联磁场有许多圈数的导线&#xff0c;但它只承载小电流&#xff0c;而串联励磁绕组有几圈粗线并承载负载电流 串联励磁绕组…

【JAVAEE】线程池基础知识⭐

目录 1.什么是线程池 2.为什么要使用线程池 3.怎么使用线程池 4.自定义一个线程池 5.为什么不推荐使用系统自带的线程池 5.1线程池构造方法的参数和含义 5.1.1拒绝策略 5.2线程池的工作原理 5.3为什么不适用系统自带的线程池 补充&#xff1a;工厂模式 1.什么是线程池…

stm32 74hc595外扩io 调试记录

本文使用的测试代码如下 (1条消息) stm3274hc595外扩io调试工程&#xff0c;软件代码2片74hc595级联外扩IO资源-CSDN文库 有个项目对成本比较敏感&#xff0c;又想用ST的片子&#xff0c;输出管脚比较多&#xff0c;就考虑外扩IO&#xff0c;也有一些外扩IO的片子但是用下来成…

视频理解AI模型分类与汇总

人工智能领域视频模型大体也经历了从传统手工特征&#xff0c;到卷积神经网络、3D卷积网络、双流网络、transformer的发展脉络。 视频的技术大多借鉴图像处理技术&#xff0c;只是视频比图片多了一个时间维度。 下面内容先简单汇总下&#xff0c;后续再逐渐补充。 1. 双流网…

DS200SLCCG1AFG随着频率的增加而增加。具有高 Dk 的基材将具有高 Df。

​ DS200SLCCG1AFG随着频率的增加而增加。具有高 Dk 的基材将具有高 Df。 Df 耗散因数&#xff08;又名损耗角正切&#xff09; Df 测量由于电阻加热而损失了多少功率。PCB 基板形成电容器作为绝缘体&#xff0c;导体通常位于两侧。作为电容器&#xff0c;它会表现出容抗&#…

MySQL---聚合函数、字符串函数、数学函数、日期函数

1. 聚合函数 数据准备&#xff1a; create database mydb4; use mydb4;create table emp(emp_id int primary key auto_increment comment 编号,emp_name char(20) not null default comment 姓名,salary decimal(10,2) not null default 0 comment 工资,department char(20…

激光切割机要换新该怎么选?如何减少激光加工设备的老化问题?

激光器的销量持续攀高&#xff0c;激光切割机的应用领域涉及众多行业。随着加工范围和厚度的提升&#xff0c;运行多年的中低功率 激光切割机已经不能满足时下很多设备加工的需求&#xff0c;要提企业的加工能力&#xff0c;选择一款新的激光加工设备已是势在必行。 激光切割机…

Ubuntu使用(持续更新中)

文章目录 1.以root身份登录2.配置apt源3.安装Docker3.1BUG: 4.在VMware中克隆Ubuntu5.配置apt国内源 额 我们项目用的CentOS7,由于CentOS8停止了维护,客户反馈了这个问题,所以玩一下Ubuntu,我使用的是Ubuntu Serve LST,如有不同,另行百度 1.以root身份登录 Ubuntu默认是不开启r…

java异常的声明、捕获、抛出、处理(throws、throw、try-catch详解)

异常的处理 LBYL: Look Before You Leap. 在操作之前就做充分的检查. 即&#xff1a;事前防御型 boolean ret false; ret 登陆游戏(); if (!ret) { 处理登陆游戏错误; return; } ret 开始匹配(); if (!ret) { 处理匹配错误; return; } ret 游戏确认(); if (!ret) { 处理游…

在职读研是理想还是情怀?你想要的都将在社科大能源管理硕士项目实现

在职读研是理想还是情怀呢&#xff0c;每个读研人的想法不同&#xff0c;原因也有所不同。但选择在职继续攻读硕士学位的群体也有着共同点&#xff0c;他们都是想拥有高学历&#xff0c;拥有高目标的一群人。探寻新的起点和终点是他们想所要追求的。不管读研的初心是什么&#…

Figma怎么导出PDF文件?

Figma 是一款备受网页和 UI 设计师喜爱的基于矢量的设计工具。其最大的优势在于方便用户与团队成员进行协作。当设计师需要与其他团队成员、设计师或客户共享设计文件时&#xff0c;设计师需要将设计图案导出为 PDF 格式以方便查看。同样地&#xff0c;当设计师需要将设计图稿打…

优维低代码实践:数据模型设计

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

msvcp110.dll丢失的解决方法,msvcp110.dll一键修复教程

昨天卸载了一个垃圾软件以后&#xff0c;我的ps软件就无法打开运行&#xff0c;提示msvcp110.dll丢失&#xff0c;无法继续执行此代码。今天早上找了很多方法&#xff0c;终于把msvcp110.dll丢失的原因以及修复的方法都弄明白了。msvcp110.dll是一个非常重要的文件&#xff0c;…

【Python】【进阶篇】30、Django模板继承精讲

目录 30、Django模板继承精讲1. 模板继承的概念2. 模板继承的应用3. 父模板内容扩展(block.super)4. 总结归纳 30、Django模板继承精讲 在本节我们讲述模板语言中最后一个知识点&#xff0c;也是最重要的&#xff0c;那就是模板继承。模板继承和 Python 语言中类的继承含义是一…

敏捷专题:下一代的飞机交付

随着信息化和网络化的发展&#xff0c;航空航天领域的装备已经发展成为软件密集型系统&#xff0c;软件负责完成航空装备的大部分功能。资料显示&#xff0c;以美国的F-22战斗机为例&#xff0c;由软件实现的功能已经达到80%以上&#xff0c;航空航天领域的软件规模和重要度与日…

【MySql】数据建模

目录 一&#xff1a;概念 二&#xff1a;数据建模 三&#xff1a;E-R模型 1.1E-R模型之chen方法 1.2E-R模型之crows foot方法 1.3E-R模型之idef 1x方法 一&#xff1a;概念 现实世界---->信息世界(概念模型[E-R模型])---->计算机世界(数据模型[关系、网状和层次模型])…

20230512-VSCode-配置C++17-win11-22h2

20230512-VSCode-配置C17-win11-22h2 一、软件环境 标签&#xff1a;C VSCode w64devkit gcc12分栏&#xff1a;C操作系统&#xff1a;Windows11 x64 22h2编译器&#xff1a;vscode-1.78.2 w64devkit-1.18.0 二、操作步骤 1. 下载安装VScode 官网 打开官网【https://code…

深度学习笔记——数值稳定性、模型初始化与激活函数

机器学习笔记——数值稳定性、模型初始化与激活函数 引言数值稳定性——梯度爆炸小插曲&#xff1a;关于对角阵 Diag [ σ ′ ( W t ⋅ h t − 1 ) ] \text{Diag}[\sigma(\mathcal W_t \cdot h_{t-1})] Diag[σ′(Wt​⋅ht−1​)]的解释梯度爆炸的问题 如何让参数更新更加稳定方…

移动机器人运动规划---基于图搜索的基础知识---配置空间

配置空间 机器人规划的配置空间概念&#xff1a;一个空间包含所有机器人自由度的机器人配置&#xff0c;描述为C-space 机器人配置&#xff1a;表示对机器人上面所以点的位置的描述机器人自由度&#xff1a;规划的时候用最少的坐标数量去表示机器人配置&#xff0c;例如无人机…