当前位置: 首页 > news >正文

【C++】STL -- list 的使用与模拟实现

本篇文章主要介绍 STL 中 list 的使用以及其模拟实现


目录

1  list 的介绍与使用

1) list 的介绍

2) list 的使用

(1) 构造函数

(2) list 遍历

(3) capacity 系列接口

(4) 元素修改系列接口

3) list 的迭代器失效

2  list 的模拟实现

1) list 的节点

2) 成员变量

3) 构造函数

4) 拷贝构造与赋值运算符重载

5) 析构函数

6) 实现迭代器

(1) 迭代器的成员变量

(2) 构造函数

(3) operator++

(4) operator++(int)

(5) operator--

(6) operator--(int)

(7) operator==

(8) operator!=

(9) operator*

(10) operator->

(11) iterator 与 const_iterator

7) 代码

3  实现反向迭代器

1) std 中的反向迭代器

2) 模拟实现反向迭代器

3) 代码


1  list 的介绍与使用

1) list 的介绍

        list 是列表的意思,在C++中代表的是链表那一种数据结构,其底层是我们之前学过的带头双向循环链表,而不是单链表:

所以说经过了初阶的链表的学习,list 学起来应该比较简单。


2) list 的使用

        我们依旧是通过阅读文档来了解 list 的接口有哪些。

(1) 构造函数

list 的构造函数其实跟 vector 的构造函数是差不多的,在 list 的构造函数中都有一个参数是 alloc,这个参数是内存池的意思,我们现在不用管这个参数,用他的缺省参数就可以。第一个构造函数是无参构造,第二个构造函数是用 n 个 val 值来构造一个 list,第三个是用一个迭代器区间来构造,第四个是拷贝构造,最后一个是用一个 initializer_list 来构造,其实也就是用一个 {} 来进行构造。

示例:

#include <iostream>
#include <list>
#include <vector>using namespace std;int main()
{//无参构造list<int> ls1;//n个val构造list<int> ls2(10, 1);//迭代器区间构造vector<int> v1 = { 1, 2, 3, 4, 5 };list<int> ls3(v1.begin(), v1.end());//拷贝构造list<int> ls4(ls3);//initializer_list 构造list<int> ls5 = { 1, 2, 3, 4, 5 };//使用范围 for 来遍历链表for (auto x : ls2) cout << x << ' ';cout << endl;for (auto x : ls3) cout << x << ' ';cout << endl;for (auto x : ls4) cout << x << ' ';cout << endl;for (auto x : ls5) cout << x << ' ';cout << endl;return 0;
}

输出结果:

1 1 1 1 1 1 1 1 1 1
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5

(2) list 遍历

        list 的遍历由于没有重载 [],因为 list 的底层结构是一个一个的节点嘛,所以其迭代器是双向迭代器,并不是随机迭代器,是没法重载 [] 的。所以其遍历访问元素只有两种方式,一种是使用迭代器:

另一种就是使用范围 for 了,实际上就是一种,因为范围 for 就是替换为迭代器。

示例

#include <iostream>
#include <list>using namespace std;int main()
{list<int> ls = {1, 2, 3, 4, 5, 6};//使用迭代器list<int>::iterator it = ls.begin();while (it != ls.end()){cout << *it << ' ';++it;}cout << endl;//使用范围 forfor (auto& x : ls){cout << x << ' ';}cout << endl;return 0;
}

输出结果:

1 2 3 4 5 6
1 2 3 4 5 6

        但是在 list 中虽然没有 list 整体的遍历,但是 list 中有两个在其他线性结构中都具有的接口(string、vector、queue)front 和 back,也就是返回链表的头节点中的元素以及尾节点中的元素,但是注意返回的是引用,而不是拷贝,所以是可以直接改的:

示例

#include <iostream>
#include <list>using namespace std;int main()
{list<int> ls = {1, 2, 3, 4, 5, 6, 7};//front 与 back 返回的是引用ls.front() -= 1;ls.back() -= 2;for (auto& x : ls)cout << x << ' ';cout << endl;return 0;
}

输出结果:

0 2 3 4 5 6 5

(3) capacity 系列接口

list 的 capacity 系列接口很简单,一个就是 empty 判空,一个就是 size 判断元素的个数,也就是节点的个数,并没有 capacity 接口,因为 list 底层是一个个的节点,所以 size 其实就是 capacity 了,max_size 就是返回最大能够申请的节点个数,实际应用时没有什么作用。因为这部分接口比较简单,所以我们就不做示例了,大家能够灵活运用就可以了。

(4) 元素修改系列接口

我们挑几个重要的来说一下:

push_front头插
pop_front头删
push_back尾插
pop_back尾删
insert可以在一个迭代器之前插入一个元素,或者 n 个 val,或者插入一个容器一段迭代器区间内的元素
erase可以删除一个迭代器位置或者一个迭代器区间的元素
swap与另一个 list 进行交换
clear清空链表中的有效元素

其中 emplace_front、emplace_back、emplace 分别对应着 push_front、push_back 与 insert,当插入单个属性的对象或者内置类型时,是没有区别的,但是如果插入的是多个属性的对象,那就有区别的,emplace 系列的接口效率更高(原因会到后面进行讲解):

#include <iostream>
#include <list>
#include <vector>using namespace std;struct A
{A(int a1 = 1, int a2 = 2):_a1(a1),_a2(a2){}int _a1;int _a2;
};int main()
{list<int> ls1 = { 1, 2, 3, 4, 5, 6, 7 };//push_back 与 push_frontls1.push_back(10);ls1.push_front(20);for (auto& x : ls1)cout << x << ' ';cout << endl;cout << "**********************" << endl;//pop_back 与 pop_frontls1.pop_back();ls1.pop_front();for (auto& x : ls1)cout << x << ' ';cout << endl;cout << "**********************" << endl;//insertlist<int> ls2(ls1);//插入一个元素auto it1 = ls2.begin();++it1;ls2.insert(it1, 100);//插入 n 个 valls2.insert(it1, 2, 200);//插入一个迭代区间vector<int> v1 = { 300, 400 };--it1;ls2.insert(it1, v1.begin(), v1.end());for (auto& x : ls2)cout << x << ' ';cout << endl;cout << "**********************" << endl;//eraseauto it2 = ls2.begin();++it2;++it2;//删除一个元素//这里删除完 it2,it2 已经失效了,不能再用了ls2.erase(it2);auto it3 = ls2.begin();++it3;++it3;auto it4 = ls2.end();--it4;//删除一段区间ls2.erase(it3, it4);for (auto& x : ls2)cout << x << ' ';cout << endl;cout << "**********************" << endl;//emplace 系列接口list<A> ls3;//push_back 必须插入一个对象,所以必须加{}来进行类型转换,调用A的构造函数来构造临时对象ls3.push_back({ 2, 2 });//emplace 系列不需要加 {},这里与可变参数有关,其余的 emplace 系列接口相同ls3.emplace_back(2, 2);ls3.emplace_front(3, 3);ls3.emplace(ls3.begin(), 4, 4);for (auto& a : ls3)cout << a._a1 << ' ' << a._a2 << endl;return 0;
}

输出结果:

20 1 2 3 4 5 6 7 10
**********************
1 2 3 4 5 6 7
**********************
1 100 200 300 400 200 2 3 4 5 6 7
**********************
1 100 7
**********************
4 4
3 3
2 2
2 2

3) list 的迭代器失效

        与 vector 和 string 一样,list 也具有迭代器失效现象,在 string 与 vector 中,迭代器失效可能会由插入时扩容或者 erase 删除元素之后导致,但是在 list 中,可以将迭代器就认为是指向 list 中节点的指针,当指向的节点被删除时,指向该结点的指针也就没有指向了,该指针就变成了野指针,所以迭代器就会失效,所以如果删除了迭代器对应的节点,那么该迭代器就会失效,那么插入元素呢?很显然,插入元素是不会引起迭代器失效的,因为插入元素并不会导致扩容等现象,所以list 的迭代器失效只会由删除元素导致

#include <iostream>
#include <list>using namespace std;int main()
{list<int> ls = { 1, 2, 3, 4, 5, 6 };auto it = ls.begin();++it;++it;ls.erase(it);//it 所指向的节点被删除了,it 迭代器已经失效了cout << *it << endl;return 0;
}

运行结果:


2  list 的模拟实现

        对于 list 的模拟实现,我们之前已经在初阶数据结构阶段实现过了带头双向循环链表了,list 的头插、头删、尾插、尾删以及任意一个位置的插入和删除和之前逻辑相同,所以这里就不再赘述,这里主要是讲解 list 的构造函数、拷贝构造、赋值重载、析构函数以及最重要的迭代器。我们依然是像 vector 和 string 一样,为了防止与库中的 list 冲突,所以我们选择使用命名空间将 list 给封起来。

1) list 的节点

        要模拟实现 list,由于 list 是一个一个的节点构成的,所以我们需要先写一个 list 中节点的结构体,该结点就类似于初阶数据结构中双向带头循环链表的节点,在节点中一共有三个域,一个是存储数据的数据域,还有两个指针域,分别指向前一个节点 _prev 与 后一个节点 _next,为了实现泛型编程,我们需要将节点写成模板,这样写一份代码就可以实现所有类型的节点。当然在 list 中插入节点时需要对节点进行初始化,所以我们还需要一个节点的构造函数:

template<class T>
struct list_node
{T _data;//数据list_node<T>* _next;//指向下一个节点list_node<T>* _prev;//指向前一个节点list_node(const T& data = T()):_data(data),_next(nullptr),_prev(nullptr){}
};

2) 成员变量

        同样为了实现泛型编程,list 依然要设计成模板的形式,那么 list 的成员变量是什么呢?list 既为带头双向循环链表,所以其成员函数就是一个 _head 头节点,只要有了该头节点,那么就可以顺着该头节点依次找到后面的所有节点。另外,这里还有一个需要注意的,就是我们可以在成员变量中添加一个 _size 变量,用来记录除了 _head 之外的其他节点的个数,这样 size() 接口的时间复杂度就是 O(1) 的,加了之后就是需要注意增加节点与删除节点时需要更改 _size,初始化时也需要初始化 _size 的值。当然,不加也是可以的,但是 size() 接口就需要遍历一遍链表,时间复杂度为 O(n)。这里我们选择增加一个 _size 变量:

template<class T>
class list
{typedef list_node<T> Node;Node* _head = nullptr;size_t _size = 0;
};

3) 构造函数

        我们在这里主要实现 4 个构造函数,分别是无参构造,使用 initializer_list 构造、n 个 val 构造以及一个迭代器区间构造。

        在实现这四个构造函数之前,由于 list 是一个带头循环双向链表,所以每一个构造函数里面都需要先构造一个哨兵位,所以这里我们先写一个 empty_init 函数,这个函数实现的功能就是申请一个头节点,注意在申请完节点之后,需要让该节点循环起来

void empty_init()
{_head = new Node;//注意需要让链表循环_head->_next = _head->_prev = _head;//不要忘了初始化 _size,当然这里不写也是可以的,因为初始化列表会初始化 _size_size = 0;
}

无参构造

        有了 empty_init 函数,无参构造就比较简单了,直接调用 empty_init 函数即可。

initializer_list 构造

        在这个构造函数的实现中,我们依然是先调用 empty_init 函数,而且我们选择复用 push_back 函数。因为 initializer_list 是支持迭代器的,所以我们可以使用范围 for 来遍历 initializer_list,然后依次将值尾插到 list 对象中。

initializer_list 提供的接口

迭代器区间构造

        在迭代器区间构造中,我们依然需要先调用 empty_init 函数。由于迭代器区间的类型我们是不确定的,可能是 vector 的迭代器,也可能是 string 的迭代器,所以为了适配任何一个容器的迭代器,所以我们这里需要将这个迭代器设计成模板的形式,然后将迭代器中的值依次 push_back 到链表中即可:

template<class InputIterator>
list(InputIterator first, InputIterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}

n 个 val 构造

        我们依然需要先调用 empty_init 函数,然后 push_back n 次 val 值即可完成构造。但是这里有个需要注意的点,就是我们实现的 n 如果是 size_t 类型,而 list 是 list<int> 类型,那么插入的时候,就不会去调用 n 个 val 的构造,转而去调用迭代器区间构造,因为迭代器区间构造是一个模板,当 n 和 val 都是 int 类型时,迭代器区间构造函数模板会生成一个 list(int first, int last) 的构造函数,显然是比 list(size_t n, const int& val) 更加合适的,所以这里就会去调用迭代器区间构造,但是迭代器区间构造会出现对 int 类型解引用的情况,会发生报错。当 n 与 val 都是 size_t 类型时,会发生同样的错误,所以对于这个构造函数,我们需要实现两个版本,一个是 int n,另一个是 size_t n 版本,当实现了这两个版本后,就有了适合的现成的函数,所以编译器就不会再去利用模板生成另一个函数:

list(size_t n, const T& val)
{empty_init();for (size_t i = 0; i < n; i++)push_back(val);
}list(int n, const T& val)
{empty_init();for (int i = 0; i < n; i++)push_back(val);
}

4) 拷贝构造与赋值运算符重载

拷贝构造函数

        对于拷贝构造函数,我们这里依然不能使用浅拷贝,否则一个 list 会影响另一个 list,并且会析构两次,所以我们这里依然要实现深拷贝。深拷贝的实现也很简单,先调用 empty_init 函数,只要在实现完迭代器之后,使用范围 for 将链表的元素依次拷贝到 this 链表中即可。

赋值重载函数

        在实现完拷贝构造之后,赋值重载就很简单了,我们只需要将参数写为值传递,注意这里一定是值传递,不能引用传递,然后会自动调用拷贝构造函数,再与参数交换 _head 与 _size 即可。

5) 析构函数

        在析构函数中,我们只需要释放每个节点就可以了。我们需要先释放掉除了 _head 节点之外的所有节点,然后最后释放掉头节点就可以了,释放完成之后,记得要将 _head 指针变为 nullptr,当然,我们也可以将释放除了 _head 节点之外的功能封装为 clear() 函数,在析构函数中,直接调用 clear() 函数即可。


6) 实现迭代器

        在模拟实现 list 中,最重要的是实现其迭代器。首先我们先想一下能不能用指针来实现你能不能达到我们的效果,假设有一个迭代器 it,我们 ++it 是希望到达下一个节点,但是如果用 Node* 来作为迭代器,++it 只是让指针向后走了一个 Node 结构体大小的距离,但是下一个节点并不一定在下一个位置,而是应该让 it = it->_next,所以单纯使用指针并不能达到我们想要的效果,所以这里我们必须使用一个类来作为迭代器,在该类中封装 Node* 来达到我们想要的效果。在该类中我们需要实现的函数为:迭代器的构造函数、operator++、operator++(int)、operator--、operator--(int)、operator==、operator!=、operator*:

(1) 迭代器的成员变量

        由于迭代器是封装指针实现的,所以自然成员变量就是 Node* _node 了。而且迭代器中的 Node 的类型也是不确定的,所以我们依然要将迭代器设计成为模板。另外在 operator++、operator-- 中,需要返回迭代器自身对象的引用,也就是 Iterator<T>&,写起来很麻烦,所以这里我们选择将 Iterator<T> typedef 为 Self,这样写起来会简便一些:

template<class T>
struct Iterator
{typedef list_node<T> Node;typedef Iterator<T> Self;//成员变量Node* _node;
};

(2) 构造函数

        Iterator 的构造函数就很简单了,只需要传进来一个 Node* node的指针变量,让 _node = node 即可。

(3) operator++

        这个实现就很简单了,只需要让 _node = _node_->next 即可。

(4) operator++(int)

        后置++实现也很简单,只需要注意返回的是 this 的拷贝,并且返回值为 Self 拷贝,不能反回引用。

(5) operator--

        只需要 _node = _node->_prev 即可。

(6) operator--(int)

        返回 _node 的拷贝,然后 _node = _node->_prev 即可。

(7) operator==

        返回 _node == it._node。

(8) operator!=

        返回 _node != it._node。

(9) operator*

        解引用我们希望返回的是每个节点其中的值,所以我们只需要返回 _node->_data 的引用即可。

(10) operator->

        但是在迭代器中有一个特殊的符号,那就是"->"。因为迭代器设计初衷是为每个容器提供一种统一的遍历方式,能够像指针一样遍历和访问,而在结构体指针中,有 "->" 这种运算符,可以使得结构体通过指针访问其成员变量,既然我们希望迭代器像指针一样使用,那么我们就希望使用 it->_data 这种方式来访问节点内部的成员变量,但是迭代器是一个类,为了达到这一效果,在使用 it->data,我们就需要先返回 it 对应节点的指针 _node,然后使用 _node 来访问内部的成员变量;所以在库中对于这种情况做了特殊处理,当我们使用 it->_data 时,其实是省略了一个 "->",如果写全的话,应该是 it->->_data,第一个箭头会调用 operator-> 返回其节点的指针 _node,然后再利用 _node->_data,进而访问到内部的成员变量,也就是 (it.operator->)->_data,所以我们在编写 operator-> 时,返回底层 _node 的地址就可以了。

(11) iterator 与 const_iterator

        有了这个 Iterator 这个类之后,我们就可以在 list 中对 Iterator 进行 typedef 了,进而就实现了 iterator 迭代器:

template<class T>
class list
{typedef list_node<T> Node;
public:typedef Iterator<T> iterator;private:Node* _head = nullptr;size_t _size = 0;
};

在迭代器中,不仅有普通的 iterator,还有一个 const_iterator,在 const_iterator 中,list_node 为 list_node<const T> 类型,也就是 _data 为 const T 类型,但是在 iterator 中的 operator* 与 operator-> 函数,返回的是 T& 与 T* 类型,如果 _data 为 const T 类型,应当返回 const T& 与 const T* 类型,否则会发生权限放大问题,那么我们要再实现一个 const_Iterator 类吗?const_iterator 与 iterator 不一样的只有 operator* 与 operator-> 这两个函数的返回值不一样,其他都是一样的,所以我们可以增加两个模板参数 Ref 与 Ptr,在实例化的时候传入不同的参数,就可以达到写一个 Iterator 同时实现 iterator 与 const_iterator 的作用了:

template<class T, class Ref, class Ptr>
struct Iterator
{typedef list_node<T> Node;typedef Iterator<T, Ref, Ptr> Self;//成员变量Node* _node;
};template<class T>
class list
{typedef list_node<T> Node;
public:typedef Iterator<T, T&, T*> iterator;typedef Iterator<T, const T&, const T*> const_iterator;private:Node* _head = nullptr;size_t _size = 0;
};

7) 代码

list.h:

#pragma once
#include <iostream>
#include <assert.h>using namespace std;namespace LTL
{//创建链表的节点template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;//节点的构造函数list_node(const T& data = T()):_data(data), _next(nullptr), _prev(nullptr){}};template<class T, class Ref, class Ptr>struct Iterator{typedef list_node<T> Node;typedef Iterator<T, Ref, Ptr> Self;//成员变量Node* _node;//构造函数Iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node;}//重载 ++ 运算符Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Node* tmp = _node;_node = _node->_next;return Iterator(tmp);}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Node* tmp = _node;_node = _node->_prev;return Iterator(tmp);}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};//链表template<class T>class list{typedef list_node<T> Node;public:typedef Iterator<T, T&, T*> iterator;typedef Iterator<T, const T&, const T*> const_iterator;//创建头节点的函数void empty_init(){//先创建一个哨兵位_head = new Node;//需要让自己循环起来_head->_next = _head->_prev = _head;_size = 0;}//创造迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}//无参默认构造list(){empty_init();}//n个 val 构造list(size_t n, const T& val){empty_init();for (size_t i = 0; i < n; i++)push_back(val);}list(int n, const T& val){empty_init();for (int i = 0; i < n; i++)push_back(val);}//initializer_list 构造list(const initializer_list<T>& il){empty_init();for (auto& x : il)push_back(x);}//迭代器区间构造template<class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);++first;}}//拷贝构造list(const list<T>& ls){empty_init();for (auto& x : ls)push_back(x);}void swap(list<T>& ls){std::swap(_head, ls._head);std::swap(_size, ls._size);}//赋值运算符重载list<T>& operator=(list<T> ls){swap(ls);return *this;}//析构函数~list(){clear();delete _head;_head = nullptr;}void clear(){Node* pcur = _head->_next;while (pcur != _head){Node* next = pcur->_next;Node* prev = pcur->_prev;prev->_next = next;next->_prev = prev;delete pcur;pcur = next;}_size = 0;}size_t size() const{return _size;}bool empty() const{return _head->_next == _head;}T& back(){return *(--end());}T& front(){return *(begin());}const T& back() const{return *(--end());}const T& front() const{return *(begin());}//尾插void push_back(const T& data){//复用 insertinsert(iterator(_head), data);}//头插void push_front(const T& data){insert(iterator(_head->_next), data);}//尾删void pop_back(){assert(_head->_next != _head);//复用 eraseerase(iterator(_head->_prev));}//头删void pop_front(){assert(_head->_next != _head);erase(iterator(_head->_next));}void insert(iterator pos, const T& val){//先创建一个节点Node* newnode = new Node(val);Node* node = pos._node;//改变指针指向Node* prev = node->_prev;//prev newnode nodenewnode->_next = node;newnode->_prev = prev;prev->_next = newnode;node->_prev = newnode;//不要忘记让 _size++++_size;}iterator erase(iterator pos){assert(_head->_next != _head);Node* del = pos._node;//改变指针指向Node* prev = del->_prev;Node* next = del->_next;//prev del nextprev->_next = next;next->_prev = prev;delete del;//最后不要忘记让 _size----_size;return iterator(next);}private:Node* _head = nullptr;size_t _size = 0;};
}

test.cpp:

#include "list.h"
#include <vector>//测试构造函数以及范围for
void Test_list01()
{LTL::list<int> ls1;LTL::list<int> ls2(10, 1);for (auto& x : ls2)cout << x << ' ';cout << endl;LTL::list<int> ls3 = { 1, 2, 3, 4, 5 };for (auto& x : ls3)cout << x << ' ';cout << endl;std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7 };LTL::list<int> ls4(v.begin(), v.end());for (auto& x : ls4)cout << x << ' ';cout << endl;//拷贝构造LTL::list<int> ls5(ls4);for (auto& x : ls5)cout << x << ' ';cout << endl;//赋值重载LTL::list<int> ls6;ls6 = ls5;for (auto& x : ls6)cout << x << ' ';cout << endl;LTL::list<std::string> ls7 = { "left", "right", "sort", "reverse" };for (auto& s : ls7)cout << s << ' ';cout << endl;
}//测试插入删除接口
void Test_list02()
{LTL::list<int> ls;//插入ls.push_back(1);ls.push_back(2);ls.push_back(3);ls.push_front(4);ls.push_front(5);ls.push_front(6);for (auto& x : ls)cout << x << ' ';cout << endl;//删除ls.pop_front();ls.pop_back();for (auto& x : ls)cout << x << ' ';cout << endl;//insert 与 eraseauto it = ls.begin();++it;++it;ls.insert(it, 10);ls.erase(it);for (auto& x : ls)cout << x << ' ';cout << endl;
}//测试其他接口
void Test_list03()
{LTL::list<int> ls = { 1, 2, 3, 4 };ls.front() += 2;ls.back() += 3;for (auto& x : ls)cout << x << ' ';cout << endl;ls.clear();for (auto& x : ls)cout << x << ' ';cout << endl;
}int main()
{Test_list03();return 0;
}

3  实现反向迭代器

1) std 中的反向迭代器

        之前实现的迭代器为正向迭代器,也就是从头开始往后遍历,那么反向迭代器就是从尾部开始向前遍历。std 中的反向迭代器叫做 reverse_iterator 以及 const_reverse_iterator:

比如:

#include <iostream>
#include <list>using namespace std;int main()
{list<int> ls = { 1, 2, 3, 4, 5 };auto it = ls.rbegin();while (it != ls.rend()){cout << *it << ' ';++it;}cout << endl;return 0;
}

输出结果:

5 4 3 2 1

可以看到反向迭代器与正向迭代器行为是完全类似的,只不过一个是正向遍历,一个是反向遍历。


2) 模拟实现反向迭代器

        我们可以先看一下库中的反向迭代器是如何实现的:

template<typename _Iterator>class reverse_iterator: public iterator<typename iterator_traits<_Iterator>::iterator_category,typename iterator_traits<_Iterator>::value_type,typename iterator_traits<_Iterator>::difference_type,typename iterator_traits<_Iterator>::pointer,typename iterator_traits<_Iterator>::reference>{protected:_Iterator current;typedef iterator_traits<_Iterator>		__traits_type;public:typedef _Iterator					iterator_type;typedef typename __traits_type::difference_type	difference_type;typedef typename __traits_type::pointer		pointer;typedef typename __traits_type::reference		reference;/***  The default constructor value-initializes member @p current.*  If it is a pointer, that means it is zero-initialized.*/// _GLIBCXX_RESOLVE_LIB_DEFECTS// 235 No specification of default ctor for reverse_iteratorreverse_iterator() : current() { }/***  This %iterator will move in the opposite direction that @p x does.*/explicitreverse_iterator(iterator_type __x) : current(__x) { }/***  The copy constructor is normal.*/reverse_iterator(const reverse_iterator& __x): current(__x.current) { }/***  A %reverse_iterator across other types can be copied if the*  underlying %iterator can be converted to the type of @c current.*/template<typename _Iter>reverse_iterator(const reverse_iterator<_Iter>& __x): current(__x.base()) { }/***  @return  @c current, the %iterator used for underlying work.*/iterator_typebase() const{ return current; }/***  @return  A reference to the value at @c --current**  This requires that @c --current is dereferenceable.**  @warning This implementation requires that for an iterator of the*           underlying iterator type, @c x, a reference obtained by*           @c *x remains valid after @c x has been modified or*           destroyed. This is a bug: http://gcc.gnu.org/PR51823*/referenceoperator*() const{_Iterator __tmp = current;return *--__tmp;}/***  @return  A pointer to the value at @c --current**  This requires that @c --current is dereferenceable.*/pointeroperator->() const{ return &(operator*()); }/***  @return  @c *this**  Decrements the underlying iterator.*/reverse_iterator&operator++(){--current;return *this;}/***  @return  The original value of @c *this**  Decrements the underlying iterator.*/reverse_iteratoroperator++(int){reverse_iterator __tmp = *this;--current;return __tmp;}/***  @return  @c *this**  Increments the underlying iterator.*/reverse_iterator&operator--(){++current;return *this;}/***  @return  A reverse_iterator with the previous value of @c *this**  Increments the underlying iterator.*/reverse_iteratoroperator--(int){reverse_iterator __tmp = *this;++current;return __tmp;}/***  @return  A reverse_iterator that refers to @c current - @a __n**  The underlying iterator must be a Random Access Iterator.*/reverse_iteratoroperator+(difference_type __n) const{ return reverse_iterator(current - __n); }/***  @return  *this**  Moves the underlying iterator backwards @a __n steps.*  The underlying iterator must be a Random Access Iterator.*/reverse_iterator&operator+=(difference_type __n){current -= __n;return *this;}/***  @return  A reverse_iterator that refers to @c current - @a __n**  The underlying iterator must be a Random Access Iterator.*/reverse_iteratoroperator-(difference_type __n) const{ return reverse_iterator(current + __n); }/***  @return  *this**  Moves the underlying iterator forwards @a __n steps.*  The underlying iterator must be a Random Access Iterator.*/reverse_iterator&operator-=(difference_type __n){current += __n;return *this;}/***  @return  The value at @c current - @a __n - 1**  The underlying iterator must be a Random Access Iterator.*/referenceoperator[](difference_type __n) const{ return *(*this + __n); }};

通过源码,其实我们可以看到反向迭代器是一个模板类,其模板参数叫做 _Iterator,其中成员变量是 _Iterator current,而且反向迭代器的 operator++、operator--、operator* 等接口,都是利用 current 来实现的,所以说 reverse_iterator 就是通过封装 iterator 来实现的,并且这样写完会有一个好处,那就是只要实现了一个 reverse_iterator 这个类,那么之后所有容器在实现完 iterator 和 const_iterator 之后,那就可以利用 reverse_iterator 这个类来封装容器的 iterator 和 const_iterator,这样就可以实现任何一个容器的 reverse_iterator 了,实际上就是代码的复用。

        有了这个思想之后,实现起来就很简单了:

operator*:只需要调用 iterator 的 operator* 即可,但是在源码中 operator* 返回的是其前一个 iterator 的 operator*,返回前一个的原因是因为在构造 rbegin() 和 rend() 时,利用的是 end() 和 begin() 迭代器:

 reverse_iteratorrbegin(){ return reverse_iterator(end()); }const_reverse_iteratorrbegin() const{ return const_reverse_iterator(end()); }reverse_iteratorrend(){ return reverse_iterator(begin()); }const_reverse_iteratorrend() const{ return const_reverse_iterator(begin()); }

用 list 画个图就是:

所以要返回上一个 iterator.operator*()。

        当然这里你也可以选择使用 --end() 来作为 rbegin(),用 end() 来作为 rend(),这样就直接调用 current.operator*() 就可以了。

operator->:返回 this->operator* 的地址就可以。

operator++:就是 --current。

operator--:就是 ++current。

operator==:就是判断两个 reverse_iterator 中的 current 是否相等即可。

operator!=:就是判断两个 reverse_iterator 中的 current 是否不相等即可。


3) 代码

ReverseIterator.h

#pragma oncetemplate<class Iterator, class Ref, class Ptr>
struct ReverseIterator
{typedef ReverseIterator<Iterator, Ref, Ptr> Self;Iterator _it;ReverseIterator(Iterator it):_it(it){}Ref operator*(){Iterator tmp = _it;--tmp;return *tmp;}Ptr operator->(){return &(this->operator*);}Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp = *this;--_it;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp = *this;++_it;return tmp;}bool operator==(const Self& it){return _it == it._it;}bool operator !=(const Self& it){return _it != it._it;}
};

list.h(只有迭代器与反向迭代器部分):

template<class T>
class list
{
public:typedef Iterator<T, T&, T*> iterator;typedef Iterator<T, const T&, const T*> const_iterator;typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;//创造迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}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());}
};

        这里只是实现了 list 的反向迭代器,如果想要实现 vector 的反向迭代器也很简单,只不过就是传的模板参数不一样罢了,如果有兴趣也可以自己实现哦。

http://www.dtcms.com/a/602669.html

相关文章:

  • 百度认证官方网站怎么制作网站布局
  • 劲松网站建设专门做dm单的网站
  • Go语言编译器 | 探索Go语言编译过程与优化技巧
  • 在线监测系统:农药精细化工的“安全锁”与“效率引擎”
  • Python 爬虫教程 | 豆瓣 TOP250 数据抓取与分析实战
  • 专门做创意桌椅的网站访问中国建设银行网站
  • 搭建外文网站计算机网络课程设计
  • 长沙美容网站建设百度广告优化
  • 网站建设全过程seo发外链网站
  • 《C++ Primer》和《Effective C++》哪个更难?
  • 织梦网站联系我们的地图怎么做设计室内装修的软件
  • 电影数据可视化分析系统的设计与实现
  • 显存占用、kvcache和并发学习笔记
  • wordpress网站被攻击毕节网站怎么做seo
  • 烟台市建设工程交易中心网站网站滑动效果怎么做的
  • wordpress 时间不对呼和浩特网站seo优化方案
  • AI发展的好快
  • agx装机系列大全(包括刷机/下载中文输入法/浏览器/星火商店/anaconda等)持续更新ing
  • 有没有专门做游戏人物的绅士视频网站视频营销案例
  • 网站架构师的工作内容wordpress使用json
  • 智慧城市与车路协同:驶向未来交通新纪元
  • 深圳网站建设公司服务流程网页贷款
  • 旅游电子商务网站建设背景建网站html5
  • 责任链设计模式->规则树
  • 做仓单的网站h5手机网站发展趋势
  • 1013 Battle Over Cities
  • 山东泰山队深圳队seo怎么学
  • Bootstrap5 轮播功能详解
  • title 株洲网站建设开通公司网站
  • 站长工具综合查询系统电子商务网站设计目的及要求