目录
一:而额外封装一个const迭代器。const_iterator
现阶段我们已经逐渐熟悉了各个STL库中的容器,对于他们的各个接口都大差不差,在我们学习完vector之后我们就可以陆陆续续接触一些算法题。我们的《好题分析》这一专栏也会不断的进行更新!下面我们先来熟悉以下list这个容器。


此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

注意!!
1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动



6、list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
- void TestListIterator1()
- {
- int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
- list<int> l(array, array + sizeof(array) / sizeof(array[0]));
- auto it = l.begin();
- while (it != l.end())
- {
- // erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,
- // 必须先给其赋值
- l.erase(it);
- ++it;
- }
- }
-
- // 改正
- void TestListIterator()
- {
- int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
- list<int> l(array, array + sizeof(array) / sizeof(array[0]));
- auto it = l.begin();
- while (it != l.end())
- {
- l.erase(it++); // it = l.erase(it);
- }
- }
我们对list的迭代器的理解与我们之前对于string 和 vector中的iterator的理解有十分大的区别。string和vector的迭代器我们可以替换理解为指针,在我们遍历vector或者string时,仅需要对++运算符进行重载,我们就可以拿到下一个位置的值,而operator++()也很好理解,就是指针+1指向下一个下标的位置。
但是这次我们的list就大有不同,我们都知道list是一个带头的双向链表,而想要获得下一个结点的数据,应当是node = node -> next; 如果我们将运算符++重载为这个代码,那对于其它的代码想要运用++操作符,就纯粹扯淡。
这个时候的唯一解决方法就是————————封装一个类!!!
- // 一个结点的结构!!!
- template<class T>
- struct ListNode
- {
- T _data;
- ListNode
* _next; - ListNode
* _prev; -
- ListNode(const T& x = T())
- :_next(nullptr)
- , _prev(nullptr)
- , _data(x)
- {}
- };
-
- // 将迭代器封装成一个类
- template<class T>
- struct ListIterator
- {
- typedef ListIterator
Self; - typedef ListNode
Node; -
- Node* _node;// iterator
-
- ListConstIterator(Node* node)
- :_node(node)
- {}
-
- bool operator != (const Self& it)
- {
- return _node != it._node;
- }
-
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
-
- Self operator++(int)
- {
- Self tmp(*this);
- _node = _node->_next;
- return *this;
- }
-
- Self& operator--()
- {
- _node = _node->_prev;
- return *this;
- }
-
- Self operator--()
- {
- Self tmp(*this);
- _node = _node->_prev;
- return tmp;
- }
-
- T& operator*()
- {
- return _node->_data;
- }
-
- T* operator->()
- {
- return &_node->_data;
- }
- };
我们在之后的项目中,如果发现我们目前处理的数据中的内置类型不满足我们的需求,不妨我们可以将其封装成一个类!!!
首先我们先通过画图得方式来理解代码:

因为这个iterator类我们并不会定义私有成员,所以我们这里用的struct来定义。
而我们在整个链表的总体类中,我们需要先找到两个 头 和 尾 结点的位置,即begin 和 end.
- iterator begin()
- {
- return iterator(_head->_next); // 匿名对象
- // iterator it(_head->_next); // 调用 iterator类 里面的 构造函数
- // return it;
- }
-
-
- iterator end()
- {
- return iterator(_head); // 匿名对象
- // iterator it(_head); // 调用 iterator类 里面的 构造函数
- // return it;
- }


想要去到_a1, 若是不进行函数重载, 则代码为:
- std::cout << it._node->_data._a1 << " " << it._node->_data._a2 << std::endl;
- it++;
- std::cout << it._node->_data._a1 << " " << it._node->_data._a2 << std::endl;
很明显代码非常的冗长和麻烦, 因此我们可以利用函数重载:
- T* operator->()
- {
- return &_node->_data;
- }
- std::cout << it->_a1 << " " << it->_a2 << std::endl;
- it++;
- std::cout << it->_a1 << " " << it->_a2 << std::endl;
it.operator->()->_a1
在这里编译器会自动优化代码,将代码的可读性提高。
it->_a1 <==> it.operator->()->_a1 <==> it->->_a1
通过公式推导,我们不难发现 it->_a1 <==> it->->_a1 这两个式子是等价的
const迭代器,需要的是迭代器指向的内容不能被修改而const iteratror 作返回值时,代表了迭代器的指向不可被修改。
在我们实施这个方法后,我们会发现仅仅只有Self& operator*() 和 Self* operator->()的返回值是需要加const,其它的都不变
- //对于每一个容器来说,都有存在const的类型接口,因此我们也需要创建一个const迭代器。
- //(运用const主要还是 防止 在进行 拷贝构造 或者 ++ -- 等出现 修改一个 const链表的情况。)
- template<class T>
- struct List_const_iterator
- {
- typedef ListNode
Node; - typedef List_const_iterator
Self; -
- Node* _node;// 最重要的一行代码,代表在 List_iterator 类中 封装一个 _node 用来指向结点,通过在类里面
- // 控制运算符重载来操纵迭代器
-
- List_const_iterator(Node* node) // 传值构造
- :_node(node)
- {}
-
- // ++it
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
-
- // it++
- Self operator++(int)
- {
- Self tmp(*this); // 无需写拷贝构造函数, 默认的 浅拷贝 即可完成操作
- _node = _node->_next;
- return *this;
- }
-
- // *it
- const T& operator*()
- {
- return _node->_data;
- }
-
- // it->_data,此时的 _data 是结构体时才调用
- const T* operator->()
- {
- return &_node->_data;
- }
-
- bool operator!=(const Self& it)
- {
- return _node != it._node;
- }
-
- };


因此我们没必要多此一举。
- // 我们在创建 const_iterator 迭代器时发现在整个类中,仅仅只是对 operator*() 与 operator->() 的返回值进行了修改
- // 为了尽可能的减少代码量,利用模板是一个不错的选择。
- // Ref == reference , Prt == pointer
- // T& T*
- template<class T, class Ref, class Ptr>
- //template
- struct List_iterator
- {
- typedef ListNode
Node; - typedef List_iterator
Self; -
- Node* _node;// 最重要的一行代码,代表在 List_iterator 类中 封装一个 _node 用来指向结点,通过在类里面
- // 控制运算符重载来操纵迭代器
-
- List_iterator(Node* node) // 传值构造
- :_node(node)
- {}
-
- // ++it
- Self& operator++()
- {
- _node = _node->_next;
- return *this;
- }
-
- // it++
- Self operator++(int)
- {
- Self tmp(*this); // 无需写拷贝构造函数, 默认的 浅拷贝 即可完成操作
- _node = _node->_next;
- return *this;
- }
-
- // *it 返回类型为T&
- Ref operator*()
- {
- return _node->_data;
- }
-
-
- // it->_data,此时的 _data 是结构体时才调用, 返回类型为T*
- Ptr operator->()
- {
- return &_node->_data;
- }
-
- bool operator!=(const Self& it)
- {
- return _node != it._node;
- }
-
- };
在list类中:
- template<class T>
- class list
- {
- public:
- typedef ListNode
Node; - typedef List_iterator
iterator; - typedef List_iterator
const T&, const T*> const_iterator; - //typedef List_const_iterator
const_iterator; - // 构造函数创建 哨兵位 的头结点
利用模板是最高效的方法!!!
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
本文的代码思路与之前大为不同,本次首先接触到了利用类封装一个迭代器,其次是对结点里的类进行分类讨论,从而引出对->运算符的重载,再然后又对const的迭代器进行了扩展,发现利用模板可以有效的解决出现的一系列问题。
代码在我的Gitee:
my_list_practices_2/my_list_practices_2/list.h · 无双/C_Plus_Plus_Acer - Gitee.com