模板的进阶

前言

我们在之前已经了解过普通的模板,它提供模板参数,方便函数或者类的实现,那么今天我们来更加深入的学习模板,掌握一些新的知识。前一篇文章参考:https://blog.csdn.net/OKkankan/article/details/146441102?spm=1001.2014.3001.5501

一. 非类型模板参数

什么叫做非类型的模板参数呢?普通的模板参数类型是跟在class或者typename之后的,而非类型模板就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。eg:

cpp 复制代码
template<class T, size_t N>//N可以给缺省值
class stack
{
private:
	T _a[N];
	int _top;
};

这拿array容器来做比较,array的传参模板与我们举的例子很像,而且都是要在编译期间就要确定大小,当a1运行的时候如果超出了范围会提前中止警告,一定程度上减少错误的发生,但是普通数组的越界访问不做编译期或运行期检查,程序会 "偷偷" 访问内存中数组后面的非法地址(可能是其他变量的内存、无效内存等)可能输出一个随机的垃圾值,也可能导致程序崩溃。而非类型模板参数也同array容器一样,在编译期间如果发生越界,也会提前终止警告。但是我们要注意的非类型模板参数只适用于整型,不适用于浮点数、类对象以及字符串等之类的,(注意C++20以后会支持浮点数等类型)。

二. 模板的特化

2.1 函数模板特化

简单来说就是当一个模板不能够满足全部需求的时候,我们必须另外生成一个特定的模板,但是前提是基于某一个普通的模而存在,这个时候我们就要将某一个模板特化来满足我们的需求。先给大家一个日期类的模板,我们借助这个来实现模板的特化:

cpp 复制代码
class Date
{
public:
	Date(int year=2000,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);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d);

private:
	int _year;
	int _month;
	int _day;

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

前面两个都可以输出正确的答案,但是第三个确实随机值,这说明对于Date*类型来说,Less比较的是p1和p2本身地址的值,所以出现的都是随机值。那么这个时候就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化

cpp 复制代码
//函数模板特化
template<>
bool Less<Date*>(Date* a, Date* b)
{
	return *a < *b;
}

加上模板的特化以后我们就得到了正确的结果,不会再出现随机值,而需要注意的是模板的特化需要基于普通的模板存在,不可以单独存在,所以我们总结出:

函数模板的特化步骤:

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

2.2 类模板特化

类模板的特化分为全特化偏特化,全特化就是模板参数的类型全部都已经确定,偏特化就是模板参数部分确定。

全特化其实是比较容易理解的,就是模板的参数列表所有的数据都是确定的,每这里就不做举例了。而偏特化又分为部分特化参数更进一步设置( 偏特化不仅仅是只确定一部分模板参数列表,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

cpp 复制代码
template<class T1,class T2>
class Date
{
public:
	Date(){ cout<< "Data<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

//部分特化
template <class T1>
class Date<T1, int>
{
public:
	Date() { cout << "Date<T1, int>" << endl; }
private:
	T1 _d1;
	int _d2;
};

//参数更近一步设置
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Date <T1*, T2*>
{
public:
	Date() { cout << "Date<T1*, T2*>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Date <T1&, T2&>
{
public:
	Date(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Date<T1&, T2&>" << endl;
	}
private:
	const T1& _d1;
	const T2& _d2;
};

三. 模板的分离编译

什么是分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

cpp 复制代码
// 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;
}

C++程序的运行是经过预处理->编译->汇编->链接四部分完成,头文件是不被编译的,是在预处理阶段被处理的,然后就是.cpp文件生成各自的obj文件进行链接,但是问题就出在a.cpp编译器没有看到Add模板函数的实例化,因此不会生成具体的加法函数,而在main.cpp中有实例化但是却没有找到的函数代码,也就是相互找不到的情况,所以一般来说,我们是不建议分离的。

相关推荐
拾光Ծ4 小时前
【高阶数据结构】哈希表
数据结构·c++·哈希算法·散列表
鼓掌MVP4 小时前
Rust Web实战:构建高性能并发工具的艺术
开发语言·前端·rust·异步编程·内存安全·actix-web·高性能web服务
盒马盒马4 小时前
Rust:函数与控制流
开发语言·网络·rust
豐儀麟阁贵4 小时前
5.4静态变量和静态方法
java·开发语言
终焉代码4 小时前
【C++】C++11特性学习(1)——列表初始化 | 右值引用与移动语义
c语言·c++·学习·1024程序员节
枫叶丹44 小时前
【Qt开发】容器类控件(二)-> QTabWidget
开发语言·qt
我不会插花弄玉4 小时前
c语言实现栈【由浅入深-数据结构】
c语言·数据结构
会灭火的程序员4 小时前
银河麒麟V10 SP3 升级GCC环境
linux·c++·supermap
shaominjin1234 小时前
OpenCV 4.1.2 SDK 静态库作用与功能详解
android·c++·人工智能·opencv·计算机视觉·中间件