STL:List从0到1
🎉个人名片:
🐼作者简介:一名乐于分享在学习道路上收获的大二在校生
🙈个人主页🎉:GOTXX
🐼个人WeChat:ILXOXVJE
🐼本文由GOTXX原创,首发CSDN🎉🎉🎉
🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路
🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉
————————————————
🎉文章简介:
🎉本篇文章将 介绍如何使用C++编写代码来实现一个类似于STL中的List容器 相关知识进行分享!
💕如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加 油,一起奔跑,让我们顶峰相见!!!🎉🎉🎉
——————————————————
一.前言
这篇文章将介绍如何使用C++编写代码来实现一个类似于STL中的List容器。 list是一个可以在常数范围内在任意位置进行插入和删除的序列式容器。在这篇文章中,你将学习并实现List的常见功能,如添加元素、删除元素等。通过实现自己的List容器,你将更好地熟悉List的使用及相关特性,并提升对C++语言的理解和掌握。
————————————————
二.List的介绍
List文档介绍链接: link
1. list是一个可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代;
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素;
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效;
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好(不需要挪动数据);
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素);
三.List的结构及模拟实现
一.底层结构
List底层是一个带头双向循环链表
如图:
库里面的begin() 与end() 返回的节点位置:
begin()返回的是头节点的下一个节点;
而end()返回的是头节点;
二.List的模拟实现
重点::迭代器的实现
1. 构造函数
template struct ListNode { ListNode(const T& x=T()) :_next(nullptr) ,_prev(nullptr) ,_val(x) { } ListNode* _next; ListNode* _prev; T _val; };
库里面的List类的构造函数是另写一个函数,因为这个函数拷贝构造会使用,然后复用它,所以我们也这样实现;
template class List { public: void empty_List() { _phead = new node; _phead->_next = _phead; _phead->_prev = _phead; } List() { empty_List(); } }
2. 拷贝构造函数
List(const List& lt) { empty_List(); //初始化 for (const auto& e : lt) { push_back(e); //将lt里面的数据依次尾插 } }
3. 插入函数
思路:记录前一个和后一个节点,然后连接
iterator insert(iterator pos, const T& x) { node* newnode = new node(x); //构造一个节点 node* next = pos._node; node* prev = next->_prev; //记录前一个 newnode->_next = next; next->_prev = newnode; //链接 newnode->_prev = prev; prev->_next = newnode; return pos; }
4. 尾插函数
复用insert函数
void push_back(const T& x) { /* node* newnode = new node(x); node* tail = _phead->_prev; tail->_next = newnode; newnode->_prev = tail; //不复用的写法 newnode->_next = _phead; _phead->_prev = newnode;*/ insert(end(), x); }
5. 头插函数
复用insert函数
void push_front(const T& x) { insert(begin(),x); }
6. 删除函数
iterator erase(iterator pos) { assert(pos!=end()); node* prev = pos._node->_prev; //保存前一个节点 node* next = pos._node->_next; //保存后一个节点 prev->_next = next; //连接 next->_prev = prev; delete pos._node; //释放掉该节点 return next; //返回删除元素的下一个节点 }
7. 尾删函数
复用删除函数
void pop_back() { //erase(end()._node->_prev); erase(--end()); //头节点的前指针指向的是最后一个节点 }
8. 头删函数
复用删除函数
void pop_front() { erase(begin()); }
9. 迭代器的实现
因为链表的底层物理空间不是连续的,所以不能使用原生指针类实现。因为原生指针++,可以找到下一个数据,但是链表的节点与节点之间不是连续的,指针++,不能找到下一个节点,所以我们需要操作符重载,改变 ++, != ,* 等操作符的行为;又因为节点指针是内置类型,不能进行操作符重载,所以我们只能将它进行封装,封装在一个类里面,进行重载;
template struct __List_iterator { typedef ListNode node; typedef __List_iterator self; __List_iterator(node* node) //构造函数 :_node(node) {} self& operator++() //运算符的重载 { _node = _node->_next; //前置++,返回++后的值 return *this; } self operator++(int) { self tmp(_node); //保存++前的值 _node = _node->_next; return tmp; //返回++前的值 } self& operator--() { _node = _node->_prev; return *this; } self operator--(int) { self tmp(_node); _node = _node->_prev; return tmp; } Ref operator*() { return _node->_val; } Ptr operator-> { return &_node->-val; } bool operator!=(const self& s) { return s._node!= this->_node; } bool operator==(const self& s) { return s._node == this->_node; } node* _node; };
10. 赋值运算符重载
传统写法:
void clear() { iterator lt = begin(); while (lt != end()) { lt = erase(lt); } } //lt1=lt2 List& operator=(const List& lt) { clear(); //清空函数,将链表中的有效数据删除掉,保留头节点 for (const auto& e : lt) { push_back(e); //依次尾插 } return *this; }
现代写法:
void swap(List& lt) { std::swap(_phead, lt->_phead); } //lt1=lt2 List& operator=(List lt) //lt是lt2的拷贝构造 { swap(lt); //交换lt与lt1 return *this; //返回 }
补充知识:
typedef 放在类里面与外面的区别:
如果是放在公有里面,则类外面也可以使用,但是要指定类域;
如果是私有的话,则类外面不能使用;
三.总代码:
#pragma once #include #include using namespace std; namespace L { template struct ListNode { ListNode(const T& x=T()) :_next(nullptr) ,_prev(nullptr) ,_val(x) { } ListNode* _next; ListNode* _prev; T _val; }; template struct __List_iterator { typedef ListNode node; typedef __List_iterator self; __List_iterator(node* node) :_node(node) {} self& operator++() { _node = _node->_next; return *this; } //self& operator++(int)//错误, self operator++(int) { self tmp(_node); _node = _node->_next; return tmp; } self& operator--() { _node = _node->_prev; return *this; } self operator--(int) { self tmp(_node); _node = _node->_prev; return tmp; } Ref operator*() { return _node->_val; } Ptr operator->() { return &_node->_val; } bool operator!=(const self& s) { return s._node!= this->_node; } bool operator==(const self& s) { return s._node == this->_node; } node* _node; }; template class List { public: typedef ListNode node; typedef __List_iterator iterator; typedef __List_iterator const_iterator; iterator begin() { return iterator(_phead->_next); } iterator end() { return iterator(_phead); } const_iterator begin()const { return iterator(_phead->_next); } const_iterator end()const { return iterator(_phead); } void empty_List() { _phead = new node; _phead->_next = _phead; _phead->_prev = _phead; } List() { empty_List(); } List(const List& lt) { empty_List(); for (const auto& e : lt) //引用更好,如果T类型是自定义类型的话 { push_back(e); } } //lt1=lt2 List& operator=(const List& lt) { clear(); for (const auto& e : lt) { push_back(e); } return *this; } void swap(List& lt) { std::swap(_phead, lt->_phead); } //lt1=lt2 List& operator=(List lt) { swap(lt); return *this; } void clear() { iterator lt = begin(); while (lt != end()) { lt = erase(lt); } } ~List() { clear(); delete _phead; _phead = nullptr; } void push_back(const T& x) { /* node* newnode = new node(x); node* tail = _phead->_prev; tail->_next = newnode; newnode->_prev = tail; newnode->_next = _phead; _phead->_prev = newnode;*/ insert(end(), x); } void push_front(const T& x) { insert(begin(),x); } iterator insert(iterator pos, const T& x) { node* newnode = new node(x); node* next = pos._node; node* prev = next->_prev; newnode->_next = next; next->_prev = newnode; newnode->_prev = prev; prev->_next = newnode; return pos; } iterator erase(iterator pos) { assert(pos!=end()); node* prev = pos._node->_prev; node* next = pos._node->_next; prev->_next = next; next->_prev = prev; delete pos._node; return next; } void pop_back() { //erase(end()._node->_prev); erase(--end()); } void pop_front() { erase(begin()); } private: node* _phead; };