因为list容器是用双向循环链表来实现的,所以每一个结点都有指向下一个结点的next指针和指向上一个结点的prev指针;
template<class T>
struct ListNode
{T _value;ListNode<T>* _next;ListNode<T>* _prev;ListNode(const T& val = T()):_value(val), _next(nullptr), _prev(nullptr){}
};
这里为什么要自己封装一个迭代器类呢?
首先list容器中的结点不是连续存储的,那么如果使用ListNode*类型的迭代器的话,++操作并不能移动到下一个结点,所以需要我们自己封装一个迭代器类;
这个迭代器类的原理是:将指向这个结点的指针封装成一个迭代器类,这样在进行++操作时,就可以通过指针来获取下一个结点的位置,从而实现++功能了;
迭代器的参数也是通过模板来接收的,可以看到模板参数有三个:T,Ptr,Ref;
T:结点指针的类型;Ptr:T*或者const T*类型,是->运算符重载函数operator->的返回值类型;Ref:T&或者const T&类型,是解引用运算符重载函数operator*的返回值类型;
使用三个模板参数的好处是:当使用const迭代器时,不需要我们自己再封装一个const迭代器的类,只需要通过这个模板参数来区别const和非const迭代器就可以;
template<class T, class Ptr, class Ref>
class ListIterator
{
public:typedef ListNode<T> Node;typedef ListIterator<T, Ptr, Ref> Self;Node* _node;//构造ListIterator(Node* node):_node(node){}//这里不需要显示写出拷贝构造和赋值运算符重载还有析构函数//因为迭代器类是封装的指向结点的指针,并没有额外的资源,所以只需要默认的浅拷贝即可//解引用 operator*Ref operator*(){//解引用返回的是结点的数值return _node->_value;}//前置operator++Self& operator++(){//迭代器向后移动_node = _node->_next;return *this;}//后置operator++Self operator++(int){//保存原来的位置Self tmp = *this;//将该迭代器向后移动_node = _node->_next;return tmp;}//前置operator--Self& operator--(){_node = _node->_prev;return *this;}//后置operator--Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}//operator!=bool operator!=(const ListIterator& it){//判断两个迭代器对象里面的_node成员是否相等return _node != it._node;}//operator==bool operator==(const ListIterator& it){return _node == it._node;}//operator-> 理论上是:it.operator->()->_year;实际上是:it->_year;省略了一个->Ptr operator->(){//返回的是一个指向数据的指针return &(_node->_value);}
};
此处标明:以下成员函数都是写在my_list类内部的
template<class T>
class my_list
{
public:typedef ListNode Node;typedef ListIterator<T, T*, T&> iterator;typedef ListIterator<T, const T*, const T&> const_iterator;//成员函数private:Node* _header;
};
//构造my_list(){_header = new Node;//形成循环结构_header->_next = _header->_prev = _header;}
//拷贝构造my_list(const my_list<T>& lst){//创建新的头结点_header = new Node;_header->_next = _header->_prev = _header;//将lst中的每个结点值插入到新的my_list中for (const auto& e : lst){push_back(e);}}
这里赋值运算符重载使用了简便的写法,通过接收参数时的拷贝,再直接将两个对象的头指针交换,就可以直接完成赋值的深拷贝操作;
//赋值运算符重载my_list<T>& operator=(my_list<T> lst){//交换两个头指针swap(_header, lst._header);return *this;}
//析构~my_list(){clear();delete _header;}
这里的返回值都是需要调用迭代器构造函数来构造一个迭代器对象返回的;
iterator begin(){//返回头结点下一个结点的迭代器对象return iterator(_header->_next);}iterator end(){//返回头结点的迭代器对象return iterator(_header);}const_iterator begin() const{//返回头结点下一个结点的迭代器对象return const_iterator(_header->_next);}const_iterator end() const{//返回头结点的迭代器对象return const_iterator(_header);}
//Capacity//获取结点个数size_t size() const{size_t count = 0;for (const auto& e : *this){count++;}return count;}//判空bool empty() const{return _header->_next == _header;}
//Access//获取最后一个元素T& back(){return _header->_prev->_value;}//获取第一个元素T& front(){return _header->_next->_value;}const T& back() const{return _header->_prev->_value;}const T& front() const{return _header->_next->_value;}
1、插入
(1)在任意结点前插入
因为list里的结点不是连续存储的,所以插入之后迭代器并不会失效,还是只想原来的结点;
插入的位置传入的是迭代器,所以也不用考虑越界的问题;
//任意位置前插入void insert(iterator pos, const T& val){Node* cur = pos._node;Node* prev = cur->_prev;Node* newNode = new Node(val);prev->_next = newNode;newNode->_prev = prev;newNode->_next = cur;cur->_prev = newNode;}
(2)头插、尾插
头插和尾插都是对insert的一个复用
//尾插void push_back(const T& val){insert(end(), val);}//头插void push_front(const T& val){insert(begin(), val);}
2、删除
(1)删除任意位置的元素
与插入不同,删除会导致当前的迭代器失效,所以返回值是当前位置的下一个位置的迭代器;
当然,不能够删除头结点,所以当传入的删除位置是头结点的位置时,不做任何操作;
//删除任意位置元素,返回值:被删除数据的下一个位置iterator erase(iterator pos){//不能删除end迭代器指向的数据,即_header节点if (pos != end()){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;//创建指向下一个节点的迭代器return iterator(next);}//等于end,则返回,不作任何操作return pos;}
(2)头删、尾删
头删和尾删是对erase的一个复用
//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}
本文发布于:2024-02-02 09:21:01,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170683686042840.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |