适配器
适配器是一种设计模式,【设计模式类似于孙子兵法,36计,适配器就类似于其中一个计谋】
适配器将一个类的接口转换成另一种接口.
容器适配器
容器适配器是把特定的类作为底层容器,并提供一组特定成员函数来访问其元素.
以栈为例子,对于栈的实现:
(1) 底层可以使用vector容器来实现,尾部当栈顶
(2) 底层也可以使用list容器来实现
stl栈和队列
stl的栈和队列不是容器,而是容器适配器.
cplusplus.com文档上的截图:
T:存储的数据类型
Container:底层可以指定使用容器,默认用deque容器
--栈模拟实现
namespace lyh
{
template<class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T& val)//入栈
{
_con.push_back(val);
}
void pop()//出栈
{
_con.pop_back();
}
T& top()//栈顶
{
return _con.back();
}
bool empty()//判空
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
}
void myStackTest()
{
//显式传vector作为栈的底层容器
lyh::stack<int, vector<int>> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
}
容器要能提供以下接口,才能作为stack的底层容器.
cplusplus.com
--队列模拟实现
namespace lyh
{
template<class T, class Container >
class queue
{
public:
//入队列
void push(const T& val)
{
_con.push_back(val);
}
//出队列
void pop()
{
_con.pop_front();
}
//队头
T& front()
{
return _con.front();
}
//队尾
T& back()
{
return _con.back();
}
//判空
bool empty()
{
return _con.empty();
}
size_t size()
{
return _con.size();
}
private:
Container _con;
};
}
void myQueueTest()
{
lyh::queue<int, list<int>> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
while (!q.empty())
{
cout << q.front() << " ";
q.pop();
}
}
cplusplus.com文档
deque容器
deque和vector、list一样,都是stl里的容器,
一种数据结构,也是栈和队列的默认使用容器.
--简化模型【只能了解到它的功能】
cplusplus.com
--具体模型【deque的物理存储方式】
deque是一种分段连续的数据结构,连续是假象,分段是事实,
A 不进行任何操作:
( 1 ) 它会有多段连续的buffer空间,在这些buffer里存放数据.
( 2 ) 但如何把这么多段buffer连接起来?
用一个指针数组,按顺序存放这些buffer的地址(一般叫中控数组)
( 3 ) 刚开始时,buffer地址数组是从中间开始使用,保证两边都可以填充数据.
B 进行尾插头插/尾删头删时:
( 1 ) deque里会保存第一个元素的位置start,和最后一个元素的下一个位置finish.
因此可以随时对头尾进行插入删除
( 2 ) 模拟实现时要考虑【buffer地址数组的扩容】,【buffer空间的申请释放】.
--迭代器模拟实现
迭代器成员分析
sgi版stl中的deque迭代器有4个成员属性
cur : 指向存储的数据
first : 数据所在的缓冲区buffer的起始位置
last : 数据所在缓冲区buffer的末尾
node : T**类型,
( 1 ) 哪里保存当前buffer地址?
中控数组/buffer地址数组的某个位置
( 2 ) 在用迭代器进行遍历deque时,迭代器必须有能力去跳到下一个buffer中,
所以迭代器必须保存 管理当前buffer 的位置
模拟实现代码
#include <string.h>
#include <assert.h>
namespace lyh
{
int bufferSize = 10;
template<class T, class Ref, class Ptr>
struct dequeIterator
{
public: //成员变量
//具体数据的指针
T* _cur;
//标识 具体数据所在buffer 的开始和结束
T* _first;
T* _last;
//缓冲区地址数组,第n个位置保存了当前buffer首地址,标识该第n个位置的地址
//也称为中控器
T** _buffer_adr_array_n;
public:
typedef dequeIterator<T, Ref, Ptr> Iterator;
//用deque里具体数据的地址,和维护当前数据缓冲区的中控器,来构造iterator
dequeIterator(T* val,T** buffer_adr_array_n)
{
_cur = val;
_buffer_adr_array_n = buffer_adr_array_n;
if (_cur != nullptr)
{
_first = *buffer_adr_array_n;
_last = *buffer_adr_array_n + bufferSize;
}
else
{
_first = nullptr;
_last = nullptr;
}
}
//Iterator it;
//++it
//即让迭代器指向下一个元素
//思路:++_cur,如果_cur != _last,说明没有超过当前buffer
//如果_cur == _last,说明要跳转到下一个buffer首地址
Iterator& operator++()
{
++_cur;
if (_cur == _last)
{
T* nextBuffer = *(++_buffer_adr_array_n);
_cur = nextBuffer;
//跳转以后,要重新标识边界
_first = nextBuffer;
_last = nextBuffer + bufferSize;
}
return *this;
}
//--it
//即让迭代器指向前一个元素
//思路:先判断_cur == _first
//如果 _cur !=_first, 只需要--_cur
//_cur = _first, 跳转到上一个buffer的尾元素
Iterator operator--()
{
if (_cur != _first)
{
--_cur;
return *this;
}
else
{
T* preBuffer = *(--_buffer_adr_array_n);
_cur = preBuffer + bufferSize - 1;
_first = preBuffer;
_last = preBuffer + bufferSize;
return *this;
}
}
//it += 5
Iterator& operator+=(int step)
{
//即让迭代器从_cur开始 向前走step步,求得新指向位置
//思路:_cur + step 看成 _first + (_cur - _first) + step
// 让迭代器从_first开始 向前走【 (_cur - _first) + step】 步
size_t offset = _cur - _first + step;
if (offset < bufferSize)
{
//没有超过当前所在buffer
_cur += step;
}
else
{
//从_first开始,需要的步数,跨越了几个buffer
size_t bufferNum = offset / bufferSize;
//多出的步数,就是最终所在buffer的位置
size_t pos = offset % bufferSize;
_buffer_adr_array_n += bufferNum;
_cur = *_buffer_adr_array_n + pos;
_first = *_buffer_adr_array_n;
_last = *_buffer_adr_array_n + bufferSize;
}
return *this;
}
Iterator operator+(int step)
{
Iterator tmp = *this;
tmp += step;
return tmp;
}
// it2 - it1 默认it2指向后面的元素,返回两个迭代器相差的距离
size_t operator-(const Iterator& it1)
{
//如果在同一个buffer中
if (it1._buffer_adr_array_n == _buffer_adr_array_n)
{
return _cur - it1._cur;
}
else
{
//it1所在buffer的剩下元素 + it2所在buffer的剩下元素 + 之间完整buffer的所有元素
int num2 = _cur - _first;
int num1 = it1._finish - it1._cur;
int tmp = (_buffer_adr_array_n - (it1._buffer_adr_array_n + 1)) * bufferSize;
return num1 + num2 + tmp;
}
}
bool operator==(const Iterator& it1)
{
return _cur == it1._cur;
}
bool operator!=(const Iterator& it1)
{
return _cur != it1._cur;
}
bool operator<(const Iterator& it1)
{
return _buffer_adr_array_n < it1._buffer_adr_array_n;
}
bool operator>=(const Iterator& it1)
{
return !(*this < it1);
}
bool operator<=(const Iterator& it1)
{
return (*this == it1) || (*this < it1);
}
bool operator>(const Iterator& it1)
{
return !(*this <= it1);
}
Ref operator*()
{
return *_cur;
}
Ptr operator->()
{
return _cur;
}
Ref operator[](size_t n)
{
return *(*this + n);
}
};
--优缺点
优点
( 1 ) 头尾插入删除效率高,适合做stack和queue的默认底层容器
( 2 ) 支持[]随机访问
缺陷
( 1 ) [ ]的计算比vector的复杂,速度不如vector
( 2 ) 中间的插入删除仍然需要挪动数据,这方面不如链表快.
优先级队列priority_queue
仿函数
仿函数,本质是一个类,类里重载了()运算符,即
返回值 operator()(函数形参列表){}
例:下面是仿函数greater的定义
namespace yh
{
template<class T>
struct greater
{
bool operator()(const T& val1, const T& val2)const
{
return val1 > val2;
}
};
}
void testFunctor()
{
//用仿函数定义一个对象
yh::greater<int> functor;
bool ret = functor(1, 2);//本质是functor.operator()(1, 2)
std::cout << ret;
}
仿函数的对象,可以像函数一样去使用.
在优先级队列【本质是堆】中,把仿函数作为模板参数,
用户通过传仿函数less/greater,可以控制要大堆还是小堆.
一般传less,建大堆;传greater,建小堆.
优先级队列简介
它也是stl中的容器适配器,【本质是堆】.
cplusplus.com:
实现代码
堆的数据结构讲解:
在向上/向下调整算法进行比较时,使用Compare仿函数的对象,
(1) 默认把该对象当成less的,将大的数据往上换,小的数据往下调整,
(2) 默认建成大堆即可.
template<class T, class Container = std::vector<T>, class Compare = std::less<T>>
class priority_queue
{
private:
Container _con;
public:
//对自定义类型,初始化列表会去调用它的默认构造
priority_queue(){}
//用一段迭代器区间构造
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
//相当于建堆,建堆最好用向下调整算法为O(N)
while (first != last)
{
_con.push_back(*first);
++first;
}
//从倒数第一个非叶子结点开始,使用向下调整算法建堆
for (int i = (size() - 1 - 1) / 2; i >= 0; --i)
{
//size() - 1是最后一个结点位置,(size()-1 - 1) / 2是最后一个结点的父结点
adjustDown(i);
}
}
//向优先级队列插入数据,插入后保证它仍然是一个堆
//使用向上调整算法
void push(const T& val)
{
_con.push_back(val);
adjustUp(_con.size() - 1);
}
//将pos位置元素,向上调整(比较它与它的祖先结点)
void adjustUp(size_t pos)
{
static Compare compare;
size_t parent = (pos - 1) / 2;
//若pos = 0,没有父亲结点,不能再向上调整
while (pos > 0)
{
//小于建大堆,默认建大堆
if (compare(_con[parent], _con[pos]))//默认是_con[parent] < _con[pos]
{
//pos向上调整
std::swap(_con[parent], _con[pos]);
//更新pos和它的parent
pos = parent;
parent = (pos - 1) / 2;
}
else
break;
}
}
//删除堆顶数据,一般用来选出最大最小的元素
//1 交换 堆顶元素和尾元素
//2 尾删
//3 对此时堆顶数据 用向下调整算法
void pop()
{
assert(size() > 0);
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
adjustDown(0);
}
void adjustDown(size_t pos)
{
static Compare compare;
//前提是左边和右边都是大堆或小堆
//当没有左孩子时,调整结束
size_t mChild = 2 * pos + 1;
while (mChild < size())
{
if (mChild + 1 < _con.size() && compare(_con[mChild], _con[mChild + 1]) )//默认是_con[mChild] < _Con[mChild+1]
++mChild;
if (compare(_con[pos], _con[mChild]))
{
std::swap(_con[pos], _con[mChild]);
pos = mChild;
mChild = pos * 2 + 1;
}
else
break;
}
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
const T& top() const
{
return _con.front();
}
};
void testPriorityQueue()
{
int arr[] = { 9, 7, 15, 1, 86, 47, 55 };
//默认使用vector作为底层容器,默认传less建大堆,大的元素优先级高
priority_queue<int> pq(arr, arr + sizeof(arr) / sizeof(int));
//用一段迭代器区间构造pq
//不断取堆顶的数,然后pop堆顶数据,从大到小输出
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
}
反向迭代器(迭代器适配器)
void testUse6()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
}
以list的迭代器为例子:
begin()是第一个有效结点的迭代器,
end()是最后一个有效结点的下一个迭代器位置,即指向哨兵位头结点
--反向迭代器与正向迭代器的不同
( 1 ) 【反向迭代器的起点、终点】可能和正向迭代器的不同
( 2 )正向迭代器的--,就是反向迭代器的++;
反向迭代器的++,就是正向迭代器的--
--用正向迭代器构造出反向迭代器
反向迭代器与正向迭代器在实现上,只有++和--不同,
因此,反向迭代器内部可以封装正向迭代器.
反向迭代器也称为 【 迭代器适配器 】.
//提供list的迭代器,就能适配出list的反向迭代器
//提供vector的迭代器,就能适配出vector的反向迭代器
namespace yh
{
template<class Iterator, class Ref, class Ptr>
class Reverse_Iterator
{
//成员属性
Iterator _it;
typedef Reverse_Iterator<Iterator,Ref, Ptr> RIterator;
Reverse_Iterator(Iterator it)
:_it(it)
{}
RIterator& operator++()
{
//反向迭代器++,就--封装的_it
--_it;
return *this;
}
RIterator& operator--()
{
//反向迭代器--,就++封装的_it
++_it;
return *this;
}
//其它运算符实现,直接复用正向迭代器即可
Ref operator*()
{
return *_it;
}
Ptr operator->()
{
return _it.operator->();
}
bool operator!=(const RIterator& rit)
{
return _it != rit._it;
}
bool operator==(const RIterator& rit)
{
return _it == rit._it;
}
};
}
--stl中的反向迭代器设计
结论
在list内部,
反向迭代器的起点 就是 正向迭代器的终点
反向迭代器的终点 就是 正向迭代器的起点
实现方式
stl反向迭代器所在的文件是
current就是封装的正向迭代器.
仍然以链表为例子:
观察得到:反向迭代器真正指向的数据,在前一个位置,所以只需要修改*的返回值即可.
Ref operator*()
{
//实现 反向迭代器的rbegin() 就是 正向迭代器的end()
// 反向迭代器的rend() 就是 正向迭代器的begin()
// 反向迭代器所指的位置 其实是 前一个元素
return *(_it - 1);
}
完整模拟实现反向迭代器代码
namespace yh
{
template<class Iterator, class Ref, class Ptr>
class Reverse_Iterator
{
private:
//成员属性
Iterator _it;
typedef Reverse_Iterator<Iterator,Ref, Ptr> RIterator;
public:
Reverse_Iterator(Iterator it)
:_it(it)
{}
RIterator& operator++()
{
//反向迭代器++,就--封装的_it
--_it;
return *this;
}
RIterator& operator--()
{
//反向迭代器--,就++封装的_it
++_it;
return *this;
}
Ref operator*()const
{
//实现 反向迭代器的rbegin() 就是 正向迭代器的end()
// 反向迭代器的rend() 就是 正向迭代器的begin()
// 反向迭代器所指的位置 其实是 前一个元素
Iterator tmp = _it;
--tmp;
return *tmp;
}
Ptr operator->()
{
return &(operator*());
}
bool operator!=(const RIterator& rit)
{
return _it != rit._it;
}
bool operator==(const RIterator& rit)
{
return _it == rit._it;
}
};
}
在list内部的代码:
typedef Reverse_Iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_Iterator<const_iterator, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin());
}