目录
一、 list的介绍及使用
1. list的介绍
1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)
2. list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。
2.1 list的构造
2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
【注意】 :
1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
2.3 list capacity
2.4 list element access
2.5 list modifiers
2.6 list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
二、 list的模拟实现
1. list的结构
list的结构是带头双向循环链表,这里的链表结构虽然复杂,但是操作起来却简单不少。
//List的节点类
template<class T>
struct ListNode
{
ListNode(const T& val = T())
:_pPrve(nullptr)
,_pNext(nullptr)
,_val(val)
{}
ListNode<T>* _pPrve;
ListNode<T>* _pNext;
T _val;
};
//list类
template<class T>
class list
{
typedef ListNode<T> Node;
private:
//初始化哨兵位节点
void CreateHead()
{
_pHead = new Node(T());
_pHead->_pPrve = _pHead;
_pHead->_pNext = _pHead;
_size = 0;
}
Node*_pHead;
size_t _size;
};
2. insert和push
这里的头插尾插都是复用的insert,这里的insert表示在pos位置之前插入,知道当前位置,再通过当前位置的前置指针找到前一个节点的位置,把新节点插入到他们两个中间就可以了。
如果没有实现迭代器,那么头插就是在哨兵位之后插入节点,尾插就是在哨兵位之前插入,因为链表是个循环,我们可以很容易找到头和尾。
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._pNode;
Node* prve = cur->_pPrve;
prve->_pNext = newnode;
newnode->_pPrve = prve;
newnode->_pNext = cur;
cur->_pPrve = newnode;
++_size;
//迭代器构造
return iterator(newnode);
}
//尾插
void push_back(const T& val)
{
insert(end(), val);
}
//头插
void push_front(const T& val)
{
insert(begin(), val);
}
3. erase和pop
erase这里也是用迭代器,删除当前位置,返回节点的下一个位置。
如果没有实现迭代器,头删就是删除哨兵位的下一个节点,尾删就是删除哨兵位的前一个节点。
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
assert(pos != end());
Node* prve = pos._pNode->_pPrve;
Node* next = pos._pNode->_pNext;
prve->_pNext = next;
next->_pPrve = prve;
delete pos._pNode;
--_size;
//迭代器构造
return iterator(next);
}
//尾删
void pop_back()
{
erase(--end());
}
//头删
void pop_front()
{
erase(begin());
}
4. 迭代器类
//List的迭代器类
template<class T,class Ref,class Ptr>
class ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T,Ref,Ptr> Self;
public:
//迭代器构造
ListIterator(Node* pNode = nullptr)
:_pNode(pNode)
{}
ListIterator(const Self& it)
:_pNode(it._pNode)
{}
Node* _pNode;
};
4.1 * , -> , != , ==
//返回当前节点的值
Ref operator*()
{
return _pNode->_val;
}
//返回当前节点的值的地址
Ptr operator->()
{
return &(_pNode->_val);
}
//迭代器相不相同
bool operator!=(const Self& it)
{
return _pNode != it._pNode;
}
bool operator==(const Self& it)
{
return _pNode == it._pNode;
}
4.2 ++,--
//前置++
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
//后置++
Self operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
//前置--
Self& operator--()
{
_pNode = _pNode->_pPrve;
return *this;
}
//后置--
Self operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPrve;
return *this;
}
5. begin 和end
begin是哨兵位的下一个节点。
end是哨兵位节点,也就是尾结点的下一个节点。
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
//迭代器
iterator begin()
{
return iterator(_pHead->_pNext);
}
iterator end()
{
return iterator(_pHead);
}
const_iterator begin()const
{
return const_iterator(_pHead->_pNext);
}
const_iterator end()const
{
return const_iterator(_pHead);
}
6. 构造,拷贝构造,赋值重载, 析构
这里的拷贝构造和赋值重载用的是一个现代写法,找一个打工人,然后把他的成果拿来用。
//构造
list()
{
//初始化哨兵位节点
CreateHead();
}
//构造n个节点
list(int n, const T& value = T())
{
CreateHead();
while (n--)
{
push_back(value);
}
}
//用迭代器区间去构造
template<class iterator>
list(iterator first, iterator last)
{
CreateHead();
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造 it2(it1)
list(const list<T>& it)
{
CreateHead();
list<T> tmp(it.begin(), it.end());
swap(tmp);
}
//赋值重载
list<T>& operator=( list<T> it)
{
CreateHead();
swap(it);
return *this;
}
//析构
~list()
{
//清空链表节点(不删除哨兵位)
clear();
delete _pHead;
_pHead = nullptr;
}
7. size和empty,swap,clear
//容量
size_t size()const
{
return _size;
}
bool empty()const
{
return _size == 0;
}
//清除链表节点,不删除哨兵位节点
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void swap(list<T>& it)
{
std::swap(_pHead, it._pHead);
std::swap(_size, it._size);
}
8. front和back
//取头节点的值
T& front()
{
return _pHead->_pNext->_val;
}
const T& front()const
{
return _pHead->_pNext->_val;
}
//取尾节点的值
T& back()
{
return _pHead->_pPrve->_val;
}
const T& back()const
{
return _pHead->_pPrve->_val;
}
操作演示:
三、 list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不 同,其主要不同如下: