模板进阶 | 非类型模板参数 | 类模板的特化 | 模板的分离编译 | 模板的优缺点

  非类型模板参数

我们可以认为非类型模板参数就是一个常量,在我们的类里面我们是不能对它进行改造

为什么会有这样的场景,其次就是C语言那里我们一般使用什么。

场景1

#include<iostream>
using namespace std;

#define N 10
template<class T>
class Array
{
public:
	T& operator()(size_t index)
	{
		return _arr[index];
	}
	const T& operator[](size_t index)
	{
		return _arr[index];
	}
	size_t size()
	{
		return _size;
	}
	bool empty()
	{
		return _size == 0;
	}

private:
	T _arr[N];
	size_t _size;

};

int main()
{
	Array<int> a;

	return 0;
}

 在我们上面这个场景中,我们要开辟这样一个数组,通过宏来控制我们静态数组的大小,这个也是C语言经常使用的场景,但是假如我们需要开辟另一个空间很大的数组,现在面对这种情况有两种方法,一种是改变宏的大小,这样就可以改变了,但是这个方法还是有一定的缺陷,比如我们一个数组的空间需要很大的时候,但是这个时候还有一个数组的空间是很小的时候,那这个时候两边都是需要进行满足的,所以就会在存在有一个数组的空间是会存在浪费的,第二个方法就是我们在写一个模板,需要一个数组就去再写一个模板,ctrl c + ctrl v

就能解决

所以面对上面的问题,我们也是有两种选法,但是都不能从根本上解决问题,所以就有了我们现在C++用到的非类型模板参数可以解决

改变代码

template<class T,size_t N = 10>
class Array
{
public:
	T& operator()(size_t index)
	{
		return _arr[index];
	}
	const T& operator[](size_t index)
	{
		return _arr[index];
	}
	size_t size()
	{
		return _size;
	}
	bool empty()
	{
		return _size == 0;
	}

private:
	T _arr[N];
	size_t _size;

};

int main()
{
	Array<int> a;
	Array<int,20> b;

	return 0;
}

这样就可以解决我们的问题,这里需要记住的是非类型模板参数其实是个常量,而且必须是整型

总结使用场景

我们需要定义一个静态数组的时候就可以是用的到,我们的库里面其实就是有一个array,但是实际上非类型模板参数其实是苦了我们的编译器,我们的模板这样写之后,编译器就能根据你的需求去进行实例化              

                                                                                   --------很像到家思想的 死道友不死贫道

注意: 当前编译器只支持整型,非类型模板也是支持缺省参数(类型也是支持的,比如优先级队列就是这样的)

区别函数传参数和模板传参数

 函数传参数

是我们在运行的时候来确定参数的,就比如我们之前写的一些函数,这里用 add函数来说

int Add(int x, int y)
{
	return x + y;
}

比如在这个场景下我们要进行函数传参的时候,如果我们不去调用其实就没有传参数的过程,所以也就是只有在我们调用的时候才是会出现传参数

模板传参数

模板传参数就是在我们实例化的时候会进行,虽然有我们的按需实例化(后面讲),但是模板传参数的时候一定是在我们编译的时候编译器就是得知道的事

array真的很好吗

上面讲的array我们库里面其实是有的,但是array这个东西真的有这么好用吗,其实不是的,我们的vector可以完全替代array,array这个类是有点鸡肋的功能,而且他是静态的数组,在我们进行扩容的时候或者空间满的时候就不能进行调整了,所以array煤油vector好

但是这个发明肯定不是区别于我们的vector,是区别于我们的普通数组,因为它的检查更加严格,在我们的数组的时候对越界的检查其实是抽查,就和酒驾是一样的,而且越界读一般是不能检查出来的,越界写是抽查,但是array这些越界访问的操作都是能检查出来, 这就是array的唯一的优点了。但是还是想吐槽它的优点太有限了,我们vector也能进行检查访问,因为检查其实就是一个断言而已,这样就是一个小小的函数调用而已。

它还有缺陷 : 会导致栈溢出,因为我们栈的空间不是很大,堆的空间才很大。所以我们能用vector绝对不用array

按需实例化

演示按需实例化


template<class T,size_t N = 10>
class Array
{
public:
	T& operator()(size_t index)
	{

		return _arr[index];
	}
	const T& operator[](size_t index)
	{
		size(1);//有问题
		return _arr[index];
	}
	size_t size()
	{
		return _size;
	}
	bool empty()
	{
		return _size == 0;
	}

private:
	T _arr[N];
	size_t _size;

};


int Add(int x, int y)
{
	return x + y;
}
int main()
{
	Array<int> a;
	Array<int,20> b;

	return 0;
}

这个地方我注释的地方语法是不是有问题的,但是我们如果编译的时候是没有报错误的。

我们模板在预处理的时候和编译过程中增加了一个环节 

根据模板实例化 - > 半成品模板 - > 实例化具体的类或者函数 - > 语法进行编译

所以我们可以猜测一下这里我们应该是 没有进行实例化!!!

哪怕我们这里写了实例化,也是没有检查出这个语法,所以结论就是我们的编译器是按需实例化的

也就是我们的成员函数调用哪个就进行哪个的实例化,我们这里没有调用这个operator[]函数,所以就不会进行报错,因为我们的编译就没有对他进行调用,那只有我们去调用的时候就会去实例化,这样就能检查出来这个问题了。

 模板的特化

函数模板的特化

场景1: 如果我们要对我们的日期进行比较大小 所以需要先有一个日期类的实现的代码

class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);

	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);
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

如果正常比较日期的话我们的代码是没有问题的,比如比较两个日期的大侠

class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);

	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);
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

// 函数模板
template<class T>
bool Less(T left, T right)
{
	cout << "bool Less(T left, T right)" << endl;

	return left < right;
}
int main()
{
	Date d1(2024, 1, 1);
	Date d2(2023, 2, 2);
	cout << Less(d1, d2) << endl;
	return 0;
}

这样我们就是可以正常的进行比较的,但是现在我们给出场景2,如果遇到的是Date* 的指针呢,我们这个时候就是需要模板的特化

场景2 ------ 引出特化

我们现在要进行比较的是我们的Date* 指针的大小,我们可以写一个这样的模板解决。

class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);

	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);
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

// 函数模板
template<class T>
bool Less(T left, T right)
{
	cout << "bool Less(T left, T right)" << endl;

	return left < right;
}
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}
int main()
{
	Date d1(2024, 1, 1);
	Date d2(2023, 2, 2);
	cout << Less(d1, d2) << endl;
	Date* pd1 = new Date(2023, 12, 1);
	Date* pd2 = new Date(2023, 12, 3);
	cout << Less(pd1, pd2) << endl;
	return 0;
}

这样就对我们的Date* 进行比较了。

但是这个只是特化的最基础的使用。这是对于某种特殊类型的特殊处理

场景3

我们上面只是解决了我们Date* 的比较,如果我们还是需要对我们所有的指针进行比较的时候,我们的代码需要怎么写呢????

template<class T>
bool Less(T* left, T* right)
{
	return *left < *right;
}

只需要这样写就能解决,这样可以测试不同的指针了。

注意 : 上面写的都是全特化 ,建议 : 函数模板不建议使用特化,使用重载能解决大部分的问题。

类模板的特化

全特化

template<class T1, class T2>
class Date
{

public:
	Date()
	{
		cout << "Date<T1,T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};


//全透化

template<>
class Date<int, char>
{
public:
	Date()
	{
		cout << "Date<int,char>" << endl;
	}
};
int main()
{
	Date<int, char> d1;
	Date<int, int> d2;
	return 0;
}

其实看代码就是能看出来,我们给出了他们的具体类型,这个就是全透化。

半特化/偏特化
template<class T1>
class Date<T1, char>
{
public:
	Date()
	{
		cout << "Date<T1,char>" << endl;
	}
};

上面的这个就是我们的特化的全部语法,但是要知道半特化不一定是特化部分的参数,只是对一些参数的限制,我们这里也能使用指针和引用(list的迭代和反向迭代器就是这样的)。就不演示了

template<class T>
class Date<T*,T*>
{
public:
	bool operator()(T* x, T* y)
	{
		return *x < *y;
	}
};

总结 : 想针对某种类型进行特殊处理的时候就可以考虑使用特化。

模板的分离编译

模板是不支持声明和定义分离进行编译的,因为我们的编译器是不知道我们需要实例化成什么类,这里不支持分离编译是指在两个不同的文件下,而不是在同一个文件,我们来举出一个例子,然后进行分析,首先就是我们需要创建一个头文件,一个就是Fun.h,然后就是fun.cpp,还有就是test.cpp

Fun.h

#pragma once

#include<iostream>
using namespace std;

template<class T>
T Add(const T& x, const T& y);


void Fun();

fun.cpp


#include"Fun.h"

template<class T>
T Add(const T& x, const T& y)
{
	return x + y;
}

void Fun()
{
	
	cout << "void Fun()" << endl;
	
}

test.cpp

     

#include"Fun.h"


int main()
{
	int ret = Add(1, 3);

	return 0;
}

   然后进行编译就会出现这样的报错信息

但是我们的普通函数就是可以通过,而且要注意这里的报错不是编译问题,而是链接问题

分析程序在预处理,编译,汇编以及链接做的事情

 

预处理

预处理这一部分需要做的是条件编译,宏替换,去注释,头文件展开,所以我们的每个.cpp文件里的头文件都会进行展开,因为编译器是不会对我们写的.h文件进行编译的。

编译

编译这一步很重要,会进行的事情很多,比如要语法分析,语意分析,还会生成语法树,就是在检出我们写的代码语法是不是正确,最后就是生成汇编代码。

汇编

生成二进制代码,形成目标文件

链接

合并生成可执行文件

注意: 这里只是讲个大概,具体的之前的文章也是讲过的。

两个文件都进行编译之后生成的目标文件,然后进行链接之后,我们的fun函数是有它的地址的,这个地址相当于一个跳转的指令,和我们之前所得call地址可以认为是一样的,fun函数是能成功生生call地址的,但是我们的函数模板,它会按需实例化,我们也不知道T要变成什么类型,这也就导致了最后链接时候找不到地址,所以才会链接错误

解决办法

1.模板的声明和定义放在同一个头文件,不要进行分离编译

2.声明类型,让编译器能实例化。也就是显示实例化

结论就是我们还是最好模板的声明和定义放在一起,这才是最好的解决方法。

模板的优缺点

【优点】
1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
今天的分享就到这里,下次分享的是oo语言的三大特性之一继承

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

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

相关文章

uniapp开发小程序手写板、签名、签字

可以使用这个插件进行操作 手写板-签名签字-lime-signature - DCloud 插件市场 但是目前这个插件没有vue3 setup Composition API的写法。所以对于此文档提供的可以直接使用,需要使用Composition API方式实现的,可以继续看。 因为Composition API方式,更加的简单、灵活,…

2024 CleanMyMac X 优化储存苹果电脑空间 的好帮手

在数字时代&#xff0c;我们的Mac设备承载着越来越多的重要信息和日常任务。然而&#xff0c;随着时间的推移&#xff0c;这些设备可能会变得缓慢、混乱&#xff0c;甚至充满不必要的文件。这就是CleanMyMac X发挥作用的地方。 CleanMyMac X是一款功能强大的Mac优化工具&#…

基于java+springboot+vue实现的药品管理系统(文末源码+Lw)23-297

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;药品信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

动态规划原理及其在优化问题中的应用解析

动态规划原理及其在优化问题中的应用解析 一、最优子结构二、重叠子问题三、何时使用动态规划法四、伪代码示例五、C代码示例七、详细说明动态规划原理7.1、最优子结构7.2 重叠子问题7.3 动态规划的实现 八、结论 动态规划是一种解决优化问题的方法&#xff0c;它通过将原问题分…

Triton Server Python 后端优化

接上文 不使用 Docker 构建 Triton 服务器并在 Google Colab 平台上部署 HuggingFace 模型 MultiGPU && Multi Instance Config 追加 instance_group [{count: 4kind: KIND_GPUgpus: [ 0, 1 ]} ]Python Backend Triton 会根据配置信息启动四个实例&#xff0c;…

win10系统中exe文件打不开

问题描述 昨天下载了某个驱动安装程序之后&#xff0c;点击.exe文件没有反应。 解决方法 1. 开启兼容模式运行 右键点击属性 点击【兼容性】&#xff0c;并且【以兼容模式运行程序】 2. 给exe文件换个文件夹再次尝试 我使用第一个方法没有用&#xff0c;之后尝试了把文…

Eureka-搭建Eureka步骤

简介&#xff1a; Eureka是Netflix开发的服务发现框架&#xff0c;本身是一个基于REST的服务&#xff0c;主要用于定位运行在AWS域中的中间层服务&#xff0c;以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中&#xff0c;以实…

转让北京100万旅行社带国内旅行社许可证条件和要求

旅行社的主要分类为国际旅行社和国内旅行社两类。国际旅行社拥有更为广泛的经营范围&#xff0c;不仅涵盖国内旅游业务&#xff0c;还包括出境旅游业务以及入境旅游业务&#xff1b;相比之下&#xff0c;国内旅行社则专注于国内旅游市场以及入境旅游业务。当前情况下&#xff0…

Pandas部分应掌握的重要知识点

目录 Pandas部分应掌握的重要知识点一、DataFrame数据框的创建1、直接基于二维数据创建&#xff08;同时使用index和columns参数&#xff09;2、基于excel文件中的数据来创建 二、查看数据框中的数据和联机帮助信息1、查看特殊行的数据2、查看联机帮助的两种常见方法&#xff0…

MDK平台 - Code, RO-data , RW-data, ZI-data详解

文章目录 1 . 前言2 . Code, RO-data , RW-data, ZI-data解析3 . RAM上电复位4 . 细节扩展5 . 总结 【全文大纲】 : https://blog.csdn.net/Engineer_LU/article/details/135149485 1 . 前言 MDK编译后&#xff0c;会列出Code, RO-data , RW-data, ZI-data&#xff0c;以下解析…

2024年会计、审计、财务与经济管理国际会议(ICAAFEM2024)

2024年会计、审计、财务与经济管理国际会议&#xff08;ICAAFEM2024&#xff09; 会议简介 2024年国际会计、审计、财务和经济管理会议&#xff08;ICAAFEM2024&#xff09;将在云南省昆明市举行。会议旨在为从事“会计、审计、财务、经济管理”研究的专家学者提供一个平台&am…

java快速幂算法

快速幂算法 参考视频(参考五角七边up大佬&#xff09; 幂运算的介绍 幂运算是指将一个数自身乘以自身多次的运算&#xff0c;其表达式为 a n a^n an&#xff0c;其中 a a a 是底数&#xff0c; n n n 是指数。 快速幂解释 快速幂算法是一种用于快速计算幂运算的算法&…

easyui combobox下拉框组件输入检索全模糊查询

前引&#xff1a; easyui下拉组件&#xff08;combobox&#xff09;&#xff0c;输入检索下拉内容&#xff0c;是默认的右模糊匹配&#xff0c;而且不支持选择。因业务要求需要做成全模糊查询&#xff0c;目前网上搜索有两种方案&#xff1a; 1.修改easyui源码&#xff0c;这个…

Windows联网状态工具TCPView

文章目录 TCPView命令行工具更多Sysinternals Suite工具 TCPView TCPView用于显示系统上所有 TCP 和 UDP 终结点的详细列表&#xff0c;包括本地和远程地址以及 TCP 连接的状态&#xff0c;界面如下。 列表的表头含义如下 表头含义表头含义Process name应用名称Process id进程…

STM32H7各块RAM的位置和作用

STM32H7各块RAM的位置和作用 RAM各块RAM的特性各块RAM的时钟问题RAM分配方案 摘抄于armfly-V7开发板bsp手册&#xff0c;仅供个人学习。 RAM 这个图可以方便识别总线所外挂的外设&#xff0c;共分为三个域&#xff1a;D1 Domain&#xff0c;D2 Domain 和 D3 Domain。 ◆ ITCM 和…

【Hello算法】 > 第 2 关 >数据结构 之 数组与链表

数据结构 之 数组与链表 1&#xff1a;Understanding data structures &#xff01;——了解数据结构——1.1&#xff1a;Classification-分类-1.2&#xff1a;Type-类型- 2&#xff1a;Arrays are the bricks that make up the wall of data structures *——数组是组成数据结…

【Java核心能力】美团优选后端一面:Java 八股文相关内容

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

基于分布式鲁棒性的多微网电氢混合储能容量优化配置——1

Optimal configuration of multi microgrid electric hydrogen hybrid energy storage capacity based on distributed robustness A B S T R A C T 储能与微电网相结合是解决分布式风能、太阳能资源不确定性、降低其对大电网安全稳定影响的重要技术路径。随着分布式风电和太阳…

Web自动化测试进阶:网页中难点之等待机制 —— 强制等待,隐式等待

为什么要添加等待 避免页面未渲染完成后操作&#xff0c;导致的报错 经常会遇到报错&#xff1a;selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":&q…

架构设计参考项目系列主题:新零售SaaS架构:客户管理系统架构设计

什么是客户管理系统? 客户管理系统,也称为CRM(Customer Relationship Management),主要目标是建立、发展和维护好客户关系。 CRM系统围绕客户全生命周期的管理,吸引和留存客户,实现缩短销售周期、降低销售成本、增加销售收入的目的,从而提高企业的盈利能力和竞争力。 …