【C++进阶(七)】仿函数深度剖析&模板进阶讲解

博主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

【C++进阶(七)】仿函数深度剖析&模板进阶讲解_第1张图片

讲到这儿就不得不介绍STL中一个不常用的容器了

在这里插入图片描述

array是静态数组
也就是固定大小的顺序容器
使用时,要显示传参N来初始化数组

array属于C++的数组,使用array
时,不管是越界读还是越界写都能
被检测到从而报错,然而使用C语言
的数组时,越界读写不一定会报错


5. 模板的特化简单介绍

通常情况下,使用模板可以实现一些与
类型无关的代码,但对于一些特殊类型的
可能会得到一些错误的结果需要特殊处理
比如:实现用来进行小于比较的函数模板

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

Less绝对多数情况下都可以正常比较
但是在特殊场景下就得到错误的结果

比如这里我传入指针地址过来
这里的比较就会有问题,我想比较
的是指针指向的内容,然而传入指针
的话会比较指针的地址高低,就和数据无关

此时,就需要对模板进行特化
即:在原模板类的基础上
针对特殊类型所进行特殊化的实现方式

类模板分为函数模板和类模板


6. 函数模板深度剖析

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
// 函数模板 -- 参数匹配
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" <<endl;}
private:
	T1 _d1;
	T2 _d2;
};
template<>
class Data<int, char>
{
public:
	Data() {cout<<"Data" <<endl;}
private:
	int _d1;
	char _d2;
};
Data<int, int> d1;
Data<int, char> d2;

和函数模板特化一样,特化的部分
要加上template<>作为格式,上面
初始化时,类型不会走模板特化
然而类型会走模板特化


7.2 模板的偏特化

偏特化:
任何针对模版参数进一步进行条件限制设计的特化版本

然而偏特化又有两种表现形式:

  • 部分特化
  • 对参数做进一步限制

比如对于上面例子中的模板类做部分特化:

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() {cout<<"Data" <<endl;}
private:
	T1 _d1;
	int _d2;
};

此时,只要第二个参数是int,就会
走偏特化,第二个参数不是int就不走

对上面的类做参数进一步限制:

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public:
	Data() {cout<<"Data" <<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" <<endl;
	}
private:
	const T1 & _d1;
	const T2 & _d2; 
 };

8. 总结以及拓展

补充完仿函数和模板进阶相关知识后
接下来我们将进入继承和多态的学习
继承和多态这部分在校招中考察的很多
请耐心学习~~

对于模板分离编译的拓展:

为什么模板不能分离编译?
模板分离编译问题剖析


下期预告:C++继承

你可能感兴趣的:(C++从入门到精通,c++,java,开发语言)