目录
priority_queue
适配器
适配器初认识
模板参数的缺省值
仿函数
priority_queue 向上调整算法&&向下调整算法
为什么需要使用仿函数
函数指针方式
仿函数方式
仿函数较于函数指针的优点
函数指针的调用实现
仿函数的调用实现
使用仿函数
deque
背景
deque结构
deque迭代器
指针类型与迭代器的比较
反向迭代器
非类型模板参数
priority_queue
本质就是一个堆,它是适配器,不是容器,没有迭代器,优先级队列的插入就是堆的插入,堆插入向上调整
适配器
适配器初认识
适配器的本质就是复用,比如在实现栈的时候可以通过利用vector来完成
适配器的作用是能够使两个不相同的类型,使用同一功能,比如栈和vector
适配器实现过程:适配器类则实现目标接口,并使用适配者类的方法来提供所需的功能,在此间,栈就可以使用vector的功能
我们可以通过下面类模板中多提供了一个参数class Container 这个参数,通过main函数里的lhl::stack<int, vector<int>> s; 来表明我的栈是通过vector来实现的,这就是数组栈
namespace lhl {
template<class T,class Container>
class stack {
private:
Container _con;
public:
void push(const T& x) {
_con.push_back(x);
}
void pop() {
_con.pop_back();
}
T& top() {
return _con.back();
}
bool empty() {
return _con.empty();
}
size_t size() {
return _con.size();
}
};
}
int main() {
lhl::stack<int,vector<int>> s; //数组栈
s.push(1);
s.push(2);
s.push(3);
s.push(4);
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
return 0;
}
甚至可以通过修改容器参数来实现其它结构的栈
eg:lhl::stack<int, list<int>> s; 这是链式栈
模板参数的缺省值
函数参数可以用缺省参数,模板参数也同样可以用缺省参数,模板参数传的是类型
在STL文档中,stack的模板第二个参数的缺省值传的就是deque<T>,模板参数是在编译的时候传参,但并不是随便填一个容器都能够完成适配器!!!适配器构造,析构和拷贝都不用我们自己去写
deque的方括号效率不高
仿函数
priority_queue 向上调整算法&&向下调整算法
void adjust_up(int child) {
int parent = (child - 1) / 2;
while (child > 0) {
if (_con[child] > _con[parent]) {
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else {
break;
}
}
}
void adjust_down(int parent) {
int child = parent * 2 + 1;
while (child < _con.size()) {
if (_con[child + 1] < _con.size() &&
_con[child] < _con[child + 1]) {
child = child + 1;
}
if (_con[child] > _con[parent]) {
swap(_con[child], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else {
break;
}
}
}
这种算法调整是属于大堆的
那假如我的要求是小堆呢?
难不成需要额外在写一次向上调整和向下调整吗?
就算我们在写一次,我们再调用的时候,还是需要去更改调用对象啊,所以肯定是及其繁琐且无意义的
因此,我们可以通过仿函数来控制
为什么需要使用仿函数
仿函数也叫做函数对象,仿函数最重要的实现步骤的是重载()!!!,即重载圆括号
先来看看为什么要用仿函数,如果我们使用函数指针我们又该如何
函数指针方式
bool lessfc(int x,int y) {
return x < y;
}
bool greaterfc(int x, int y) {
return x > y;
}
class A {
public:
A(bool(*pf)(int,int))
:_pf(pf)
{}
void func(int xx, int yy) {
cout << "void func(int xx, int yy): " << _pf(xx, yy) << endl;
}
bool (*_pf)(int, int);
};
仿函数方式
namespace LHL {
template<class T>
class less {
public:
bool operator()(const T& x, const T& y) {
return x < y;
}
};
template<class T>
class greater {
public:
bool operator()(const T& x, const T& y) {
return x > y;
}
};
}
template<class Compare>
class B {
public:
void func(int xx, int yy) {
Compare com;
cout << "void func(int xx, int yy): " << com(xx, yy) << endl;
}
};
仿函数较于函数指针的优点
仿函数在此处的优点就是:类型简单,它就是一个普通的类型
而函数指针需要则需要在类里面传进来,因此需要一个成员将它存储起来,在上例中,bool (*_pf)(int, int);就是一个存储函数指针的成员,此外还需要通过构造函数将其传递进来。
而仿函数则只需要给个自定义对象,就可以调用重载()的比较效果
函数指针的调用实现
void test3() {
A a1(lessfc);
a1.func(1, 2);
A a2(greaterfc);
a2.func(4, 5);
}
仿函数的调用实现
void test4() {
B<LHL::less<int>> a1;
a1.func(1, 2);
B<LHL::greater<int>> a2;
a2.func(1, 2);
}
明显能够对比出仿函数可以直接通过对象就可以调用,而函数指针还需要传进去才可以使用
函数指针是个对象,不能够在类模板的参数中去传
使用仿函数
所以我们在自己手写priority_queue的时候,可以将模板参数扩充到三位
template<class T,class Container = vector<T>>
template<class T,class Container = vector<T>,class Compare = less<T>>
或者
template<class T,class Container = vector<T>,class Compare = greater<T>>
回头看,我们就会发现
stack queue 和 priority_queue本质上都是仿函数
stack 和 queue 的默认容器是deque
priority_queue的默认容器时vector
deque
背景
vector和list的优缺点
vector优点:(连续存储的优势)
1,下标的随机访问
2,缓存命中更厉害
vector缺点:
1,插入删除较前的位置,效率较低
2,扩容消耗高
list优点:
1,任意位置插入删除效率高
2,按需申请,消耗较小
list缺点:
1,随机访问效率低
2,缓存命中率低
我们可以看出,vector和list的优势和劣势是相反互补的,那么存不存在一张容器,能够既有list的优点,又有vector的优点
既然都这样问了,那答案肯定是有的
deque就是这种情况下,就又有vector的优点,也有list的优点
deque结构
deque的结构是指针与数组并用,一般情况下,deque会开几个数组规模大小相同的buffer,再通过一个中控 (存储buffer的指针数组)来对buffer进行管理,它是中控满了才进行扩容
deque迭代器
deque的迭代器有四个指针
first指向的是一个buffer的开始
last指向的是一个buffer的结束
cur指向的是buffer中的一个位置
node指向存储当前buffer指针在中控的位置,node相当于二级指针
deque有两个迭代器
class deque{ iterator start; iterator finish; }
start指向的是第一个buffer,并且其cur指向的是这个buffer的第一个位置
finish指向的是最后一个buffer,并且其cur指向的是这个buffer的最后一个数据的下一个位置
前插和尾插都是通过修改start和finish来实现,如果buffer满了,则修改中控的位置,如果中控满了则扩充中控
优势是头插头删,尾插尾删很方便
结论:下标随机访问,效率不错,但跟vector仍有差距,中间插入删除效率差
所以这就是为什么stack和queue会选择它
指针类型与迭代器的比较
ListNode<int>* it1;
__list_iterator<int, int&, int*> it;
它们两个不是同一类型,第一个是内置类型,第二个是自定义类型
它们的解引用,++的意义也不一样,相差很大
++后地址都发生了变化,但他们的刚开始所指向的却是一样的,且都拥有8字节的大小(指针大小)
it1++是根据Node的大小来跨越,it2++后是指链表的下一个位置,因为链表的物理空间不是连续的,所以++后的下一个节点的地址就会变得不确定
反向迭代器
我们可以通过跟实现正向迭代器一样来实现反向迭代器,但是设计者认为STL中有很多容器,难道都要一一实现,既然一个容器的正向迭代器已经完成,那么可不可以跟用适配器一样,采用迭代器适配器,同意建立一个类,来实现基本上容器的反向迭代器,至于是什么类型的迭代器,就让迭代器模板去推测吧
基本功能都是在正向迭代器内完成了,即对链表内部的处理等,再此只需复用即可
Iterator能够确认类型,所以第一个模板参数由class T 演变成 class Iterator 是经得起推敲的
//适配器,是谁的适配器我不管,我也不知道
template<class Iterator,class Ref,class Ptr>
struct ReverseIterator
{
typedef ReverseIterator<Iterator,Ref,Ptr> Self;
Iterator cur;
ReverseIterator(Iterator it)
:cur(it)
{}
Self& operator++() {
--cur;
return *this;
}
Ref operator*() {
Iterator tmp = cur; //不能用Self,用了就是自己减自己了
--tmp;
return *tmp;
}
Ptr operator->() {
return &(operator*());
}
bool operator!=(const Self& s) {
return cur != s.cur;
}
……
};
*rit取到的是前一个位置,即要减减一下,如果不这样,对rbegin解引用的时候,就会变成对哨兵位解引用
typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin() {
return reverse_iterator(end());
}
reverse_iterator rend() {
return reverse_iterator(begin());
}
测试,引入头文件 #include"ReverseIterator.h"
写出来的反向迭代器类不仅list能用,vector也能用,其它类也能用
所以实现一个类,基本上的其他类都能够复用
非类型模板参数
在实现类模板过程中,我们有时会出现比较固定的类
#define N 10
template<class T>
class Stack {
private:
T _a[N]; //10
int _top;
public:
void fun() { }
};
int main() {
Stack<int> s1;
return 0;
}
比如此类,如果我想看一个20个空间的_a[N],我们便会较为尴尬,就是不能够达到我可以随意定义我想要的空间大小
所以,就出现了非类型模板参数
template<class T,size_t N>
class Stack {
private:
T _a[N];
int _top;
public:
void fun() {}
};
int main() {
Stack<int,10> s1;
Stack<int,30> s2;
return 0;
}
非类型模板参数只能是整型常量,浮点数那些也不行
以上就是本次博文的学习内容了,如有错误,还望各位大佬指点,谢谢阅读!