💓博主CSDN主页:杭电码农-NEO💓
⏩专栏分类:C++从入门到精通⏪
🚚代码仓库:NEO的学习日记🚚
🌹关注我🫵带你学习C++
🔝🔝
模板进阶
- 1. 前言
- 2. 仿函数的概念
- 3. 仿函数的实际用途
- 4. 模板的非类型模板参数
- 5. 模板的特化简单介绍
- 6. 函数模板深度剖析
- 7. 类模板的特化深度剖析
- 7.1 模板的全特化
- 7.2 模板的偏特化
- 8. 总结以及拓展
1. 前言
C++进阶中关于STL库的初级数据
结构就已经结束了,高阶数据结构如:
二叉搜索树AVL树,红黑树,哈希
等等将在C++高阶讲解.
本章重点:
本篇文章着重讲解仿函数的概念
以及自行实现一个仿函数.模板进阶
中,着重讲解非类型模板参数,模板
的特化还有模板的分离编译
2. 仿函数的概念
仿函数的本质就是一个类,此类中
运算符重载了括号()!所以它使用起来
和函数很相似,就叫做仿函数
在标准库的优先级队列的类模板中
有这样一个缺省参数叫less:
这个less就是一个仿函数,它会将
优先级队列变成大堆,在算法库的
sort函数默认是升序,其实就是用的less
与less对应的仿函数是greater,greater
可以将优先级队列变成小堆,将sort变成降序
我们可以模仿实现一下less的使用场景:
class Less
{
public:
bool operator()(int x,int y)
{
return x<y;
}
};
int main()
{
Less functor;
cout<<functor(1,2);
}
注:1小于2,会返回true,打印1
3. 仿函数的实际用途
首先是使用库中的某些函数时
仿函数能很方便的改变升降序或大小堆
升序写法:
vector<int> v{9,8,7,6};
sort(v.begin(),v.end());
sort(v.begin(),v.end(),less<int>);
降序写法:
vector<int> v{6,7,8,9};
sort(v.begin(),v.end(),greater<int>);
大堆写法:
priority_queue<int> p1;
priority_queue<int,vector<int>,less<int>> p2;
小堆写法:
priority_queue<int,vector<int>,greater<int>> p;
注:优先级队列的适配器参数在仿函数
前面,想要显示传仿函数,先要穿前面的
当然,greater的内部实现和less
只差了一个符号而已,如下:
class Greater
{
public:
bool operator()(int x,int y)
{
return x>y;
}
};
4. 模板的非类型模板参数
模板参数类型解析:
模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中
跟在class/typename之后的参数类型
非类型形参,就是用一个常量作为
类(函数)模板的一个参数,在类(函数)
模板中可将该参数当成常量来使用
比如:
template<class T,int N = 10>
class test
{
T a[N];
};
test<int,50> t1;
test<double> t2;
注:N=10是缺省值,没传时默认为10
讲到这儿就不得不介绍STL中一个不常用的容器了
array是静态数组
也就是固定大小的顺序容器
使用时,要显示传参N来初始化数组
array属于C++的数组,使用array
时,不管是越界读还是越界写都能
被检测到从而报错,然而使用C语言
的数组时,越界读写不一定会报错
5. 模板的特化简单介绍
通常情况下,使用模板可以实现一些与
类型无关的代码,但对于一些特殊类型的
可能会得到一些错误的结果需要特殊处理
比如:实现用来进行小于比较的函数模板
template<class T>
bool Less(T left, T right)
{
return left < right;
}
Less绝对多数情况下都可以正常比较
但是在特殊场景下就得到错误的结果
比如这里我传入指针地址过来
这里的比较就会有问题,我想比较
的是指针指向的内容,然而传入指针
的话会比较指针的地址高低,就和数据无关
此时,就需要对模板进行特化
即:在原模板类的基础上
针对特殊类型所进行特殊化的实现方式
类模板分为函数模板和类模板
6. 函数模板深度剖析
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<int*>(int* left, int* right)
{
return *left < *right;//比较指针指向的内容
}
当传参时给函数传了int类型的指针
那么就不会调用第一个函数,而是走
第二个特化的函数,特化也就是特殊处理
注:一般情况下如果函数模板遇到不能处理或者处理有误的类型
为了实现简单通常都是将该函数直接给出
bool Less(int* left, int* right)
{
return *left < *right;
}
所以实际上函数模板的特化是不常用的
7. 类模板的特化深度剖析
类模板的特化分为全特化和偏特化
7.1 模板的全特化
全特化即是将模板参数中所有参数都确定
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
template<>
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
int _d1;
char _d2;
};
Data<int, int> d1;
Data<int, char> d2;
和函数模板特化一样,特化的部分
要加上template<>作为格式,上面
初始化时,<int,int>类型不会走模板特化
然而<int,char>类型会走模板特化
7.2 模板的偏特化
偏特化:
任何针对模版参数进一步进行条件限制设计的特化版本
然而偏特化又有两种表现形式:
- 部分特化
- 对参数做进一步限制
比如对于上面例子中的模板类做部分特化:
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
此时,只要第二个参数是int,就会
走偏特化,第二个参数不是int就不走
对上面的类做参数进一步限制:
//两个参数偏特化为指针类型
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(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
8. 总结以及拓展
补充完仿函数和模板进阶相关知识后
接下来我们将进入继承和多态的学习
继承和多态这部分在校招中考察的很多
请耐心学习~~
对于模板分离编译的拓展:
为什么模板不能分离编译?
模板分离编译问题剖析