非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
这种应用场景可以用在你如果要定义的对象是静态变量就可以考虑使用,比如:静态数组
cpp
template <class T,size_t N=10>
class Array {
public:
//......一些函数
private:
T* _a[N];
size_t size();
};
如上实例中定义的类里的模板参数里N就是非类型模板参数,它的类型一般是整型,浮点数与类类型都不行,到c++20才支持。 这种静态数组的类在STL容器的array中是使用的,它的里面的数组就是静态数组 https://legacy.cplusplus.com/reference/array/array/?kw=array 这是在cplusplus上的详细对array的内容,与vector的区别就是它的数组是定长的。
模板的特化
函数模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
cpp
template <class T>
bool Less(const T& left, const T& right)
{
return left < right;
}
比如这是用来比较的函数模板,对于传入的为非指针的类型,比较逻辑不变,但如果是传入指针类型的话,这个模板实例化之后就是比较两个指针的大小在一般情况下是毫无意义的
cpp
cout << Less(1, 2) << endl;
double* p1 = new double(2.5);
double* p2 = new double(4.9);
cout << Less(p1, p2) << endl;
比如这里的p1与p2的比较比较最后比较的是指针的大小,而不是它们的指针指向的内容的比较,这种比较是无意义的
cpp
template<>
bool Less<double*>(double* const& left, double* const& right)
{
return *left < *right;
}
这里的语法就是写个模板但是不带任何参数,这就是模板特化也叫做全特化,然后类名跟定义的参数是一样的然后尖括号里面带具体类型,比如这里是double* 函数体要接受的 形参也是尖括号里的类型,然后用解引用后的内容比较,它调用函数就会找最匹配的函数模板,第一个输出会找初始的那个,第二个会找刚刚写的特化的那个
写特化的函数模板一定要先有初始的写的不是具体类型的模板,否则就会报错,要先有这个才能写后面具体类型的模板特化
类模板的特化
全特化
跟函数模板的语法类似也是template<>
cpp
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
比如如上的一个简单的类模板,如果要写它的全特化就是
cpp
`template<>
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
int _d1;
char _d2;
};`
全特化就是<>里面不带任何参数 而且写的特化后的模板里面的除了类名要一样,其他的比如成员变量和成员函数都可以不一样甚至可以不写都行,如果定义的对象的类型与特化的更匹配就会调用特化的类进行构造,否则就会使用最初的类模板进行构造,两者是互不干扰的,但是必须要先有类模板才有后面的特化模板
这里自己可以尝试一下然后调试写不同类型会调用那个类去构造
偏特化
意思为对部分模板参数进行具体类型显示
比如:
cpp
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
保留第一个参数,显示具体类型第二个参数,如果将第一个显示具体化那就在就行后面类名跟的尖括号也要变
特别重要的一种:
如果对指针类型进行偏特化,因为我们想要的函数一般不会直接返回指针比较的值,所以可以如下这样写:
cpp
template<class T1,class T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
};
int main()
{
Data<int*,int*> d1;
这里的T1和T2的实例化类型都是int 而不是int* 这是编译器的特殊处理这样会给用户的体验更好,,因为如果是int* 那类名中的就是二级指针对二级指针的操作又很麻烦,所以这里就这样处理后,对一级指针的操作不管是比较还是什么都很方便
举个应用场景:
在我们以前学的优先级队列------priority_queue中,我们的类模板参数中有默认的仿函数来做比较堆中的父子与孩子结点的优先级
cpp
template <class T>
//仿函数的应用
struct less {
bool operator()(const T& x,const T& y )
{
return x < y;
}
};
template <class T, class container=std::vector<T>,class Compare=less<T>>
class priority_queue {
..........
};
我们默认是建大堆出优先级高的数据,现在我创建个对象里面装的数据是日期类型的值
cpp
priority_queue<Date*> q1;
q1.push(new Date(2026, 4, 10));
q1.push(new Date(2026, 4, 11));
q1.push(new Date(2026, 4, 12));
while (!q1.empty())
{
cout << *q1.top() << " ";
q1.pop();
}
如果我们这样比较就是比较指针的排序,我们要的是根据日期的大小的来出列,所以我们要偏特化仿函数模板来针对这个来写:
cpp
template <class T>
//仿函数的应用
struct less {
bool operator()(const T& x,const T& y )
{
return x < y;
}
};
template<class T>
struct less<T*> {
bool operator()(T* const& x, T* const& y)
{
return *x < *y;
}
};
所以当时传的是指针类型的日期就会走偏特化模板 然后里面的比较就会走它的类里的 < 的比较方式,最后就是排序就会是从大到小的输出:
模板分离编译
我们之前提过在有模板的函数里不能做函数的声明与定义,会发生链接错误。
函数的执行会通过这几个过程 预处理------编译------汇编------连接
在main()函数中调用普通函数最先会在一般是在.h文件里有函数的声明,它会在预处理展开头文件,它要 call函数的地址在编译层面,他会在定义的函数里取一般是第一行执行命令的地址但你有函数的声明就相当于向编译器保证有函数的定义,所以它会去其他文件里找,找到就call 地址后 连接是与头文件连接
如果是带有函数模板的类或函数的话,首先在头文件里找到声明,然后去其它文件找定义,但是其它文件的定义都是未实例化的函数不是你传的实参的类型的参数它是一个泛型的,所以就找不到定义也就是call 中没有地址返回给它,最后在连接的时候就会报错,但也有种方法就是显示实例化对象,比如你传的是int类型,就显示写 如下:
cpp
test.h文件 test.cpp文件
template<class T>
void fun(T x){
.........
}
template<class T> template<class T>
void fun(T x); voide fun(int x);
但是这样你每传个不同类型的参数都要再写一遍显示的实例化,所以就很麻烦,所以不推荐这样写,解决办法就是: 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的
https://blog.csdn.net/pongba/article/details/19130 更详细的讲解请看这位大佬的解释