List容器是STL最基础也是最常用的两大容器之一。相比于vector来说,List在设计上与其有巨大差异,首先是内部结构,List在其内部维护一个环形链表的头节点(在概念上并不属于链表中的一节);其次是迭代器设计,List的迭代器实现在链表中的移动操作,也就是递增时上一个节点、递减时指向下一个节点,而不是单纯的指针值(地址)的加减;还有List以链表节点存储元素的单元,而不是数组,这也导致了List在内存分配上的精准性。总而言之,List与Vector等其他容器有着完全不同的特性。(对于链表的概念,在此不做多余叙述)。
我们依旧从不同角度将List解剖,逐个击破。List实现主要体现在以下几个点:
1.迭代器操作
2.内存操作
3.元素操作
在认识List迭代器之前我们先认识List的节点:
class _list_node
{
public:
_list_node<T>* next;
_list_node<T>* prev;
T data;
};
List节点实际上是只是一个普通的链表节点。但我们要清楚一个概念,我们应将链表结点视作vector中数组中的一个节点,也就是链表的首节点相当于a[0] (start)。他们都是容器存储元素的单元,而迭代器的实现就是建立于这些单元之上。
那我们如何在List中维护一个链表呢?我们在Vector中使用了三个表示界限的指针来勾勒出了一个数组的轮廓,由此可以想到,我们也可以使用某个具有代表性的节点的指针来表示一个链表,其余的节点都可以通过它到达。最容易想到的是维护一个首节点的指针,但我们还有另外一个难题,我们如何满足STL规范中的前闭后开区间的要求?
因为我们的链表是一个环形链表,所以可以选择在尾节点之后(同时也是首节点之前)放置一个空节点作为缓冲,即可满足最后一个元素之后为无意义空间的要求。
解决了链表结构问题后,我们将视线转回迭代器上来。List迭代器需要单独设计,不仅因为其移动操作的独特性,还因为对元素进行的取值(*)、存取(->)等操作也通过迭代器进行,而这些都因元素存储单元的独特性而需特别设计。
下面是迭代器的实现:
//list迭代器
template <typename T, typename Ref, typename Ptr>
class _list_iterator
{
friend class stl_list < T, alloc > ;
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, Ref, Ptr> self;
typedef stl_bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr Pointer;
typedef const Ptr const_Pointer;
typedef Ref Reference;
typedef const Ref const_Reference;
typedef ptrdiff_t Distance;
typedef _list_node<T>* link_type;
typedef size_t size_type;
protected:
link_type node;//内部维护一个指向节点的指针
public:
_list_iterator(){}
_list_iterator(link_type lint) :node(lint){}
_list_iterator(const _list_iterator& rhs) :node(rhs.node){}
//使用合成的copy assignment
bool operator==(const self& rhs)const{ return node == rhs.node; }
bool operator!=(const self& rhs)const{ return !(*this == rhs); }
//元素取值、成员存取操作
Reference operator*(){ return (*node).data; }
const_Reference operator*()const { return (*node).data; }
Pointer operator->(){ return &(operator*()); }
const_Pointer operator->()const{ return &(operator*()); }
//迭代器移动操作
self& operator++()
{
node = node->next;
return *this;
}
self operator++(int)
{
self temp = *this;
++*this;
return temp;
}
self& operator--()
{
node = node->prev;
return *this;
}
self operator--(int)
{
self temp = *this;
--*this;
return temp;
}
};
List迭代器内部维护一个指向节点的指针,在构造操作中将为指针设定初值。其余操作围绕这个指针来进行。
下面进入到内存配置环节。List内存操作分为:
1.节点空间的配置与释放
2.节点的创建与移除
3.链表的初始化
protected:
link_type get_node(){ return list_node_allocator::allocate(); } //在内存中分配一个节点大小的空间
void put_node(link_type p){ return list_node_allocator::deallocate(p); } //释放节点空间
link_type create_node(const T& ); //创建节点(get_node+构造节点中元素)
void empty_initialize();//初始化链表
void destroy_node(link_type );//移除一个节点(元素的析构+put_node)
节点空间的配置与释放我们直接用STL通用的空间配置器完成,其主要为创建与移除节点服务。
创建一个节点分为两步:配置空间+构造节点中的元素。类似的,移除一个节点也分为两步:元素的析构+释放空间
template <typename T, typename Alloc>
typename stl_list<T, Alloc>::link_type stl_list<T, Alloc>::create_node(const T& value)
{
link_type p = get_node();
construct(&p->data, value);
return p;
}
template <typename T, typename Alloc>
void stl_list<T, Alloc>::destroy_node(link_type p)
{
destroy(&(p->data));
put_node(p);
}
链表的初始化也分为两步:为特殊的尾后空节点分配一个空间+设置指针使链表成为空链表
template <typename T,typename Alloc>
void stl_list<T, Alloc>::empty_initialize()
{
node = get_node();
node->next = node;
node->prev = node;
}
将node的前向、后向指针全部指向自己,这样就满足了空链表的验证条件:
iterator begin(){ return (*node).next; }
iterator end(){ return node; }
bool empty(){ return node->next == node; }
这里要特殊说下size()操作,如果容器使用的是RandomAccessIterator(随机存取迭代器),那我们可以简单的用finish-start来完成。但List使用的是:
typedef stl_bidirectional_iterator_tag iterator_category;
那这就很尴尬了,因为我们不能使用随机迭代器所特有的加减操作计算距离。我们只能通过遍历来记录大小,但是对于一个size()操作使用复杂度为N的实现很浪费。尤其是容器的大小也是其他复杂操作经常使用的一个值,这样会很隐蔽的增加了其余操作的时间。
所以我们再List中为维护一个标志大小的数据成员:
protected:
size_type mysize=0;
并设其默认值为0,这样虽增加了类的大小,但与通过遍历来知道一个大小信息来说,这太微不足道了。我们只需在一些改变链表大小的操作之后记得更新它就好了。
List内部链表可以带来一个其他容器所不容易实现的操作:接合。
所谓接合,就是将一段链表插入到另一端链表的内部。这个操作在有些容器那里(如vector、deque),将是个打代码量的工作,且时间复杂度还很糟糕。但对于链表来说,由于其特殊的元素之间的连接方式,则很容易实现一个(一些元素)放到另一些元素之间的操作,并且复杂度为O(1)。下面为接合操作的实现:
template <typename T,typename Alloc>
void stl_list<T, Alloc>::transfer(iterator position, iterator first, iterator last)
{
if (position != last)
{
(first.node->prev)->next = last.node;
(last.node->prev)->next = position.node;
(position.node->prev)->next = first.node;
link_type temp = position.node->prev;
position.node->prev = last.node->prev;
last.node->prev = first.node->prev;
first.node->prev = temp;
}
}
对于指针之间的操作,光看代码很容易混乱,建议大家画一下图,就会发现实际上很简单。(写类似代码时也是一样)
我们将接合操作扩展一下,就可以实现很多其他更通用的操作:
template <typename T, typename Alloc>
typename stl_list<T, Alloc>::iterator stl_list<T, Alloc>::insert(iterator position, const T& value)
{
link_type temp = create_node(value);
temp->next = position.node;
temp->prev = position.node->prev;
(link_type(position.node->prev))->next = temp;
position.node->prev = temp;
++mysize;
return temp;
}
template<typename T, typename Alloc>
typename stl_list<T, Alloc>::iterator stl_list<T, Alloc>::erase(iterator position)
{
link_type prev_node = position.node->prev;
link_type next_node = position.node->next;
prev_node->next = position.node->next;
next_node->prev = prev_node;
destroy_node(position.node);
--mysize;
return iterator(next_node);//返回被移除元素的下一个元素
}
template <typename T, typename Alloc>
void stl_list<T, Alloc>::remove(const T& value)
{//清除所有相邻且值相同的元素,在对链表排序之后可使用
iterator first = begin();
iterator last = end();
while (first != last)
{//因为有可能清除first指向的对象,所以保存first->next的信息
iterator temp = first.node->next;
if (*first == value)
erase(first);
first = temp;
}
}
template <typename T, typename Alloc>
void stl_list<T, Alloc>::splice(iterator position, stl_list<T, Alloc>& rhs)
{//将rhs中的所有元素放置到position之前
if (!rhs.empty())
transfer(position, rhs.begin(), rhs.end());
mysize += rhs.mysize;
rhs.mysize = 0;
}
template <typename T, typename Alloc>
void stl_list<T, Alloc>::splice(iterator position, stl_list<T, Alloc>& rhs, iterator first, iterator last)
{//将rhs中的first到last之间的元素放到position之前
if (this != &rhs)
{
if (first == rhs.begin() && last == rhs.end())
mysize += rhs.mysize;
else
mysize += count(first, last);
}
if (first != last)
transfer(position, first, last);
}
template <typename T, typename Alloc>
void stl_list<T, Alloc>::splice(iterator position, iterator i)
{//将i放到position之前
iterator j = i;
++j;
if (position == j || position == i)
return;
transfer(position, i, j);
++mysize;
}
注意,transfer操作为剪贴性质。
以上就是List最特殊的操作,其他常规操作下面一并贴出:
iterator begin(){ return (*node).next; }
iterator end(){ return node; }
bool empty(){ return node->next == node; }
void push_back(const T& value){ insert(end(), value); }
void push_front(const T& value){ insert(begin(), value); }
void pop_back();
void pop_front(){ erase(begin()); };
void clear();
template <typename T, typename Alloc>
void stl_list<T, Alloc>::pop_back()
{
iterator temp = end();
erase(--temp);
}
template <typename T, typename Alloc>
void stl_list<T, Alloc>::clear()
{
iterator cur = begin();
iterator temp;
while (cur != node)
{
temp = cur;
cur = cur.node->next;
destroy_node(temp);
}
node->next = node;
node->prev = node;
mysize = 0;
}