【C++】进阶模板

非类型模板参数

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

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

例如,先来搞一下静态数组:

//静态数组
namespace flash     //下面代码中的array会和std中的array冲突,因此加个命名空间
{
	template<class T, size_t N = 10>   //N代表的就是元素个数,而不是一个特定的类型,可以有缺省值。
	class array
	{
	private:
		T _a[N];
	};
}

void test_template()
{
	flash::array<int, 10> a1;
	flash::array<double, 66>  a2;
}

这里需要注意以下两点:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的,只能是整形家族类型,而且传参时必须传常量。
  2. 非类型的模板参数必须在编译期就能确认结果。

在这里插入图片描述
array这个容器:一旦定义就固定大小,是一个静态数组,支持迭代器、[ ]、不支持插入(因为数组大小是固定的,不能扩容),没有构造函数,不能初始化。

这里就能看出这个容器还是不咋地的,因为我们学C时就已经有了数组这个东西,二者功能基本一样的,而且数组定义的时候还更简便一点。

array 的最大优点还是在于越界检查是非常屌的。

在这里插入图片描述

  • 对于a1而言:a1的[ ]为函数调用,就是重载的[ ]。其会在[ ]内部检查所访问的下标是否超过了其内部的size,那么如果越界了是一定能检查到的。但是普通的数组就不是了。
  • 对于a2而言:a2为指针,其检查是抽检。而且只针对越界写,越界读不做检查。

在这里插入图片描述

模板的特化

模板的特化也称为模板的特殊化。就是当模板参数传参时,有一种类型所执行的功能不是我们本来模板函数或模板类中所想要的功能时,就要对这个类型进行特殊化处理。
例如我们对Data类

class Date
{
	friend ostream& operator<<(ostream& out, const Date& date);
public:
	Date(int year = 2023, int month = 5, int day = 7)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& date)
	{
		cout << "Date(const Date& date)" << endl;
		_year = date._year;
		_month = date._month;
		_day = date._day;
	}
	
	void show() const;
	void show();

	Date& operator=(const Date& date);

	Date operator++(int);

	Date& operator++();

	int GetMonthDay(int year, int month);

	Date& operator+=(int day);

	Date operator+(int day);

	Date& operator-=(int day);

	Date operator-(int day);

	bool operator < (const Date& date);

	bool operator <= (const Date& date);

	bool operator > (const Date& date);

	bool operator >= (const Date& date);

	bool operator!=(const Date& date);

	bool operator==(const Date& date);

	int operator-(const Date& date);

	//ostream& operator<<(ostream& out) const;

private:
	int _year;
	int _month;
	int _day;

void Date::show() const
{
	cout << "void Date::show() const" << endl;
	cout << _year << "/" << _month << "/" << _day << endl;
}

void Date::show()
{
	cout << "void Date::show()" << endl;
	cout << _year << "/" << _month << "/" << _day << endl;
}

bool Date::operator==(const Date& date)
{
	return _year == date._year
		&& _month == date._month
		&& _day == date._day;
}

int Date::GetMonthDay(int year, int month)
{
	int arr[13] = { 0, 31, 28, 31, 30,31,30,31,31,30,31,30,31 };

	if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		return 29;
	}
	
	return arr[month];
}

Date& Date::operator=(const Date& date)
{
	cout << "Date & Date::operator=(const Date & date)" << endl;
	if (!(*this == date))
	{
		_year = date._year;
		_month = date._month;
		_day = date._day;
	}

	return *this;
}

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}


Date Date::operator++(int)
{
	Date tmp = *this;

	*this += 1;//+=也是重载的函数,等会再讲
	return tmp;
}

Date& Date::operator++()
{
	*this += 1;//+=也是重载的函数,等会再讲

	return *this;
}

Date Date::operator+(int day)
{
	Date tmp = *this;
	tmp += day;

	return tmp;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += -day;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day;

	return tmp;
}

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

	return false;
}

bool Date::operator <= (const Date& date)
{
	if (*this < date || *this == date)
		return true;
	
	return false;
}


bool Date::operator > (const Date& date)
{
	if (!(*this <= date))
		return true;

	return false;
}

bool Date::operator >= (const Date& date)
{
	if (!(*this < date))
		return true;

	return false;
}

bool Date::operator!=(const Date& date)
{
	if (!(*this == date))
		return true;

	return false;
}

int Date::operator-(const Date& date)
{
	int flag = 1;
	int n = 0;
	Date max = *this;
	Date min = date;
	if (max < min)
	{
		max = date;
		min = *this;
		flag = -1;
	}
	
	while (min != max)
	{
		++min;
		++n;
	}

	return n * flag;
}
};

inline ostream& operator<<(ostream& out,const Date& date)
{
	out << date._year << "_" << date._month << "_" << date._day;
	return out;
}

这里写一个less模板函数,用来比较两个数据的大小。

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
void Date_test()
{
	std::cout << Less(1, 2) << std::endl; // 可以比较,结果正确
	Date d1(2023, 11, 23);
	Date d2(2023, 11, 13);
	std::cout << Less(d1, d2) << std::endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	std::cout << Less(p1, p2) << std::endl; // 不可以比较,结果不一定正确
}

这里想用Date来比较两个Date的大小,结果却失败了。因为这里实例化对象的时候实例化出来的是Date的对象,p1和p2比较的时候是纯指针的比较,并没有调用Date对象的 < 重载。所以就出问题了。

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

函数模板特化

函数模板的特化步骤:

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

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

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

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

类模板特化

先定义如下类Less:

template<class T>
struct Less
{
	bool operator()(const T& x, const T& y) const
	{
		return x < y;
	}
};

当我们进行同样的比较的时候也会出现上面函数模板中的问题。

void test_classtemplate()
{
	Less<Date> Lessfunc1;
	Less<Date*> Lessfunc2;
	Date d1(2023, 11, 23);
	Date d2(2023, 11, 13);
	std::cout << Lessfunc1(d1, d2) << std::endl; //可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	std::cout << Lessfunc2(p1, p2) << std::endl; //不可以比较,结果不一定正确
}

想要解决的话也是特化就行。但是类模板的特化和函数模板的特化有些不一样。

template<>
struct Less<Date*>
{
	bool operator()(Date* x, Date* y)
	{
		return *x < *y;
	}
};

通过观察上述程序的结果发现,对于日期对象可以直接比较大小,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:比较方式是最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题。

类模板特化分类

类模板特化可分为 :

  • 全特化:全特化即是将模板参数列表中所有的参数都确定化。
  • 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
//普通模板
template<class T1, class T2>
class Data
{
public:
	Data()
	{
		std::cout << "Data(T1, T2)" << std::endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//全特化
template<>
class Data<int, char>
{
public:
	Data()
	{
		std::cout << "Data<int, char>" << std::endl;
	}
private:
	int _d1;
	char _d2;
};
	
//偏特化1:部分偏特化
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
    Data() {cout<<"Data<T1, int>" <<endl;}
private:
    T1 _d1;
    int _d2;
};
//这里只要第二个参数为int就走的是这个模板的特化。根特化不沾边的类型,就走的是原模版。

//偏特化2:参数更进一步的限制
//偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
//两个参数偏特化为指针类型
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()
    {
        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:
   T1 _d1;
   T2 _d2;
};

这里体现的就是有更匹配的就走更匹配的,没有匹配的就将就下。

模板分离编译

首先解释一下什么是分离编译:

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

在之前模板初阶提到过,模板不要分离编译(声明和定义放在不同文件中),因为出错了很麻烦,这里用一下模拟实现的vector,将其push_back()函数进行声明定义分离。会发现出现了链接报错:

在这里插入图片描述
原因是:

一个vector.h
一个vector.cpp
一个test.cpp

cpp文件都引了h,当我们编译了之后,两个.cpp生成对应的.o文件。
就是 vector.o 和 test.o

在test.cpp中,引了vector.h,有test_template函数进行实例化,也就是里面flash::vector v;这条语句,将类模板进行了实例化。能够生成一份对应int的代码。此时.h中的构造、size、析构等函数也就实例化了。所以这些函数的地址在编译阶段就能够确定,那么test.o中是包含有这些成员函数的地址信息的,但是.h中只有push_back和insert的声明,故这两个函数的地址没法确定,只能确定函数名,所以test.o中缺少了这两个函数的函数地址信息。

在vector.cpp中,引了vector.h,而且.cpp文件中还有两个函数模板,但是该文件中没有任何的语句能够将其中的模板参数T实例化,那么push_back和insert 这两个函数也就没法实例化,也就是说编译器无法确定这两个函数的地址,vector.h中虽也有这两个函数的声明,但是都没什么用,函数地址无法确定,最终形成的符号表中只有函数名,没有对应的地址,但是编译器是能够编译通过的,因为编译器眼里还有最后链接的时也能确定函数的地址,所以最终形成的vector.o中也没有这两个函数的地址。

最终链接阶段,两个.o文件中都没有这两个函数的地址,所以这两个函数的地址都没法确定,而且我们还使用到了push_back,但是push_back并没有实例化,所以就出现了链接错误。

解决方法:

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

模板总结

【优点】

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

【缺陷】

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

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

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

相关文章

综述:目标检测二十年(机翻版)(未完

原文地址 20年来的目标检测&#xff1a;一项调查 摘要关键词一 介绍二 目标检测二十年A.一个目标检测的路线图1)里程碑&#xff1a;传统探测器Viola Jones探测器HOG检测器基于可变形零件的模型&#xff08;DPM&#xff09; 2)里程碑&#xff1a;基于CNN的两阶段探测器RCNNSPPN…

学会Bitmap内存管理,你的App内存还会暴增吗?

相信伙伴们在日常的开发中&#xff0c;一定对图片加载有所涉猎&#xff0c;而且对于图片加载现有的第三方库也很多&#xff0c;例如Glide、coil等&#xff0c;使用这些三方库我们好像就没有啥担忧的&#xff0c;他们内部的内存管理和缓存策略做的很好&#xff0c;但是一旦在某些…

浅谈C++重载、重写、重定义

C重载、重写、重定义 重载、重写、重定义对比一、重载&#xff08;overload&#xff09;二、重写 / 覆盖&#xff08;override&#xff09;三、重定义 / 隐藏&#xff08;redefining&#xff09; * 为什么在虚函数中不能使用 static 关键字&#xff1f;动态绑定&#xff08;Dyn…

【项目设计】网络版五子棋游戏

文章目录 一、项目介绍1. 项目简介2. 开发环境3. 核心技术4. 开发阶段 二、环境搭建1. 安装 wget 工具2. 更换 yum 源3. 安装 lrzsz 传输工具4. 安装⾼版本 gcc/g 编译器5. 安装 gdb 调试器6. 安装分布式版本控制工具 git7. 安装 cmake8. 安装 boost 库9. 安装 Jsoncpp 库10. 安…

C语言实现冒泡排序(超详细)

排序算法 - 冒泡排序 什么是冒泡排序&#xff1f;冒泡排序有啥用呢&#xff1f;冒泡排序的实现代码讲解冒泡排序的总结 什么是冒泡排序&#xff1f; 冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的列表&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序…

详谈动态规划问题并解最大子数组和

今天刷力扣又学会了一种算法----动态规划&#xff0c;经过我查阅不少资料后&#xff0c;这些我总结的分享给大家 动态规划是什么&#xff1f; 动态规划&#xff08;Dynamic Programming&#xff09;是一种求解最优化问题的数学方法&#xff0c;它通常用于解决具有重叠子问题和…

802.11-2020协议学习__专题__TxTime-Calculation__HR/DSSS

802.11-2020协议学习__专题__TxTime-Calculation__HR/DSSS 16.2.2 PPDU format16.2.2.1 General16.2.2.2 Long PPDU format16.2.2.3 Short PPDU format 16.3.4 HR/DSSS TXTIME calculation PREV&#xff1a; TBD NEXT&#xff1a; TBD 16.2.2 PPDU format 16.2.2.1 General 定…

快速集成Skywalking 9(Windows系统、JavaAgent、Logback)

目录 一、Skywalking简介二、下载Skywalking服务端三、安装Skywalking服务端3.1 解压安装包3.2 启动Skywalking 四、关于Skywalking服务端更多配置五、Java应用集成skywalking-agent.jar5.1 下载SkyWalking Java Agent5.2 集成JavaAgent5.3 Logback集成Skywalking5.4 集成效果 …

Java Fasn 带您谈谈——开源、闭源

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

Python —— Mock接口测试

前言 今天跟小伙伴们一起来学习一下如何编写Python脚本进行mock测试。 什么是mock? 测试桩&#xff0c;模拟被测对象的返回&#xff0c;用于测试 通常意义的mock指的就是mock server, 模拟服务端返回的接口数据&#xff0c;用于前端开发&#xff0c;第三方接口联调 为什么…

特征缩放和转换以及自定义Transformers(Machine Learning 研习之九)

特征缩放和转换 您需要应用于数据的最重要的转换之一是功能扩展。除了少数例外&#xff0c;机器学习算法在输入数值属性具有非常不同的尺度时表现不佳。住房数据就是这种情况:房间总数约为6至39320间&#xff0c;而收入中位数仅为0至15间。如果没有任何缩放&#xff0c;大多数…

anaconda安装依赖报错ERROR: Cannot unpack file C:\Users\33659\AppData\Loca...|问题记录

执行命令&#xff1a; # 安装matplotlib依赖 pip install matplotlib-i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com出现问题&#xff1a; ERROR: Cannot unpack file C:\Users\33659\AppData\Local\Temp\pip-unpack-0au_blfq\simple (downloa…

Redis面经

Redis使用场景 1、缓存&#xff1a; 缓存三兄弟(穿透、击穿、雪崩) 、双写一致、持久化、数据过期策略&#xff0c;数据淘汰策略 2、分布式锁 setnx、redisson 3、消息队列 4、延迟队列 何种数据类型&#xff08;list、zset&#xff09; 缓存三兄弟 缓存穿透 缓存穿透…

【RH850芯片】RH850U2A芯片平台Spinlock的底层实现

目录 前言 正文 1.RH850U2A上的原子操作 1.1 Link 1.2 Link generation 1.3 Success in storing 1.4 Failure in storing 1.5 Condition for successful storing 1.6 Loss of the link 1.7 示例代码 2.Spinlock代码分析 2.1 尝试获取Spinlock 2.2 释放Spinlock …

Web前端—移动Web第二天(空间转换、动画、综合案例:全名出游)

版本说明 当前版本号[20231118]。 版本修改说明20231118初版 目录 文章目录 版本说明目录移动 Web 第二天01-空间转换空间转换简介平移视距旋转左手法则rotate3d-了解立体呈现案例-3d导航缩放 02-动画动画实现步骤animation复合属性animation拆分写法案例-走马灯精灵动画多组…

一款带数字传输信号的OVP芯片

基本概述 今天给大家介绍的一款芯片是OVP&#xff0c;相比于传统的OVP芯片来说&#xff0c;这款芯片新增了数字信号控制&#xff0c;可以进行10Mbps的一个通信&#xff0c;通过外部的GPIO口进行控制&#xff0c;达到输入与输出信号的产生。 YHM2009这款OVP芯片具有较低的导通…

图像分类(一) 全面解读复现AlexNet

解读 论文原文&#xff1a;http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf Abstract-摘要 翻译 我们训练了一个庞大的深层卷积神经网络&#xff0c;将ImageNet LSVRC-2010比赛中的120万张高分辨率图像分为1000个不…

如何实现一个下班倒计时程序

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 Hello伙伴们&#xff0c;好几天不见啦。最近也是晚上打球太累&#xff0c;加上一直在研究mybatis的多租户问题&…

LM(大模型)应用开发利器之LangChain,带你走进AI世界

原文&#xff1a;LLM&#xff08;大模型&#xff09;应用开发利器之LangChain&#xff0c;带你走进AI世界 - 简书 LangChain组件图 LangChain 是什么 首先 LangChain 是一个框架&#xff0c;这个框架是用来让开发者进行 LLMs &#xff08;大语言模型&#xff09;应用开发的。…

【Unity】单例模式及游戏声音管理类应用

【Unity】单例模式及游戏声音管理类应用 描述 在日常游戏项目开发中&#xff0c;单例模式是一种常用的设计模式&#xff0c;它允许在应用程序的生命周期中只创建一个对象实例&#xff0c;并提供对该实例的全局访问点。通过使用单例模式&#xff0c;可以提高代码的可维护性和可…