前言
本篇博客主要会给大家讲解C++的一个代码复用的重要方式——适配器模式,并且详细讲解stl是如何运用这中设计理念来实现
reverse_iterator
的,给出了模拟实现方式
适配器(Adapter)
作为stl六大组件之一,适配器也是stl中重要的一部分,那么,适配器是什么呢?
适配器就是接口,对容器、迭代器、算法进行包装,但其实质还是容器、迭代器和算法,只是不依赖于具体的标准容器、迭代器和算法类型。概念源于设计模式中的适配器模式:将一个类的接口转化为另一个类的接口,使原本不兼容而不能合作的类,可以一起运作。
适配器模式的设计模式的出现,为开发带来了极大的便利,提供了一个极好的代码复用的手段,在stl中,适配器主要分为三类:容器适配器,迭代器适配器,仿函数适配器,本篇博客将简单的介绍容器适配器和迭代器适配器。
容器适配器
应用于容器,容器适配器包括:stack
,queue
,priority_queue
我们就拿stack和queue来举例,首先我们可以先看看c++文档中对这两个适配器的声明
这两个适配器容器相对于普通容器来说最大的区别就是用了第二个模板参数,而这个模板参数的默认参数是deque容器,也就是说这两个容器是在模板提供的容器的前提下进行的设计,也就是说传入什么容器就会生成底层是什么容器的栈和队列,如下:
#include<stack>
using namespace std;
int main()
{
//默认生成用deque设立的栈
stack<int> st1;
//生成底层为vector的栈
stack<int, vector<int>> st2;
return 0;
}
那么具体是怎么实现的呢,可以看下面模拟实现的代码:
//模拟实现stl_queue
#pragma once
#include<deque>
namespace lzz
{
template<typename T, typename container = deque<T>>
class queue
{
private:
container _con;
public:
queue() {}
void push(const T& val) { _con.push_back(val); }
void pop() { _con.pop_front(); }
T& back() { return _con.back(); }
const T& back() const { return _con.back(); }
T& front() { return _con.front(); }
const T& front() const { return _con.front(); }
size_t size() const { return _con.size(); }
bool empty() const { return _con.empty(); }
};
}
///
//模拟实现stl_stack
#pragma once
#include<deque>
namespace lzz
{
template<typename T, typename container = std::deque<T>>
class stack
{
private:
container _con;
public:
//自定义类型自动调用构造和析构
stack() {}
void push(const T& val) { _con.push_back(val); }
void pop() {_con.pop_back(); }
T& top() { return _con.back(); }
size_t size() { return _con.size(); }
bool empty() { return _con.empty(); }
};
}
通过模拟实现,我们就可以发现其实容器适配器就是利用stl的容器接口再次进行上层封装得来的,通过上层封装能够做到忽略一些底层的细节,实现代码复用。
迭代器适配器——reverse_iterator
reverse_iterator,故名思意就是反向迭代器,大家仔细了解就能够知道,反向迭代器的++就是正向迭代器的–,反向迭代器的–就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即使用适配器模式进行设计,即:反向迭代器内部都可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。
首先,我们先来分析一下如何设计reverse_iterator
和const_reverse_iterator
,需要写两份代码吗?首先我们来回顾一下iterator
和const_iterator
是如何设计的:
template<typename T, typename Ref, typename Ptr>
struct __list_iterator
{
//...
}
具体可以看下面的文章链接:
stl_list模拟实现
其中有具体的iterator的设计思路和代码
是通过传三个参数来进行控制的,所以在设计reverse_iterator时,我们也可以采取这种方式,下面是reverse_iterator的整体框架:
template<typename Iterator, typename Ref, typename Ptr>
class Reverse_iterator
{
//...
}
//通过如此设计反向迭代器,所有容器的反向迭代器都可以通过传入对应正向迭代器并进行typedef实现
//list
typedef Reverse_iterator<__list_iterator, T&, T*> reverse_iterator
typedef Reverse_iterator<__const_list_iterator, T&, T*> const_reverse_iterator
//
//vector
typedef Reverse_iterator<__vector_iterator, T&, T*> reverse_iterator
typedef Reverse_iterator<__const_vector_iterator, T&, T*> const_reverse_iterator
具体逻辑
stl在设计反向迭代器时,采用了镜像对称的方式进行设计,也就时说,rbegin()
指向的地方就是end()
指向的地方,rend()
指向的地方就是begin()
指向的地方。
但是这也出现了一个问题,如果解引用,由于镜像对称的原因如果按照正常的解引用操作拿到的数据是错误的,需要拿到前一个迭代器指向的数据才能正确对应关系,所以reverse_iterator的operator*()和operator->()函数需要重新设计,如下:
//其中先构造一个tmp的原因是不能直接修改_it,而要获得前一个数据只能先创建一个临时变量
Ref& operator*()
{
Iterator tmp(_it);
return *--tmp;
}
Ptr operator->()
{
Iterator tmp(_it);
--tmp;
return &(tmp.operator*());
}
接着对反向迭代器进行++就是–,对正向迭代器进行–就是++,具体逻辑就是这样了。
完整代码
#pragma once
namespace lzz
{
template<typename Iterator, typename Ref, typename Ptr>
class Reverse_iterator
{
private:
Iterator _it;
typedef Reverse_iterator<Iterator, Ref, Ptr> self;
public:
Reverse_iterator() {}
Reverse_iterator(const Iterator& it) { _it = it; }
//由于使用了镜像对称,所以应该是返回他的上一个数据
Ref& operator*()
{
Iterator tmp(_it);
return *--tmp;
}
Ptr operator->()
{
Iterator tmp(_it);
--tmp;
return &(tmp.operator*());
}
self& operator++()
{
--_it;
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!=(const self& rit) { return _it != rit._it; }
};
}
总结
通过这篇博客,相信大家对适配器模式的这种设计思路有了一定的理解,当然若要想有更深的理解,离不开不断的实践!本篇博客到此结束,如果博主有一些错误或者大家有不懂的地方欢迎大家评论区指出!