【ONE·C++ || 模板进阶】

news2024/9/23 7:26:59

总言

  主要介绍模板相关内容:非类型模板参数、类模板特化、模板的分离编译。

文章目录

  • 总言
  • 1、非类型模板参数
    • 1.1、主要介绍
    • 1.2、std::array 简要说明
  • 2、模板的特化
    • 2.1、基本介绍
    • 2.2、函数模板特化
    • 2.3、类模板特化
      • 2.3.1、基本说明
      • 2.3.2、用途举例
      • 2.3.3、分类:全特化、偏特化
  • 3、模板的分离编译

  
  

1、非类型模板参数

1.1、主要介绍

  1)、问题引入
  在之前,我们已经对模板有一定了解:

	#define N 5
	template<class T>
	class Array
	{
	private:
		T _a[N];
	};

int main()
{
	Array<int>  a1;
	Array<double> a2;

	return 0;
}

在这里插入图片描述
  根据上述情况,我们能使用模板定义出两个类型不同的类,但是,假如我们需要a1大小为10,a2大小为8,该如何定义呢?
  
  这是我们就需要非类型模板参数。
  
  
  2)、非类型模板参数介绍
  模板参数分类类型形参与非类型形参。
  类型形参:出现在模板参数列表中,跟在class或者typename之后的参数类型名称。
  非类型形参:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
  
  举例如下:此处的size_t N即非类型模板参数。

template<class T, size_t N>
class Array
{
private:
	T _a[N];
};


int main()
{
	Array<int,10>  a1;
	Array<double,8> a2;

	return 0;
}

在这里插入图片描述

  注意事项:
  1、非类型模板参数只能是常量,因此其限制了变长数组的使用。
  2、非类型模板参数也可以使用缺省值。

template<class T, size_t N=5>
struct Array
{
	T _a[N];
};

int main()
{
	Array<int>  a1;
	Array<double,8> a2;

	return 0;
}

在这里插入图片描述

  3、非类型模板参数限制为整型(包含char),浮点数、类对象以及字符串是不允许作为非类型模板参数的。
在这里插入图片描述

  4、非类型的模板参数必须在编译期就能确认结果。
  
  
  

1.2、std::array 简要说明

  事实上,库里也有一个array:相关链接
在这里插入图片描述
  其相关使用和数组一致,细微之处在于多了迭代器的各接口。那么,有一个问题:既然有了数组,为什么还要单独创建一个array的类?

	Array<int>  a1;
	int arr[5];

在这里插入图片描述

  实际上,主要的区别在于:对越界的检查。
  Array<int> a1;:属于函数调用,只要越界,就能检查到。
  int arr[5];:属于指针解引用 ,其越界检查属于设岗抽查,且只针对越界写,越界读不检查。
  
  

2、模板的特化

2.1、基本介绍

  1)、问题引入
  如下,我们写一个Less函数模板,用于比较不同类型大小:

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

int main()
{
	// 可以比较,结果正确
	cout <<"Less(1, 2):" << Less(1, 2) << endl;

	// 可以比较,结果正确
	Date d1(2023, 4, 29);
	Date d2(2023, 6, 19);
	cout << "Less(d1, d2):" << Less(d1, d2) << endl;

	// 可以比较,结果错误
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << "Less(p1, p2):" << Less(p1, p2) << endl;

	return 0;
}

  可以发现:Less适用于绝对多数场景,但是在特殊场景下会得到错误的结果。如上述Less(p1, p2),对此分析,这是因为p1p2为指针类型,指向的是Date对象的地址,我们期望Less函数内部比较的是p1p2指向的对象内容(d1d2),但实际比较的是p1p2指针的地址。在这里插入图片描述
  针对上述情况,就需要用到模板特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。
  

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

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

2.2、函数模板特化

  1)、使用说明

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

  仍旧是上述例子,我们对Less函数模板进行特化处理:

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

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

  结果如下:
在这里插入图片描述

  函数模板的特化步骤:
  1、必须要先有一个基础的函数模板
  2、关键字template后面接一对空的尖括号<>
  3、函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4.、函数形参表:必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
  
  当然我们也可直接使用非模板函数:(这里有一个模板参数的匹配原则,相关内容见模板初阶章节)

bool Less(Date* left, Date* right)
{
	return *left < *right;
}

  
  
  

2.3、类模板特化

2.3.1、基本说明

  除了函数模板特化,类模板也可以根据需求进行特化处理,以下为相关演示:

namespace myless
{
	//类模板
	template<class T>
	struct less
	{
		bool operator()(const T& val1, const T& val2)
		{
			return val1 < val2;
		}
	};
}

void test06()
{
	Date d1(2023, 4, 29);
	Date d2(2023, 6, 19);
	myless::less<Date> lessFun1;
	cout << lessFun1(d1, d2) << endl;

	Date* p1 = &d1;
	Date* p2 = &d2;
	myless::less<Date*> lessFun2;
	cout << lessFun2(p1, p2) << endl;
}

在这里插入图片描述

  
  同样定义一个类模板,当我们传入参数不同时,存在场景使用错误,因此类模板中也需要特化处理。

namespace myless
{
	//类模板
	template<class T>
	struct less
	{
		bool operator()(const T& val1, const T& val2)
		{
			return val1 < val2;
		}
	};

	template<>//注意模板特化需要处理的地方
	struct less<Date*>//
	{
		bool operator()(Date* val1, Date* val2)//
		{
			return *val1 < *val2;
		}
	};

}

在这里插入图片描述
  
  
  

2.3.2、用途举例

  我们以优先级队列来举例演示:
  分别用date类构建两个优先级队列,根据之前所学,优先级队列实则是以堆排序数据的,因此我们将相同的date数据传入优先级队列中,再分别拿出打印:

#include<queue>
void test07()
{
	std::priority_queue<Date,vector<Date>,myless::less<Date>> pq1;
	std::priority_queue<Date*, vector<Date*>, myless::less<Date*>> pq2;
	

	pq1.push(Date(2023, 4, 29));pq1.push(Date(2023, 6, 19));pq1.push(Date(2023, 3, 07));pq1.push(Date(2023, 4, 20));
	pq1.push(Date(2023, 9, 18));pq1.push(Date(2023, 7, 11));pq1.push(Date(2023, 8, 24));
	while (!pq1.empty())
	{
		cout << pq1.top();
		pq1.pop();
	}
	
	cout << "________________________________________________" << endl;

	pq2.push(new Date(2023, 4, 29)); pq2.push(new Date(2023, 6, 19)); pq2.push(new Date(2023, 3, 07)); pq2.push(new Date(2023, 4, 20));
	pq2.push(new Date(2023, 9, 18)); pq2.push(new Date(2023, 7, 11)); pq2.push(new Date(2023, 8, 24));
	while (!pq2.empty())
	{
		cout << *(pq2.top());
		pq2.pop();
	}
}

  结果如下:可看到,在没有对模板进行特化处理时,以Date*构建出的优先级队列pq2,其结果非按照大堆排序,实则排序的是new出来的地址空间。
在这里插入图片描述

namespace myless
{
	//类模板
	template<class T>
	struct less
	{
		bool operator()(const T& val1, const T& val2)
		{
			return val1 < val2;
		}
	};

	//template<>
	//struct less<Date*>
	//{
	//	bool operator()(Date* val1, Date* val2)
	//	{
	//		return *val1 < *val2;
	//	}
	//};

}

  
  
  

2.3.3、分类:全特化、偏特化

  1)、全特化、偏特化举例
  以下述类模板举例:

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

  偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
  偏特化演示一:部分特化,将模板参数类表中的一部分参数特化。

template<class T1>//注意偏特化中,这里模板参数需要给出
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>" << endl; }
};

  偏特化演示二:偏特化不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

template<class T1,class T2>
class Data<T1*, T2*>//这也是偏特化的一种,这里限制了参数必须是指针类型
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
};

  相关结果:
在这里插入图片描述

  

template<class T1, class T2>
class Data<T1&, T2&>//模板参数还可以是引用
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
};

template<class T1, class T2>
class Data<T1*, T2&>//也可以是二者结合
{
public:
	Data() { cout << "Data<T1*, T2&>" << endl; }
};

template<class T1, class T2>
class Data<T1, T2&>//也可以是二者结合
{
public:
	Data() { cout << "Data<T1, T2&>" << endl; }
};

在这里插入图片描述

  
  

3、模板的分离编译

  1)、问题说明
  我们以vector来举例说明:

//vector.h文件
namespace myvector
{
	template<class T>
	class vector
	{
	public:
		//这里为了观察省去一部分成员函数
		//……
		
		//尾删数据
		void pop_back()
		{
			assert(_finish > _start);
			_finish--;
		}

		iterator insert(iterator pos, const T& val);
		void push_back(const T& val);


	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

  此处以insertpush_back来举例说明,我们将其在类中声明,在类外定义:

  注意这里的各种写法:
  1、vector<T>::push_backvector<T>::insert 类外使用,要注意指定类域,如果不加命名空间,则为myvector::vector<T>::push_back
  2、typename vector<T>::iterator,加上该关键字是为了区别后面iterator是类中的一个类型,而非静态类成员,因为静态类成员也可以使用类域直接访问。

//vector.cpp文件
namespace myvector
{
	template<class T>
	typename vector<T>::iterator typename vector<T>::insert(typename vector<T>::iterator pos, const T& val)//vector<T>::iterator、vector<T>::insert 类外使用,要注意指定类域
	{
		assert(pos >= _start && pos <= _finish);

		if (_finish == _end_of_storage)
		{
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			pos = _start + len;
		}

		iterator end = _finish - 1;
		while (end >= pos)
		{
			*(end + 1) = *end;
			--end;
		}
		*pos = val;
		++_finish;
		return pos;
	}

	template<class T>
	void vector<T>::push_back(const T& val)
	{
		//检查
		if (_finish == _end_of_storage)
		{
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}
		//插入
		*_finish = val;
		++_finish;
	}
}


  
  以下为测试代码:

//test.cpp
#include"vector.h"
void test09()
{
	myvector::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	for (size_t i = 0; i < v1.size(); ++i)
	{
		cout << v1[i] << " ";
	}
}

  我们运行上述代码:可发现结果报错,且是链接错误。但当我们没使用insert、push_back声明和定义分离的这两个函数时,成功运行。
在这里插入图片描述

  原因解释:
  1、test.cpp文件中包含了头文件vector.h,头文件在编译阶段会展开,而故头文件中定义的函数operator[]、size等,后续vector<int> v1实例化时,这些成员函数都跟随实例化,也就有了具体定义,那么编译阶段能够直接确定地址。

  2、insert、push_back声明和定义分离,vector.h中只有二者声明,而test.cpp中我们使用这两个函数,那么即使头文件被展开,在编译阶段我们没有得到二者的确切地址,故只能在链接阶段所有.obj文件汇总后去寻找相关地址。

  3、但我们得到的结果是报错,说明链接阶段没有找到insert、push_back的地址,这是因为声明定义分离后,其中模板参数T无法确定,即二者没有实例化,相应地址也就没有进入符号表,故链接出错。

  
  2)、解决方案
  关于模板声明定义分离解决方案:
  1、将声明和定义放到同一个文件里,比如 “xxx.hpp” 或者xxx.h,在该基础上,类里声明,类外实现函数具体方法。

  2、模板定义的位置显式实例化,如下:这种写法存在的一个缺陷是把类型写死了。

//vector.cpp文件
namespace myvector
{
	template<class T>
	typename vector<T>::iterator typename vector<T>::insert(typename vector<T>::iterator pos, const T& val)//vector<T>::iterator、vector<T>::insert 类外使用,要注意指定类域
	{
		assert(pos >= _start && pos <= _finish);

		if (_finish == _end_of_storage)
		{
			size_t len = pos - _start;
			reserve(capacity() == 0 ? 4 : capacity() * 2);
			pos = _start + len;
		}

		iterator end = _finish - 1;
		while (end >= pos)
		{
			*(end + 1) = *end;
			--end;
		}
		*pos = val;
		++_finish;
		return pos;
	}

	template<class T>
	void vector<T>::push_back(const T& val)
	{
		//检查
		if (_finish == _end_of_storage)
		{
			reserve(capacity() == 0 ? 4 : capacity() * 2);
		}
		//插入
		*_finish = val;
		++_finish;
	}

	//针对整个类进行显示实例化的的方法:
	template
	vector<int>;

	template
	vector<double>;
}

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

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

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

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

相关文章

统信UOS 20 安装达梦数据库V8

统信UOS 20 安装达梦数据库V8 1、安装教程2、启动数据库实例服务失败解决方法3、使用dm管理工具连接数据库 1、安装教程 https://blog.csdn.net/OceanWaves1993/article/details/129936878 此教程进行到启动数据库实例步骤时 使用下面命令启动数据库实例服务时&#xff0c;报…

找高清图片素材,这8个网站就够了

相信很多设计师、自媒体都为找素材而烦恼&#xff0c;很多朋友不知道去哪里找图片素材&#xff0c;找到了版权还不明确&#xff0c;怕造成侵权&#xff0c;今天我就把我独家珍藏的8个图片素材网站分享给大家&#xff0c;免费下载&#xff0c;还可以商用&#xff0c;建议收藏起来…

APIs -- DOM浏览器

1. Window对象 1.1 BOM&#xff08;浏览器对象模型&#xff09; BOM(Browser Object Model)是浏览器对象模型 window对象是一个全局对象&#xff0c;也可以说是JavaScript中的顶级对象像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方…

crackme例子1

样本 jadx 静态分析 其中v2为查v5表得到&#xff0c;v3为用户输入index 下面就分别分析这几个值是啥 abcdefghddddd 得到v5和v4 解压assets下abcdefghddddd 拖入010 editor查看&#xff0c;实际是一个带相关数据的png文件 v5为图片位置89473开始&#xff0c;长度768字节&am…

Web安全 SQL注入漏洞测试.(可以 防止恶意用户利用漏洞)

Web安全 SQL注入漏洞测试 SQL注入就是 有些恶意用户在提交查询请求的过程中 将SQL语句插入到请求内容中&#xff0c;同时程序的本身对用户输入的内容过于相信&#xff0c;没有对用户插入的SQL语句进行任何的过滤&#xff0c;从而直接被SQL语句直接被服务端执行&#xff0c;导致…

AutoSAR软件组件开发的两类工作流程(Matlab/Simulink)

目录 前面 自顶向下 导入arxml文件 生成模型框架 搭建算法模型 生成代码 自下向上 前面 如何在Matlab进行AutoSAR软件组件SWC的开发&#xff1f;也就是下图红框标识出来的部分。 常规的有两种方式自顶向下与自下而上&#xff1a; 从上往下&#xff1a;从软件组件描述文…

改bug神器ChatGPT AI测试将取代人工吗?

最近ChatGPT大火&#xff0c;各大论坛中都会出现它的关键词。 机器和人对话本不是什么新鲜事&#xff0c;而ChatGPT上线仅5天&#xff0c;用户数量就超百万&#xff0c;之所以能在短时间吸引到这么多用户尝鲜&#xff0c;是因为它比“人工智障”的AI前辈们聪明多了~ 玩了一会…

Vue 3 组件通信

本文采用<script setup />的写法&#xff0c;比options API更自由。那么我们就来说说以下七种组件通信方式&#xff1a; props emit v-model refs provide/inject eventBus vuex/pinia 举个例子 本文将使用下面的演示&#xff0c;如下图所示&#xff1a; 上图中…

继承下的类型转换

一、私有/保护继承下的向上类型转换 示例&#xff1a; 图中蓝色、黄色代码均不允许使用&#xff0c;原因是在私有继承下&#xff0c;派生类和基类的public接口完全不搭界&#xff08;所实现的功能没有重叠&#xff09;&#xff0c;因此不允许强制转换&#xff0c;也无任何意义…

04-waf绕过权限控制

WAF绕过-权限控制之代码混淆及行为造轮子 思维导图 后门工具介绍: 菜刀&#xff0c;蚁剑&#xff0c;冰蝎优缺点 菜刀&#xff1a;未更新状态&#xff0c;无插件&#xff0c;单向加密传输 蚁剑&#xff1a;更新状态&#xff0c;有插件&#xff0c;拓展性强&#xff0c;单向加…

远程虚拟桌面解决方案 OpenText™ Exceed™ TurboX(ETX)的优势有哪些?

远程虚拟桌面解决方案 OpenText™ Exceed™ TurboX&#xff08;ETX&#xff09;的优势有哪些&#xff1f; 为 Windows、Linux 和 UNIX 实施精益、经济高效的虚拟化&#xff1b;提供完整的远程 Windows 可用性&#xff1b;以类似本地的性能远程工作&#xff1b;安全地保护系统和…

【Redis7】 Redis7 哨兵(重点:哨兵运行流程和选举原理)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 哨兵&#xff0c;重点&#xff1a;哨兵运行流程和选举原理。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一篇文…

第一章 webpack与构建发展简史

官方loader和插件 Loaders | webpack Plugins | webpack 为什么需要构建工具&#xff1f; 初识webpack webpack默认配置文件&#xff1a;webpack.config.js 可以通过webpack --config <config_file_name>指定配置文件 rules是个数组&#xff0c;一个打包配置可以有多…

基于凸集上投影(POCS)的聚类算法

POCS&#xff1a;Projections onto Convex Sets。在数学中&#xff0c;凸集是指其中任意两点间的线段均在该集合内的集合。而投影则是将某个点映射到另一个空间中的某个子空间上的操作。给定一个凸集合和一个点&#xff0c;可以通过找到该点在该凸集合上的投影来进行操作。该投…

Vue3图片预览(Image)

本图片预览组件主要包括以下功能&#xff1a; 展示图片时&#xff0c;可设置鼠标悬浮时的预览文本&#xff1b;图像无法加载时要显示的描述&#xff1b;自定义图像高度和宽度&#xff1b;设置图像如何适应容器高度和宽度&#xff08; fill(填充) | contain(等比缩放包含) | cov…

Node 06-包管理器

包管理工具 概念介绍 包是什么 『包』英文单词是 package &#xff0c;代表了一组特定功能的源码集合 包管理工具 管理『包』的应用软件&#xff0c;可以对「包」进行 下载安装 &#xff0c; 更新 &#xff0c; 删除&#xff0c; 上传等操作 借助包管理工具&#xff0c;可以…

ASEMI代理ADAU1701JSTZ-RL原装ADI车规级ADAU1701JSTZ-RL

编辑&#xff1a;ll ASEMI代理ADAU1701JSTZ-RL原装ADI车规级ADAU1701JSTZ-RL 型号&#xff1a;ADAU1701JSTZ-RL 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;LQFP-48 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;48 类型&#xff1a;车…

向凯文·凯利提问:未来 5000 天我们将走向何处?

ChatGPT 的问世不禁让人遐想&#xff0c;接下来的 5000 天&#xff0c;将会发生什么事&#xff1f; 硅谷精神之父、世界互联网教父、《失控》《必然》的作者凯文凯利&#xff08;Kevin Kelly&#xff0c;以下简称 K.K.&#xff09;是这样预测的&#xff1a; 未来将会是一切都与…

WIFI-OmniPeek抓包

一、简介 有时候&#xff0c;我们需要抓取空口数据包来分析数据&#xff0c;此时就需要了解抓包软件如何使用。此时需要准备如下东西&#xff1a; SNIFFER&#xff1a;AC-12000 软件&#xff1a;OmniPeek 二&#xff1a;抓包 打开OmniPeek&#xff0c;新建捕获。 确认设备是否…

android studio ImageView和ImageButton和Button

1.ImageView 1.1代码显示 ImageView img findViewById(R.id.img); img.setImageResource(R.drawable.apple); 1.2XML <ImageViewandroid:layout_width"match_parent"android:layout_height"match_parent"android:id"id/img"android:src&qu…