【ONE·C++ || stack queue (一)】
总言
主要介绍栈和队列的基本函数使用:栈和队列、优先级队列、适配器、反向迭代器。
文章目录
- 总言
- 1、栈和队列接口基本介绍
-
- 1.1、基本介绍
- 1.2、相关例题
-
- 1.2.1、最小栈
- 1.2.2、栈的压入、弹出序列
- 1.2.3、逆波兰表达式求值
- 2、适配器介绍
-
- 2.1、引入:如何实现一个栈/队列(1.0版本)
- 2.2、适配器:如何实现一个栈/队列(2.0版本)
- 2.3、deque双端队列简单介绍
- 3、优先级队列priority_queue
-
- 3.1、基本介绍
- 3.2、模拟实现
-
- 3.2.1、1.0:基础框架实现
- 3.2.2、2.0:加入仿函数
- 4、反向迭代器相关
-
- 4.1、基本介绍
- 4.2、框架实现
-
- 4.2.1、框架搭建1.0:非对称性
- 4.2.2、框架搭建2.0:对称性
- 4.3、与适配器融合理解
-
- 4.3.1、通用性说明
- 4.3.2、迭代器类型简单介绍
1、栈和队列接口基本介绍
1.1、基本介绍
1)、总言
栈:相关链接
队列:相关链接
栈和队列我们在基础数据结构|| 栈和队列中,使用C语言实现过,这里相关接口大差不差,比如pop删除、push插入、empty判空,size获取个数等等。
那么,SQL中栈和队列与之前学习的vector、list间有什么区别呢?
通过观察可以发现stack和queue中相对而言接口较少,也没有各类迭代器,这是为了让stack/queue符合其后进先出/先进先出的特性。
此外,二者属于容器适配器: a type of container adaptor。
template <class T, class Container = deque<T> >
class stack;template <class T, class Container = deque<T> >
class queue;
template < class T, class Alloc = allocator<T> >
class vector; template < class T, class Alloc = allocator<T> >
class list;
2)、使用演示
以下为使用库中stack和queue的基本演示:
void test_std_01()
{stack<int> st1;//构造st1.push(1);//stack::pushst1.push(2);st1.push(3);st1.push(4);st1.push(5);st1.push(6);cout << "stack_size:" << st1.size() << endl;//stack::while (!st1.empty())//stack::empty{cout << st1.top() << " ";//stack::topst1.pop();//stack::pop}cout << endl;
}
void test_std_02()
{queue<int> qu1;//构造qu1.push(1);//queue::pushqu1.push(2);qu1.push(3);qu1.push(4);qu1.push(5);qu1.push(6);cout << "stack_size:" << qu1.size() << endl;//queue::sizewhile (!qu1.empty())//queue::empty{cout << qu1.front() << " ~ " << qu1.back() << endl;//queue::front、queue::backqu1.pop();//queue::pop}cout << endl;
}
1.2、相关例题
以下为一些与栈和队列有关的练习,旨在帮助我们熟悉理解相关结构功能。
1.2.1、最小栈
相关链接
分析:
根据题目,本题相当于要实现栈的常见基本接口,相比之下只是多出一个int getMin()
。注意题目要求,常数时间内获取堆栈中的最小元素,即该接口要保持时间复杂度为O(1)。那么,该如何实现呢?
首先,可以考虑到的是,C++中有各种容器,与其像C一样内部需要我们自己实现,这里相对来说便捷的选择是复用SQL,即使用库里的stack进行封装。
这里有一个方案如下: 在MinStack
中建立两个成员变量,一个为std::stack
类,用于进行各类pop、push等。另一个为普通变量
,用于辅助记录栈中最小元素。
class MinStack {
public://……private:stack<int> _st;int _minval;
};
思考: 此方案是否可行?
回答: 这里存在一个问题,假如当前记录的最小元素出栈,那么原先存储在_minval
中的最小值将失效。此时,如果要用getMin
提取最小元素,那么我们需要遍历栈重新寻找最小值,这样整体下来时间复杂度不符合要求。
改进:既然如此,该如何实现?
另一方案如下: 新增一个辅助栈_minst
。该辅助栈与插入栈_st
同步,其作用是记录插入栈_st
中插入的最小元素,若该元素出栈,则对应_minst
也同步出栈。
代码实现如下:
class MinStack {
public:MinStack() {}void push(int val) {st.push(val);if(minst.empty() || val<=minst.top())//1、注意_minst元素为空时,要插入;2、此处<=是因为存在多组重复最小数,比如3 1 1 2 5 ,这种情况下假如1只入_minst一次,那么_st删除1时,_minst同步删除后,最小值有误。minst.push(val);}void pop() {if(!minst.empty()&& !st.empty() &&st.top()==minst.top())minst.pop();assert(!st.empty());//此处可不写,因为正常使用库中stack,st.pop中会检测st.pop();}int top() {return st.top();}int getMin() {return minst.top();}
private:stack<int> st;stack<int> minst;
};
注意事项:
①、MinStack()
构造函数需要我们手动写吗?
回答:不需要。其一,这里我们可以直接将该接口删除,这时候会生成默认构造函数,其对内置类型不做处理,对自定义类型会调用它自己的构造函数。这里MinStack
的类成员恰好是自定义类型。其二,即使将该接口保留但什么也不做,这样也是可行的,因为自定义类型成员变量一定会先进过初始化列表初始化。
②if(minst.empty() || val<=minst.top())
minst.empty()
:_minst元素为空时,要插入;
val<=minst.top()
:此处<=是因为存在多组重复最小数,比如3 1 1 2 5 ,这种情况下假如1只入_minst一次,那么_st删除1时,_minst同步删除后,最小值有误。
扩展延伸:
问题:假如我们需要插入大量重复数据(特点:数据量很大,数据中重复数多),上述方法是否存在问题,可以如何改进?
回答:在该情况下,如果我们直接使用上述方法,从空间角度上讲消耗很大。一个解决方案是对_minst做改进,添加一个计数器:
struct conutval
{int val;int count;
}stack<int> st;
stack<countval> minst;
1.2.2、栈的压入、弹出序列
相关链接
分析:从逻辑角度判断某个出栈方式是否正确这很容易,但如何用代码控制实现该过程呢?以下给出了一种思路:模拟出栈过程。
class Solution {
public:bool IsPopOrder(vector<int> pushV,vector<int> popV) {stack<int> st;//辅助栈:用于模拟出栈过程int popi=0;//用于遍历popV该vector的下标变量for(auto pushVal:pushV)//遍历pushV中数据{st.push(pushVal);//无论结果如何都将pushV中数据插入栈中while(!st.empty() && popV[popi]==st.top())//比较的是辅助栈和popV,使用while是为了持续匹配{popi++;st.pop();}}return st.empty();}
};
1.2.3、逆波兰表达式求值
相关链接:关于波兰表达式(前缀表达式)和逆波兰表达式(后缀表达式)的两个词条介绍。
题目链接
问题: 后缀表达式是什么,如何运算?
后缀表达式是一种把操作数写在前面,把运算符写在后面的表示方法。例如:我们日常写的中缀表达式1+2*2
,在转换为后缀表达式则为1 2 2 * +
。
对于运算:需要借助一个栈。1、若遇到的是操作数,我们将其入栈,若遇到的是操作符,则连续取两次栈顶元素,与当前操作符进行运算,最后再将得到结果入栈。
实现如下:
class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(auto& str:tokens){if(str=="+"||str=="-"||str=="*"||str=="/")//若为操作符,取栈中元素运算{//取值int rightval=st.top();st.pop();int leftval=st.top();st.pop();//运算switch(str[0]){case '+':st.push(leftval+rightval);break;case '-':st.push(leftval-rightval);break;case '*':st.push(leftval*rightval);break;case '/':st.push(leftval/rightval);break;}}else//若为操作数,则一律入栈{st.push(stoi(str));}}return st.top();//最后,将栈顶结果返回}
};
注意事项:
①if(str=="+"||str=="-"||str=="*"||str=="/")
:这里实际上是string::operator==
的运用。使用字符串而非字符是因为给定的数据是string,可能存在“-23”
这类负数情况,需要与减号“-”
区分。
②switch(str[0])
:switch…case语句需要输入整型表达式,故而我们不可以直接使用switch(str)
来判断,这里取了其首字符,这是因为这里的操作符+、、*、/
本身即单字符。
2、适配器介绍
2.1、引入:如何实现一个栈/队列(1.0版本)
1)、模拟实现栈1.0
在C语言中我们曾从零开始模拟实现过栈,此处C++中如果我们自己手动实现一个效果大差不差,故这里。而C++中有各类容器,我们可以封装一下vector,用其来模拟实现stack。效果如下:
这是需要的基本接口:
//C++98:
void push (const value_type& val);
void pop();value_type& top();
const value_type& top() const;size_type size() const;
bool empty() const;
以下为模拟实现:
namespace My_Stack_Queue
{template<class T>class stack{public: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();}size_t size()const{return _con.size();}bool empty()const{return _con.empty();}private:vector<T> _con;};
}
验证结果如下:
void test_stack_01()
{My_Stack_Queue::stack<int> st1;//构造st1.push(1);//stack::pushst1.push(2);st1.push(3);st1.push(4);st1.push(5);st1.push(6);cout << "stack_size:" << st1.size() << endl;//stack::sizewhile (!st1.empty())//stack::empty{cout << st1.top() << " ";//stack::topst1.pop();//stack::pop}cout << endl;
}
2.2、适配器:如何实现一个栈/队列(2.0版本)
1)、模拟实现栈2.0
观察库里的stack、queue和list、vector,可看到这里有一个Container
,实际上这是一种容器适配器。适配器是一种设计模式,该种模式是将一个类的接口转换成希望达成的另外一个接口。其作用是能让我们按照自己的需求转换接口。
我们实现的stack1.0版本中,其默认底层封装的是vector,相当于我们将其接口写死固定为vector,这样就不能做到上述描述的任意转换(根据之前所学,栈可以用vector数组结构实现,也可以用list链表结构实现)。
那么,要做到转换自如,即适配器模式,该如何调整这里的模拟实现呢?
一个简单的方法是使用模板,别把接口写死。
class Container = deque<T>
,添加一个模板参数。这里为其赋予了一个缺省参数deque,也是SQL中的一种双开口的"连续"空间的数据结构,我们模拟实现时也依照库里的来:
template<class T, class Container = deque<T>>class stack{public://……//此处实现不变private:Container _con;};
如下图,我们只需要控制模板实例化时的参数,就能得不同封装下的stack:class Container = deque<T>
,我们使用哪一个容器并不重要,只要其能实现stack的相关功能即可(底层角度不同,但上层使用相同)。比如这里,使用vector或者list都能适配出stack。
总体效果如下:
template<class T, class Container = deque<T>>class stack{public: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();}size_t size()const{return _con.size();}bool empty()const{return _con.empty();}private:Container _con;};
void test_stack_01()
{My_Stack_Queue::stack<int,vector<int>> st1;//构造st1.push(1);//stack::pushst1.push(2);st1.push(3);st1.push(4);st1.push(5);st1.push(6);cout << "stack_size:" << st1.size() << endl;//stack::sizewhile (!st1.empty())//stack::empty{cout << st1.top() << " ";//stack::topst1.pop();//stack::pop}cout << endl;My_Stack_Queue::stack<int, list<int>> st2;//构造st2.push(1);//stack::pushst2.push(2);st2.push(3);st2.push(4);st2.push(5);st2.push(6);cout << "stack_size:" << st2.size() << endl;//stack::sizewhile (!st2.empty())//stack::empty{cout << st2.top() << " ";//stack::topst2 .pop();//stack::pop}cout << endl;
}
2)、模拟实现队列2.0
同理,我们可以实现队列,以下是基本的接口:
//C++98
void push (const value_type& val);
void pop();value_type& front();
const value_type& front() const;value_type& back();
const value_type& back() const;size_type size() const;
bool empty() const;
实现如下:
template<class T,class container=deque<T>>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();}const T& front()const{return _con.front();}const T& back()const{return _con.back();}size_t size()const{return _con.size();}bool empty()const{return _con.empty();}private:container _con;};
测试如下:
void test_queue_01()
{My_Stack_Queue::queue<int> qu1;//构造qu1.push(1);//queue::pushqu1.push(2);qu1.push(3);qu1.push(4);qu1.push(5);qu1.push(6);cout << "stack_size:" << qu1.size() << endl;//queue::sizewhile (!qu1.empty())//queue::empty{cout << qu1.front() << " ~ " << qu1.back() << endl;//queue::front、queue::backqu1.pop();//queue::pop}cout << endl;My_Stack_Queue::queue<int, list<int>> qu2;//构造qu2.push(1);//queue::pushqu2.push(2);qu2.push(3);qu2.push(4);qu2.push(5);qu2.push(6);cout << "stack_size:" << qu2.size() << endl;//queue::sizewhile (!qu2.empty())//queue::empty{cout << qu2.front() << " ~ " << qu2.back() << endl;//queue::front、queue::backqu2.pop();//queue::pop}cout << endl;
}
问题: queue能否使用vector适配?
回答:根据我们上述实现的方法,由于存在push.front,该接口vector无法提供,故不能使用其进行queue的适配。这里使用库里的vector和queue也会报错:即不支持。
std::queue<int, vector<int>> qu2;//构造qu2.push(1);//queue::pushqu2.push(2);qu2.push(3);qu2.push(4);qu2.push(5);qu2.push(6);cout << "stack_size:" << qu2.size() << endl;//queue::sizewhile (!qu2.empty())//queue::empty{cout << qu2.front() << " ~ " << qu2.back() << endl;//queue::front、queue::backqu2.pop();//queue::pop}cout << endl;
总结: 适配器的引入一个便捷之处在于,假如我们不想用库提供的这些类,我们也可以根据自己的需求写一个封装进去。
2.3、deque双端队列简单介绍
相关链接
3、优先级队列priority_queue
3.1、基本介绍
1)、priority_queue的基础介绍
相关链接
基本说明: 优先级队列默认使用vector
作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue
就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue
。注意:默认情况下priority_queue
是大堆。
关于优先级高低问题: 虽然默认是大堆,但判断优先级高低需要根据实际场景而定,不同类型,比如int、char所获得结果不同。
2)、使用演示
其构造函数如下:
explicit priority_queue (const Compare& comp = Compare(),const Container& ctnr = Container());template <class InputIterator>priority_queue (InputIterator first, InputIterator last,const Compare& comp = Compare(),const Container& ctnr = Container());
代码演示一:
void test_priority_queue_01()
{priority_queue<int> pq;pq.push(3);pq.push(1);pq.push(6);pq.push(2);pq.push(5);pq.push(7);pq.push(0);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}
}
验证如下:可以看到默认其建立的是大堆。
根据上述可知,priority_queue同样没有迭代器,不过我们构造时可使用一段范围区间构造:InputIterator first, InputIterator last
。例子如下:
int a[] = { 3,1,6,2,5,7,0,8,4,9 };priority_queue<int> pq2 = { a,a + sizeof(a) / sizeof(int) };while (!pq2.empty()){cout << pq2.top() << " ";pq2.pop();}
3)、一道例题:
相关链接
代码如下:
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {//建堆:O(N)priority_queue<int> maxHeap(nums.begin(),nums.end());//取前K-1个数:O(logN*k)while(--k)//此写法下要用前置自减{maxHeap.pop();}//取第K个数return maxHeap.top();}
};
3.2、模拟实现
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> >
class priority_queue;
3.2.1、1.0:基础框架实现
我们先实现一个基础版本,根据先前所属,优先级队列从物理角度空间连续,从逻辑角度其底层实际是堆。因此,我们需要将给定的数据构成堆的结构即可。
priority_queue
中基本成员变量:
template<class T, class container = vector<T>>class priority_queue{public://……private:container _con;};
1)、priority_queue::push、adjust_up
//C++98
void push (const value_type& val);
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]){std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}
2)、priority_queue::pop、adjust_down
void pop(){std::swap(_con.front(), _con.back());//首位数据交换_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() && _con[child + 1] > _con[child])//修正左右孩子{child++;}if (_con[child] > _con[parent]){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
3)、priority_queue::top
//C++98
const value_type& top() const;
const T& top()const{return _con[0];}
4)、迭代器区间构造
template <class InputIterator>priority_queue(InputIterator first, InputIterator last){//插数while (first != last){_con.push_back(*first);++first;}//自下而上建堆:向下调整函数for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i){adjust_down(i);}}
5)、priority_queue::empty、priority_queue::size
//C++98
bool empty() const;
size_type size() const;
bool empty()const{return _con.empty();}size_t size()const{return _con.size();
6)、结果验证
void test_my_priority_queue_01()
{my_priority_queue::priority_queue<int, vector<int>> pq;pq.push(3);pq.push(1);pq.push(6);pq.push(2);pq.push(5);pq.push(7);pq.push(0);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;int a[] = { 3,1,6,2,5,7,0,8,4,9 };my_priority_queue::priority_queue<int,vector<int>> pq2 = { a,a + sizeof(a) / sizeof(int) };while (!pq2.empty()){cout << pq2.top() << " ";pq2.pop();}}
3.2.2、2.0:加入仿函数
在上述1.0版本的实现中,我们建立起的是大堆,如果需要小堆则要求我们手动调整adjust_down
和adjust_up
中的关系运算符。而实际中手动去修改priority_queue内部结构不现实。那么,有什么解决方案呢?
回想C中我们学习qsort时,也涉及这样的比较,在那里我们使用的是函数指针compar,C++中,相比于函数指针,我们使用的是仿函数。
1)、仿函数基本演示
仿函数是什么: 通俗来讲,仿函数也称为函数对象,本质是一个类,可重载运算符operator()
,那么 类对象可以像函数一样去使用。
演示如下:
namespace fun
{template<class T> //使用了类模板定义类型:泛型struct less//也可以使用class,但需要注意public该运算符重载{bool operator()(const T& A, const T& B)const //()是可以进行运算符重载的{return A < B; //这里less小于,根据需求我们返回小于号}};template<class T>struct greater{bool operator()(const T& A, const T& B)const{return A > B;}};
}
验证如下:
void test_fun_01()
{fun::greater<int> lsFunc1;cout << "lsFunc1(2, 1): " << lsFunc1(2, 1) << endl;cout << "lsFunc1(4, 5): " << lsFunc1(4, 5) << endl;fun::less<int> lsFunc2;cout << "llsFunc2(2, 8): " << lsFunc2(2, 8) << endl;cout << "llsFunc2(9, 1): " << lsFunc2(9, 1) << endl;
}
2)、在优先级队列中加入仿函数
观察库中priority_queue
,其默认给的仿函数是大堆--less
,小堆--greater
,我们在此基础上修改1.0中模拟实现。
template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> >
class priority_queue;
相关代码如下:整体逻辑不变,我们只是加入了两个结构体定义的仿函数,并修改调整adjust_down
和adjust_up
中的写法:
namespace my_priority_queue
{template<class T>struct less{bool operator()(const T& left, const T& right)const{return left < right;}};template<class T>struct greater{bool operator()(const T& Aleft, const T& right)const{return left > right;}};template<class T, class container = vector<T>, class Compare = less<T>>class priority_queue{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() - 1 - 1) / 2; i >= 0; --i){adjust_down(i);}}void push(const T& val){_con.push_back(val);//先尾插adjust_up(_con.size() - 1);//为了保持堆特性:使用向上调整函数}void pop(){std::swap(_con.front(), _con.back());//首位数据交换_con.pop_back();//尾删adjust_down(0);//向下调整头部元素到适合位置}const T& top()const{return _con[0];}bool empty()const{return _con.empty();}size_t size()const{return _con.size();}private:void adjust_up(size_t child){Compare com;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 adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){//大堆--less//if (child + 1 < _con.size() && _con[child + 1] > _con[child])//修正左右孩子if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){child++;}//if (_con[child] > _con[parent])if (com(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}container _con;};
}
写法一:Compare com
void adjust_up(size_t child){Compare com;size_t parent = (child - 1) / 2;while (child > 0)//默认大堆--less{//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 adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){//大堆--less//if (child + 1 < _con.size() && _con[child + 1] > _con[child])//修正左右孩子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;}}}
写法二:匿名对象
void adjust_up(size_t child){size_t parent = (child - 1) / 2;while (child > 0)//默认大堆{//if (_con[child] > _con[parent])if (Compare()(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(size_t parent){size_t child = parent * 2 + 1;while (child < _con.size()){//大堆--less//if (child + 1 < _con.size() && _con[child + 1] > _con[child])//修正左右孩子if (child + 1 < _con.size() && Compare()(_con[child], _con[child + 1])){child++;}//if (_con[child] > _con[parent])if (Compare()(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
4、反向迭代器相关
4.1、基本介绍
1)、正向迭代器、反向迭代器对比
在list模拟实现中,我们遗留下一个问题,关于反向迭代器的模拟实现。
先来看看二者区别:
以上是我们认知的begin(有效首元素)、end(有效尾元素的下一个位置)、rbegin(反向有效首元素)、rend(反向有效尾元素的下一个位置)的相关指向。
以list
举例,我们已经实现过正向的迭代器,那么其反向迭代器该如何实现?
观察正向、反向迭代器区别,其需要实现的运算符重载大差不差,只是对于正向迭代器,operator++
是向后挪动,而反向迭代器恰好与之相反,operator--
同理。其余operator*
、operator->
所得结果一致。
//++ititerator& operator++(){_node = _node->_next;return *this;}//--ititerator& operator--(){_node = _node->_prev;return *this;}
因此一个实现方案是,根据正向迭代器的实现方法,同理创建一个struct __reverse_list_iterator
的类,用于实现反向迭代器,其内部与struct __list_iterator
相差无几,只需要对++
、--
做适当的方向处理即可。比如:
对正向:operator++()中,_node = _node->_next;
对反向,operator++()中,_node = _node->_prev;
以上方法虽然可行,但其只针对list类,类似于vector、stirng这样的类,其迭代器为原生指针,++指针所得结果是向后移一位,这样,上述方法就不具有普适性。此外,迭代器本身功能相似,上述方法为了实现反向迭代器,还得重新创建一个类,是否有简化操作?
即,有没有什么方法,能够实现上述多种容器下的反向迭代器,又能够尽可能的复用我们已经实现的功能?
4.2、框架实现
4.2.1、框架搭建1.0:非对称性
基于上述问题,我们可以考虑如何复用正向迭代器来实现反向迭代器。以下为一个框架搭建:
template<class Iterator, class Ref, class Ptr>struct __reverse_iterator{typedef __reverse_iterator<Iterator, Ref, Ptr> RIterator;//重命名Iterator _cur;//成员变量:当前指向__reverse_iterator(Iterator it)//反向迭代器构造:使用正向迭代器构造:_cur(it){}RIterator operator++(){--_cur;return *this;}RIterator operator--(){++_cur;return *this;}Ref operator*(){return *_cur;}Ptr operator->(){return &(*_cur);}bool operator!=(const RIterator& it){return _cur != it._cur;}};
4.2.2、框架搭建2.0:对称性
1)、情况说明
观察库中实现,我们发现其对反向迭代器进行了对称性设计:
//stl_list.h:正向迭代器
iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
//stl_list.h:反向迭代器
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
这种设计情况下会存在一个问题:如果直接使用我们上述1.0中的模拟实现,对rbegin解引用,实际指向的哨兵位的头结点:
Ref operator*(){return *_cur;}
这也是为什么反向迭代器在设置时,做了如下处理:*--tmp
先自减,再解引用。
//stl_iterator.hreference operator*() const {Iterator tmp = current;return *--tmp;}
问题: 为什么这里要创建一个新的临时变量tmp
?
回答: 我们只是需要获取到正确的指向位置,而不改变current
当前迭代器指针,如果直接使用*--current
,相当于进行了operator--()
的运算符重载。
2)、模拟实现
基于上述,我们再来完善修改以下1.0中的反向迭代器框架:
template<class Iterator, class Ref, class Ptr>struct __reverse_iterator{typedef __reverse_iterator<Iterator, Ref, Ptr> RIterator;//重命名Iterator _cur;__reverse_iterator(Iterator it):_cur(it){}RIterator operator++(){--_cur;return *this;}RIterator operator++(int)//修改处一{auto tmp;--_cur;return tmp;}RIterator operator--(){++_cur;return *this;}RIterator operator--(int){auto tmp;++_cur;return tmp;}Ref operator*(){auto tmp = _cur;--tmp;return *tmp;}Ptr operator->(){return &(operator*());//修改处二}bool operator!=(const RIterator& it){return _cur != it._cur;}};
相应的在list.h
中需要加入参数如下:
//反向迭代器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());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}
结果演示如下:
mylist::list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);mylist::list<int>::reverse_iterator it = lt.rbegin();while (it != lt.rend()){cout << *it << " ";++it;}cout << endl;
4.3、与适配器融合理解
4.3.1、通用性说明
一个问题:上述实现的反向迭代器,能否适配vector
?
回答:一样可以。
以下为相关演示:
vector.h
文件中将相同代码包含进去;
//反向迭代器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());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}
void vector_test_04()//验证正、反迭代器
{myvector::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);myvector::vector<int>::iterator lt = v.begin();while (lt != v.end()){cout << *lt << " ";++lt;}cout << endl;myvector::vector<int>::reverse_iterator rlt = v.rbegin();while (rlt != v.rend()){cout << *rlt << " ";++rlt;}cout << endl;}
4.3.2、迭代器类型简单介绍
一个问题:上述的反向迭代器能适用于SQL的所有容器吗?
要回答此问题首先要知晓迭代器的分类,从功能性角度上来说,可以将迭代器分为一下三种:
类型 | 功能 | 举例 |
---|---|---|
forward_iterator单向迭代器 | ++ | < forward_list >、< unordered_map >、< unordered_set > |
bidirectional_iterator双向迭代器 | ++、- - | < list >、< map >、< set > |
random_access_iterator 随机迭代器 | ++、- -、+n、-n | < vector >、< deque > |
这也说明了库里不同函数,其需求的迭代器类型的区别: