目录
一、非类型模板参数
二、模板的特化
1、特化的概念
2、函数模板的特化
3、类模板的特化
3.1 偏特化
3.2 全特化
3.3 部分特化
结语:
前言:
大多数情况下模板可以解决许多重复性工作,因为把不同的类型传给模板参数后,编译器就会自动生成该类型的函数或类,但是如果传给模板参数的类型是特殊类型(比如:指针类型),那么最终的结果可能会有所偏差,原因就是该模板的内容实现逻辑是按照普通类型的实现逻辑进行的。这时候如果传指针类型给到该模板,则需要先对指针进行解引用后才能走普通类型的实现逻辑,如果直接对指针本身进行操作则结果会发生错误。
在介绍模板的特化前,先浅浅的介绍一些非类型模板参数。模板参数列表中除了可以把类型作为参数,还能将常量作为参数。
示例代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template//n是一个常量
class arr
{
public:
arr(size_t y = n)//缺省值为n
{
for (size_t i = 0; i < n; i++)
{
_arr[i] = y;
}
}
T& operator[](size_t i)
{
return _arr[i];
}
private:
T _arr[n];//表示_arr数组的大小是模板参数n
};
int main()
{
arr a;
for (size_t i = 0; i < 10; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
运行结果:
注意事项:
1、非类型模板参数的类型只能是整形家族的(即:不能是浮点型、自定义类型...等等)。
2、非类型模板参数是常量,不可被修改。
特化就是特殊处理的意思,因为模板在处理指针类型的时候,由于处理逻辑的偏差,导致结果会发生错误,因此需要再写一个模板专门针对指针类型的处理,这个模板就叫做特化。(特化的模板是根据主模板而写的,因此要特化模板的前提是必须有主模板)
体现特化作用的具体示意图如下:
因为模板分为函数模板和类模板,因此模板的特化分为:函数模板特化和类模板特化。
体现特化作用的代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template//函数模板
bool Less(T left, T right)
{
return left < right;
}
int main()
{
int a = 10;//先创建,a处于高地址
int b = 20;//后创建,b的地址量级比a低
cout << Less(a, b) << endl; // 可以比较,结果正确
int* pa = &a;
int* pb = &b;
cout << Less(pa, pb) << endl; // 可以比较,但比较的是地址数,结果错误
return 0;
}
运行结果:
从结果可以看到,pa和pb进行比较的时候,我们期望的其实是pa指向的空间和pb指向的空间的比较,而不是pa和pb本身值的比较,因此可以写一个专门处理指针类型pa和pb的模板特化。(当然,这里重新写一个模板处理指针类型也可以,这里只是示范一下模板特化的写法)
函数模板特化的写法:
1、必须得有一个主模板。
2、template后面跟的尖括号<>中不能写任何内容。
3、函数名后面跟尖括号<>,里面写要特化的类型。
4、函数的形参要和主模板的形参对应。
函数模板特化的代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template
bool Less(T left, T right)//函数模板
{
return left < right;
}
template<>//特化
bool Less(int* left, int* right)//专门处理int* 类型的特化
{
return *left < *right;
}
int main()
{
int a = 10;//先创建,a处于高地址
int b = 20;//后创建,b的地址量级比a低
cout << Less(a, b) << endl; // 可以比较,结果正确
int* pa = &a;
int* pb = &b;
cout << Less(pa, pb) << endl; // 可以比较,结果正确
return 0;
}
运行结果:
从结果可以看到, 即使是对指针类型的比较,结果也是符合我们所期望的。
上文的例子可以用类模板的方式进行大小的比较,思路是创建一个结构体Less(跟上文方法的区别在于:上文Less是一个函数),然后该结构体中实现'()'的运算符重载,目的就是使用Less的运算符重载时,让外表看起来像是在调用一个函数,也把这种操作叫做仿函数调用。
类模板特化代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template
struct Less//类模板
{
bool operator()(T left, T right)//对()进行运算符重载
{
return left < right;
}
};
template<>
struct Less//类模板的特化
{
bool operator()(int* left, int* right)//对()进行运算符重载
{
return *left < *right;
}
};
int main()
{
int a = 10;//先创建,a处于高地址
int b = 20;//后创建,b的地址量级比a低
Less ls1;
cout << ls1(a, b) << endl; // 采用仿函数的方式进行a和b的比较
Less ls2;
int* pa = &a;
int* pb = &b;
cout << ls2(pa, pb) << endl; // 采用仿函数的方式进行pa和pb的比较
return 0;
}
运行结果:
以上的类模板特化存在局限性,即只能处理int*类型的数据,如果用该类模板去比较自定义类型的数据则编译器会执行主模板的内容,如果想让各种指针类型都能够通过该模板特化进行比较,则要用到模板的偏特化。
偏特化的写法:
1、在上述类模板特化的基础下,在template后面的'<>'中加上模板参数,并且该模板内的所有类型都换成模板参数。
类模板偏特化示例代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template
struct Less
{
bool operator()(T left, T right)//对()进行运算符重载
{
return left < right;
}
};
template
struct Less
{
bool operator()(T* left, T* right)//对()进行运算符重载
{
return *left < *right;
}
};
int main()
{
int a = 10;//先创建,a处于高地址
int b = 20;//后创建,b的地址量级比a低
Less ls1;
cout << ls1(a, b) << endl; // 采用仿函数的方式进行a和b的比较
Less ls2;
int* pa = &a;
int* pb = &b;
cout << ls2(pa, pb) << endl; // 采用仿函数的方式进行pa和pb的比较
string st1 = "aba";
string st2 = "abc";
string* pst1 = &st1;
string* pst2 = &st2;
Less ls3;
cout << ls3(pst1, pst2) << endl;//比较的是string*类型的变量
return 0;
}
运行结果:
因此在讲偏特化之前所采用的特化方式可以理解为:全特化,因为偏特化是对模板参数的限制,而全特化在此基础上又进行了明确的类型限制。
全特化就是明确模板特化的具体类型,全特化、偏特化、主模板他们三者的关系示意图如下:
体现全特化的代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template
class func
{
public:
func()
{
cout << "template" << endl;//检查编译器执行的哪个模板
}
};
template<>
class func
{
public:
func()
{
cout << "func" << endl;//检查编译器执行的哪个模板
}
};
int main()
{
func f1;
func f2;
return 0;
}
运行结果:
从结果可以得出,如果传给类模板的类型完全符合全特化的类型,则编译器会直接调用该全特化模板。
部分特化即在偏特化的基础下,可以明确指定其中一个模板参数的类型,而另一个模板参数的类型不给予明确类型。但是如果明确了其中一个类型,则template的<>括号中会少一个模板参数。
特化的总体写法示意图如下:
部分特化的代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
template
class func
{
public:
func()
{
cout << "template" << endl;//检查编译器执行的哪个模板
}
};
template
class func
{
public:
func()
{
cout << "func" << endl;
}
};
int main()
{
func f1;
func f2;
return 0;
}
运行结果:
以上就是关于模板特化的讲解,对于函数模板而言,模板的特化意义不大,因为可以再写一个函数模板作为原本模板的重载也可以解决特殊类型的情况。只有类模板的特化需要值得注意,实际上无非也就是分为三种情况:全特化、偏特化、部分特化,并且编译器会自动调用最匹配的一种情况。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞+关注+收藏!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!