👍作者主页:进击的1++
🤩 专栏链接:【1++的C++初阶】
文章目录
- 一,什么是list
- 二,构造与析构
- 2.1 结点结构
- 2.2 链表结构
- 2.3 迭代器结构
- 三,部分重要接口的作用及其实现
- 3.1 迭代器相关的接口
- 3.2 list相关接口
一,什么是list
list是可以在常数范围内进行任意插入和删除的序列式容器。
list底层是前后循环链表,因此可以双向前后迭代。与其他序列式容器相比,list的最大缺陷是不支持任意位置的随机访问。并且list还需要一些额外的空间来保存结点与结点间的相关联信息。
二,构造与析构
2.1 结点结构
template<class T>struct list_node{list_node* prev;//指向上一个结点list_node* next;//指向下一个结点T data;//构造list_node(const T& val = T()):data(val), prev(nullptr), next(nullptr){}};
在此结构中,定义出来了指向结点的前后指针,结点数据类型并对上述成员变量进行了初始化。
2.2 链表结构
template<class T>class list{public:typedef list_node<T> Node;//构造list(){_head = new Node;_head->prev = _head;_head->next = _head;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}//拷贝构造template <class InputIterator>list(InputIterator first, InputIterator last){_head = new Node;_head->prev = _head;_head->next = _head;while (first != last){push_back(*first);first++;}}//拷贝构造//现代写法list(const list<T>& lt){_head = new Node;_head->prev = _head;_head->next = _head;list<T> tmp(lt.begin(), lt.end());std::swap(_head, tmp._head);}~list(){clear();delete _head;_head = nullptr;}private:Node* _head; };
此结构中定义了头结点,并对头结点进行了初始化,使其指向上一个的指针指向自己,指向下一个的指针指向自己。
拷贝构造我们用的是现代写法,通过迭代器构造,构造出一个临时对象,再将其头结点指针进行交换。需要注意的是:在拷贝前都要进行初始化,防止其成为野指针。
在析构之前先将哨兵位头结点前的结点进行释放,因此就有了clear()函数,最后在析构时就只需将头结点释放。
2.3 迭代器结构
template<class T, class Ref, class Ptr>struct list_iterator{typedef list_iterator<T,Ref,Ptr> iterator;typedef list_node<T> Node;//申请一个结点指针Node* _node;//构造list_iterator(Node* node):_node(node){}};
list迭代器的本质就是一个指向结点的指针。先是申请结点指针,进行初始化,使其指向传过来的结点指针。
三,部分重要接口的作用及其实现
3.1 迭代器相关的接口
bool operator !=(const iterator& it)const{return _node != it._node;}bool operator == (const iterator& it)const{return _node == it._node;}Ref operator *(){return _node->data;}Ptr operator->(){return &(operator*());}iterator& operator++(){_node = _node->next;return *this;}iterator operator++(int){iterator tmp = *this;_node = _node->next;return tmp;}iterator& operator--(){_node = _node->prev;return *this;}iterator operator--(int){iterator tmp = *this;_node = _node->prev;return tmp;}
因为list迭代器是自定义类型,因此迭代器之间的一些操作,我们就必须要进行函数重载。我们解释几个比较重要的函数重载 。
Ptr operator->(){return &(operator*());}
此重载所适合的环境:当list中存储的也是一个结构体是,此运算符就能够使用。
例:
struct pos{int a;int b;};list<pos> lt;list<pos>::iterator it=lt.begin();it->a;
在此函数内部,返回了&(operator*()),而operator*()我们也进行了重载,返回的是结点数据–data,要是按此形式,那么此函数最终返回的就是data的地址。这与我们上述it->a不符合。原因在于,此处还有个隐藏的->,其最终形式为:it->data->a;这是编译器为了语法的可读性,而进行的的特殊处理。
说完->重载,我们在来说说++的重载。
iterator& operator++()//前置++{_node = _node->next;return *this;}
由于list的空间是不连续的,因此迭代器++,就是到下一结点。
3.2 list相关接口
inert的实现
iterator insert(iterator pos,const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->prev;prev->next = newnode;newnode->prev = prev;newnode->next = cur;cur->prev = newnode;return iterator(newnode);}
erase的实现
iterator erase(iterator pos){assert(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);}