一、stack
#include <deque>
using namespace std;
template<class T, class Container = deque<T>> //deque为默认底层容器
class Stack{
Container _con; //内部封装了底层容器
public:
//提供一组特定的成员函数访问其成员,以此来体现数据结构的特性。
Stack(){}
template<class InputIterator>
Stack(InputIterator first, InputIterator last)
{
while(first != last)
{
_con.push_back(*first);
++first;
}
}
void push(const T &val) //弹栈
{
_con.push_back(val);
}
void pop() //压栈
{
_con.pop_back();
}
T& top() //取栈顶元素
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
bool empty() const{
return _con.empty();
}
size_t size() const{
return _con.size();
}
};
二、queue
#include <deque>
using namespace std;
template<class T, class Container=deque<T>> //deque为默认底层容器
class Queue{
Container _con;
public:
Queue(){} //需要提供默认构造函数
template<class InputIterator>
Queue(InputIterator first, InputIterator last)
{
while(first != last)
{
_con.push_back(*first);
++first;
}
}
void push(const T& val) //队尾入队
{
_con.push_back(val);
}
void pop() //队头出队
{
_con.pop_front();
}
T& front(){ //取队头元素
return _con.front();
}
T& back(){ //取队尾元素
return _con.back();
}
const T& front() const{
return _con.front();
}
const T& back() const{
return _con.back();
}
bool empty(){
return _con.empty();
}
size_t size(){
return _con.size();
}
};
三、priority_queue
#include <vector>
using namespace std;
//此处定义了两个仿函数/函数对象,用于比较大小:
template <class T>
struct Less{
bool operator()(const T& l, const T& r) const{
return l < r;
}
};
template <class T>
struct Greater{
bool operator()(const T& l, const T& r) const{
return l > r;
}
};
//模拟实现优先级队列:
template<class T, class Container=vector<T>, class Compare = Less<T>> //vector为默认底层容器,less为默认仿函数
class Priority_queue{
Container _con; //底层容器
Compare _com; //函数对象
public:
Priority_queue(){}
template<class InputIterator>
Priority_queue(InputIterator first, InputIterator last)
{
while(first != last)
{
_con.push_back(*first);
++first;
}
//从最后一个非叶子节点开始向下调整建堆
for(int i = (_con.size()-2)/2; i>=0; --i)
{
adjust_down(i);
}
}
void push(const T& val)
{
_con.push_back(val);
adjust_up(_con.size()-1);
}
//向上调整
void adjust_up(size_t child)
{
size_t parent = (child-1)/2;
while(child > 0)
{
//if(_con[child] > _con[parent])
if(_com(_con[parent], _con[child])) //利用函数对象进行比较
{
std::swap(_con[child], _con[parent]);
child = parent;
parent = (child-1)/2;
}
else
{
break;
}
}
}
void pop()
{
std::swap(_con[0], _con[_con.size()-1]);
_con.pop_back();
adjust_down(0);
}
//向下调整
void adjust_down(size_t parent)
{
size_t child = parent*2+1;
while(child < _con.size())
{
if(child+1<_con.size() && _com(_con[child], _con[child+1]))
{
++child;
}
if(_com(_con[parent], _con[child]))
{
std::swap(_con[child], _con[parent]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
const T& top() const{ //取堆顶元素,此处返回值必须加const不允许修改,否则会破坏堆结构
return _con.front();
}
bool empty() const{
return _con.empty();
}
size_t size() const{
return _con.size();
}
};
四、reverse_iterator
4.1 反向迭代器的模拟实现
如何实现反向迭代器?
- 反向迭代器的起点和终点和正向迭代器相反。
- 反向迭代器的遍历移动方向和正向迭代器相反。
- 解引用*和成员访问->操作相同。
综上,由于反向迭代器的方法类似于正向迭代器,只是方向相反。我们可以将反向迭代器设计成适配器,底层分装一个正向迭代器。
复用正向迭代器的成员函数,翻转调用其++和--的重载函数即可:
template <class iterator, class Ref, class Ptr>
class Reverse_iterator{
iterator _it; //内部封装了一个正向迭代器
public:
Reverse_iterator(const iterator &it) //用一个正向迭代器进行构造
:_it(it)
{}
Reverse_iterator& operator++(){
--_it; //翻转调用其++和--的重载函数
return *this;
}
Reverse_iterator& operator--(){
++_it;
return *this;
}
Reverse_iterator operator++(int){
Reverse_iterator tmp(*this);
--_it;
return tmp;
}
Reverse_iterator operator--(int){
Reverse_iterator tmp(*this);
++_it;
return tmp;
}
bool operator!=(const Reverse_iterator &rit) const {
return _it != rit._it;
}
bool operator==(const Reverse_iterator &rit) const {
return _it == rit._it;
}
//......
};
现在我们要解决起点和终点的问题了:
vector的反向迭代器起点和终点:
list的反向迭代器起点和终点:
我们可以看到,源码中采用了一个对称的方案:反向的起点rbegin就是正向的终点end;反向的终点rend就是正向的起点begin;
我们跟随源码也使用这种方案进行设计:
template<class T>
class Myvector{
public:
//正向迭代器
typedef T *iterator;
typedef const T *const_iterator;
//反向迭代器
//第一个模版参数是迭代器类型,适用于多种迭代器
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
//后两个模版参数用于解决const对象的迭代器访问问题
typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
iterator begin(){
return _start;
}
iterator end(){
return _finish;
}
reverse_iterator rbegin(){
return reverse_iterator(end());
}
reverse_iterator rend(){
return reverse_iterator(begin());
}
//......
};
template <class T>
class Mylist{
typedef list_node<T> Node;
Node *_phead;
public:
//正向迭代器
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
//反向迭代器
//第一个模版参数是迭代器类型,适用于多种迭代器
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
//后两个模版参数用于解决const对象的迭代器访问问题
typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
iterator begin(){
return iterator(_phead->_next);
}
iterator end(){
return iterator(_phead);
}
reverse_iterator rbegin(){
return reverse_iterator(end());
}
reverse_iterator rend(){
return reverse_iterator(begin());
}
//......
};
使用此方案设计出的迭代器不能够直接进行解引用,因为rbegin指向一段非法空间直接解引用会导致越界访问。
因此要进行错位访问,即访问迭代器指向的前一个位置:
template <class iterator, class Ref, class Ptr>
class Reverse_iterator{
iterator _it; //内部封装了一个正向迭代器
public:
//......
Ref operator*() const{
iterator tmp = _it; //需要临时拷贝,解引用操作不能移动迭代器
return *--tmp; //访问迭代器的上一个位置
}
Ptr operator->() const{
return &(operator*()); //复用operator*()
}
};
4.2 迭代器的分类
所有的迭代器都能适配出反向迭代器吗?不一定!
迭代器器的分类:
迭代器种类 | 迭代器功能 | 对应容器的迭代器 |
---|---|---|
正向迭代器 forward_iterator | ++ | <forward_list> <unordered_map> <unordered_set> |
双向迭代器 bidirectional_iterator | ++ -- | <list> <map> <set> |
随机访问迭代器 random_access_iterator | ++ -- + - | <vector> <deque> |
只有支持++ 和 --操作的迭代器才能适配出反向迭代器。即双向迭代器和随机访问迭代器可以,而正向迭代器不可以。
迭代器是数据结构和算法的粘合剂。不同的算法对迭代器功能的要求也是不同的,比如:
- find函数模版的模版参数是InputIterator,没有指明迭代器的种类表示适用于所有迭代器。
- reverse函数模版的模版参数是BidirectionalIterator,表示使用reverse进行逆置的迭代器至少需要是双向迭代器,当然随机迭代器也行。
- sort函数模版的模版参数是RandomAccessIterator,表示使用sort进行排序的迭代器必须是随机迭代器。
五、阶段小结
STL六大组件我们已经了解学习了其中的5个,现在做一个小小的总结:
- 容器:就是将数据结构和对应数据结构的方法封装在一起。容器一般是类模版,可以存储不同类型的数据。
- 算法:通过迭代器遍历访问容器,并对容器中的数据进行排序、逆置等操作。一般是函数模版,可以用于多种容器。
- 迭代器:迭代器是几乎所有容器都支持的遍历访问方式,且各容器迭代器的使用方法相同。迭代器是数据结构和算法的粘合剂,通用算法需要通过迭代器访问修改不同容器中的数据。
- 适配器/配接器:分为容器适配器和迭代器适配器。总的来说,适配器就是将原有的容器、迭代器等进行封装,并提供一组特定的成员函数来访问其元素,以此来适配出新的容器或迭代器。这种适配器的设计模式增强了代码的复用性;并且同一个适配器可以采用多种底层结构以适应实际的应用需求;
- 仿函数:又叫函数对象。其实就是重载了operator()的类,仿函数实例化出的对象可以像函数一样使用,用于替代函数指针。
通过这段时间的学习和以上的总结,我们可以看出模版,类封装在C++中发挥的巨大作用:
- 模版使我们的代码摆脱了数据类型的束缚,同一份代码适用于多种数据类型。
- 类将底层结构和方法封装在一起,使用时只需要调用其成员函数即可不需要关注其底层。我们甚至可以进行多层封装如适配器。