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

news2024/11/25 10:00:29

  非类型模板参数

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

为什么会有这样的场景,其次就是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语言的三大特性之一继承

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

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

相关文章

基于springboot仿雀语的文档管理系统

项目介绍 本项目借鉴了雀语的一些UI设计&#xff0c;实现了文档在线管理的功能&#xff0c;知识库可以对不同分类的文档建立不同的库&#xff0c;知识库里面左边可以维护菜单菜单目录&#xff0c;右边实现在线预览。该项目可以防止用户下载和复制文档&#xff0c;只支持在线预…

RK3568平台 SPI设备驱动

一.SPI简介 SPI是许多不同设备使用的常见通信协议。例如&#xff0c;SD卡模块、RFID读卡器模块和2.4GHz无线发射机/接收器均使用SPI与微控制器进行通信。 SPI是串行外设接口&#xff08;Serial Peripheral Interface)的缩写&#xff0c;是一种高速的&#xff0c;全双工&#x…

短视频转gif怎么做?三十秒在线转换gif

在现在这个快节奏的时代&#xff0c;gif动画相较于长时间的视频更受大众的欢迎。当我们需要将短视频、电影等视频制作成gif动画图片的时候就可以使用gif动画图片&#xff08;https://www.gif.cn/&#xff09;制作网站-GIF中文网&#xff0c;无需下载软件&#xff0c;手机、pc均…

零售行业数字化广告评价标准 - 《IAB/MRC零售(广告)测量指南》

IAB/MRC零售&#xff08;广告&#xff09;测量指南 --- 最新标准&#xff0c;2024年1月发布 目录 1出台此标准的目的是什么&#xff1f;2标准宗旨3本标准的主要关键领域4为什么这对品牌和零售商很重要5能给零售媒体中小型玩家带来什么机会&#xff1f;6评价零售媒体效果的最…

【JS】获取接口返回 EventStream 结构的数据(即接收读取 stream 流)

文章目录 EventStream 是一种服务器推送的数据格式&#xff0c;可以用于实时数据传输。 接口返回的示例图 获取示例&#xff1a; // 这里的 url 为虚拟的&#xff0c;仅供演示用 fetch(https://test.cn.com/api/agent/2, {method: POST,headers: {Content-Type: applicatio…

EaticSearch学习

ES学习目标 1、全文检索 2、ES介绍 2.1 安装&#xff08;docker&#xff09; docker pull elasticsearch:7.14.0 docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" elasticsearch:7.14.0初步检索 1、_cat GET /_cat/nodes&#xff1a;查看所…

RocketMQ安装部署+简单实战开发

文章目录 1.简介、安装部署2.Springboot集成RocketMQ2.1.添加maven依赖&#xff1a;2.2.RocketMQ配置生产者配置消费者配置 2.3.生产者&#xff08;发送消息&#xff09;2.4.消费者&#xff08;接收消息&#xff09; 3.实战结果3.1.消费者服务3.2.生产者服务3.3.运行日志生产日…

【SpringBoot】-- 使用minio对象存储服务实现上传图片

目录 一、安装minio 拉取镜像 启动 查看 进入登录页面 创建bucket 二、安装miniomc 三、代码 application.yml MinioUtil Controller 四、拓展 以下基于云服务和docker使用minio服务 一、安装minio Minio 是一个开源的对象存储服务器。它允许用户在私有云环境中建…

【vue】watch 侦听器

watch&#xff1a;可监听值的变化&#xff0c;旧值和新值 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><titl…

蓝桥杯嵌入式(G431)备赛——最后一晚查漏补缺

蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——初始化cubeMX 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——LED 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——按键模块设计 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——LCD按键 蓝桥杯…

2023年度编程语言将花落谁家

2023年度编程语言将花落谁家 TIOBE的预测你预测年度最受欢迎的编程语言会是什么&#xff1f;TIOBE 认为 C# 最有可能成为年度编程语言&#xff0c;你同意吗&#xff1f;为什么&#xff1f;AI时代已经到来&#xff0c;你有学习新语言的打算吗&#xff1f; 以下是来自年度编程语言…

Android 纵向双选日历

这个日历的布局分两部分&#xff0c;一部分是显示星期几的LinearLayout&#xff0c;另外就是一个RecyclerView&#xff0c;负责纵向滚动了。 工具类&#xff1a; implementation com.blankj:utilcode:1.17.3上activity_calendar代码&#xff1a; <?xml version"1.0&…

Springboot+Vue项目-基于Java+MySQL的在线视频教育平台系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

中仕公考:非师范生能考教师编吗?

非师范生能考教师编吗?关于这个问题&#xff0c;首要条件是必须持有教师资格证。只要符合招聘条件&#xff0c;非师范专业背景的考生同样有资格报名参加教师编制考试。具体要求如下&#xff1a; 1. 年龄要求&#xff1a;申请人需年满35岁以下&#xff0c;对于特定职位&#x…

Elastic 线下 Meetup 将于 2024 年 4 月 27 号在重庆举办

2024 Elastic Meetup 重庆站活动&#xff0c;由 Elastic、新智锦绣联合举办&#xff0c;现诚邀广大技术爱好者及开发者参加。 活动时间 2024年4月27日 13:30-18:00 活动地点 中国重庆 沙坪坝区学城大道62-1号研发楼一期b3栋1楼(瑞幸咖啡旁&#xff09; 活动流程 14:00-14:50…

多因子模型的数据处理

优质博文&#xff1a;IT-BLOG-CN 数据处理的基本目的是从多量的、可能是杂乱无章的、难以理解的数据中抽取并推导出有价值、有意义的数据。特别是金融数据&#xff0c;存在数据缺失&#xff0c;不完整以及极端异常值等问题&#xff0c;对于我们的分析和建模影响很多。 对于我…

【新版HI3559AV100开发注意事项(四)】

新版HI3559AV100开发注意事项&#xff08;四&#xff09; 三十、HI3559A参数中对输入分辨率限制的原因是&#xff1f; 答&#xff1a;分辨率限制有两个来源&#xff1a; 一个是时钟频率最高为600M&#xff0c;开启一拍两像素之后相当于1200M。你这个数据量太大了&#xff0c;6…

(学习日记)2024.04.17:UCOSIII第四十五节:中断管理

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

微信小程序开发学习笔记——4.9【小案例】开启下拉刷新页面enablePullDownRefresh

>>跟着b站up主“咸虾米_”学习微信小程序开发中&#xff0c;把学习记录存到这方便后续查找。 课程连接&#xff1a;4.9.【小案例】开启下拉刷新页面enablePullDownRefresh_哔哩哔哩_bilibili 一、api2.json 小程序配置 / 页面配置 (qq.com) {"usingComponents&q…

抖音小店是什么?个人店、个体店、企业店,新手商家该如何选择?

大家好&#xff0c;我是电商花花。 抖音小店这两年来说都是一个发展不错的电商项目&#xff0c;凭借着直播电商的快速发展&#xff0c;让更多人看到了抖音小店其中的红利&#xff0c;吸引着商家入驻。 抖音小店是什么&#xff1f; 很多人会把抖音小店和达人橱窗搞混&#xff…