STL讲解——List模拟
list的介绍
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
- 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
- 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
开始设计list
因为list是链表,所以先设计一个节点,双向节点。
template<class T>
struct ListNode
{
ListNode(const T& val = T())
: _Pre(nullptr)
, _Next(nullptr)
, _val(val)
{}
ListNode<T>* _Pre;
ListNode<T>* _Next;
T _val;
};
设计list并且对节点进行初始化。
void CreateHead()
{
_phead = new Node;
_phead->_Pre = _phead;
_phead->_Next = _phead;
}
使头结点自己连接自己
刚好也可以设计判空函数:首尾相连就是空
bool empty()
{
return _phead->_Next == _phead->_Pre;
}
设计迭代器
接下来是最难的一步了,也是最容易晕的一部分
普通迭代器很简单和之前链表(忘记了,请复习数据结构)的思路一模一样。
template<class Tr>
struct __list_iterator
{
typedef ListNode<T> node;
typedef __list_iterator<T> self;
typedef node* pnode;
pnode _pnode;
__list_iterator(node* n=nullptr)
:_pnode(n)
{
}
__list_iterator(const self& l)
:_pnode(l._pnode)
{
}
T& operator*()
{
return _pnode->_val;
}
T* operator->()
{
return &(operator*());
}
self& operator++()//前置
{
_pnode = _pnode->_Next;
return _pnode;
}
self operator++(int)//后置
{
self tmp = *this;
_pnode = _pnode->_Next;
return tmp;
}
bool operator==(const self& it)
{
return _pnode == it._pnode;
}
bool operator!=(const self& it)
{
return _pnode != it._pnode;
}
//不需要析构,因为node的归属权是属于list的,迭代器只是使用权
};
但是如果我需要const iterator怎么办?
难道再设计一个类命名为const iterator?
当然不是了,这就体现出stl大佬思维的灵活性了。
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:
- 原生态指针,比如:vector
- 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
- 指针可以解引用,迭代器的类中必须重载operator*()
- 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
- 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可
以向前 移动,所以需要重载,如果是forward_list就不需要重载– - 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
迭代器的代码:
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef ListNode<T> node;
typedef __list_iterator<T, Ref, Ptr> self;
typedef node* pnode;
pnode _pnode;
__list_iterator(node* n=nullptr)
:_pnode(n)
{
}
__list_iterator(const self& l)
:_pnode(l._pnode)
{
}
Ref operator*()
{
return _pnode->_val;
}
Ptr operator->()
{
return &(operator*());
}
self& operator++()//前置
{
_pnode = _pnode->_Next;
return _pnode;
}
self operator++(int)//后置
{
self tmp = *this;
_pnode = _pnode->_Next;
return tmp;
}
bool operator==(const self& it)
{
return _pnode == it._pnode;
}
bool operator!=(const self& it)
{
return _pnode != it._pnode;
}
//不需要析构,因为node的归属权是属于list的,迭代器只是使用权
};
这个简直神迹一般的代码,因为我不管需要引用还是const引用就会调用Ref就可以了,
需要指针或者const指针,直接调用Ptr就可以了,这些东西都是打包好的。只需要改Ref和Ptr就可以实现调的是不是带const的变量。
增、删、查、改
有了迭代器就可以实现find 、insert、erase。
insert
插入是在pos前的位置插入。插入之后pos不会失效哦。
push_back\push_front
只需要再最后\最前插入这也元素就可以了(可以复用insert)
void push_back(const T& x)
{
ListNode<T>* newNode = new ListNode<T>(x);
ListNode<T>* tail = _phead->_Pre;
tail->_Next = newNode;
newNode->_Pre = tail;
newNode->_Next = _phead;
_phead->_Pre = newNode;
}
erase
直接删除pos所在位置,pos会失效,所以要更新pos。
pop_back\pop_front
其实就是erase的复用
iterator find(const T& x)
{
auto it = begin();
while (it != end())
{
if (*it==x) return it;
++it;
}
return it;
}
iterator insert(iterator pos, const T& x)//在pos前面插入
{
Node* NewNode = new Node(x);
Node* cur = pos._pnode;
Node* prev = cur->_Pre;
prev->_Next = NewNode;
NewNode->_Pre = prev;
NewNode->_Next = cur;
cur->_Pre = NewNode;
return iterator(NewNode);
}
bool empty()
{
return _phead->_Next == _phead->_Pre;
}
iterator erase(iterator pos)
{
assert(!empty());
Node* cur = pos._pnode;
Node* prev = cur->_Pre;
Node* next = cur->_Next;
prev->_Next = next;
next->_Pre = prev;
delete cur;
return (iterator)next;
}
//void push_back(const T& val) { insert(begin(), val); }
void pop_back() { erase(--end()); }
void push_front(const T& val) { insert(end(), val); }
void pop_front() { erase(begin()); }