非类型模板参数
模板参数分为:类型形参与非类型形参
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
template<class T>
非类型形参:用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template<size_t N>
例如想指定一个静态数组的长度
#define N 100
template<class T>
class array
{
private:
T _arr[N];
};
上述定义数组长度还可以用宏、枚举等等,但修改长度比较麻烦;类似于在C语言中typedef可以给类型起别名,但是想换类型还得需要在typedef时修改所以C++中使用了模板,同样的想改变常量大小可以增加一个非类型模板参数
//#define N 100
template<class T,size_t N = 100>
class array
{
private:
T _arr[N];
};
C++11中 array是一个静态数组,相比直接定义数组的优势是会检查越界
注意:非类型模板参数必须是整型相关的类型(C++20之前是,C++20以后不清楚,佬们有知道的可以告诉我一下)
模板的特化
函数模板特化
假设有下面这样一段代码,想比较日期类的大小
template<class T>
bool compare(const T& x, const T& y)
{
return x < y;
}
struct Data
{
Data(int year,int month ,int day)
:_year(year)
,_month(month)
,_day(day)
{}
int _year;
int _month;
int _day;
};
void test7()
{
Data* pd1 = new Data(2000,11,1);
Data* pd2 = new Data(2000,8,1);
cout << compare(pd1,pd2) << endl;
}
但是比较的结果并不对,这是因为这里比较的只是指针变量,比较的是地址标号,所以应该处理一下,那么有以下两种方法
方法一:重载一版指定出参数的类型(参数的匹配)
bool compare(const Data*& x, const Data*& y)
{
return x->_year < y->_year;
}
同时这里体现了模板的匹配原则:
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板;
也就是有现成的用现成的,没现成的用最匹配的
方法二:模板特化
template<class T>
bool Compare(const T x, const T y)
{
return x < y;
}
template< >
bool Compare<Data*>(Data* x, Data* y)
{
return x->_year < y->_year;
}
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
通过调用截图可以看出,特化也叫专用版本,有特化使用特化,没特化使用现成的函数,现成的函数没有就用普通函数模板
类模板特化
类模板特化步骤与函数模板特化基本类似
普通类模板
template<class T,class H>
class Data
{
Data(const T& x, const H& y)
:_x(x)
,_y(y)
{}
private:
T _x;
H _y;
};
全特化
template<>
class Data<char ,int>
{
Data(const char& x, const int& y)
:_x(x)
,_y(y)
{}
private:
char _x;
int _y;
};
全特化即是将模板参数列表中所有的参数都确定化。
偏特化
1.将模板参数类表中的一部分参数特化。
template<T>
class Data<T,int>
{
Data(const T& x, const int& y)
:_x(x)
,_y(y)
{}
private:
T _x;
int _y;
};
2.针对模板参数更进一步的条件限制。
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
模板的分离编译
分离编译就是把一个项目分为若干个源文件,再根据不同的功能放到不同的分为同一个功能的.cpp和.h文件等等。查看 .h 文件方便交接整体框架(放结构定义、函数声明等),.cpp文件可以了解具体功能(功能的实现和函数的定义等),所以分离编译的优势是为了方便维护
但若是声明在.h中,定义在.cpp中,在另一个.cpp中调用就会发链接错误
.h
template<class T>
T sub(T& x,T& y);
.cpp
#include "sub.h"
T sub(T& x, T& y)
{
return x - y;
}
在使用模板时,大都包括头文件(.h),实现文件(.cpp),测试文件(test.cpp);在编译阶段,每一个cpp文件都是相对独立的,并不知道另一个编译文件的存在,外部调用,会在链接阶段进行重定位;而模板的转换发生在编译时,模板的实例化只能发生在本编译文件的调用。分离编译时出现非本编译文件的模板调用(test.cpp调用,但是没在test中定义),只能等待链接时重定位,但是模板并没有实例化,所以会出现链接出错
注意:声明和定义分离编译,定义一个模板类型的类指针是可以的,因为他是用这个名称,并不是调用所以有声明就可以