STL—list

前面我们分析了vector,这篇介绍STL中另一个重要的容器list

list的设计

list由三部分构成:list节点、list迭代器、list本身

list节点

list是一个双向链表,所以其list节点中有前后两个指针。如下:

// list节点
template <typename T>
struct __list_node
{
        typedef void* void_pointer;
        void_pointer prev; // 指向前一个节点
        void_pointer next; // 指向下一个节点
        T data; // 节点的值
};

list迭代器

前面我们说过vector是利用其内存分配类型成员给vector分配一大块内存,而其迭代器是原始指针,所以其迭代器的移动就是指针的移动,vector那样通过指针的移动就能得到下一个元素,不需要特别设计。而list是链表结构,链表中每个节点的内存不连续,list的迭代器就是对外隐藏了从一个节点是如何移动到下一个节点的具体细节,使得外部只要将迭代器自增或自减就能得到相邻的节点。

list迭代器只有一个指向链表节点的指针数据成员。如下:

        typedef __list_node<T>* link_type;

        link_type node;  // point to __list_node

下面是迭代器的前置自增和前置自减运算符的源码,可以看到是通过节点的前向和后向指针来完成从一个节点移动到另一个节点:

        self& operator++() { node = (link_type)(*node).next; return *this;}
        self& operator--() { node = (link_type)(*node).prev; return *this;}

list

和vector一样,list也有个空间配置器的类型成员,通过该类型来为list的每个节点分配内存,并且通过该类型成员将外部指定的节点数目转换为相应节点所需的内存所以list的内存模型是每个链表节点分配一块单独的内存,然后将每个节点连接起来。而vector的内存模型是分配一大块连续的内存。如下:

          // 空间配置器
          typedef simple_alloc<list_node, alloc> list_node_allocator;

实际上,list不仅是一个双向链表,而且还是一个环状的双向链表。为了设计的方便,在list中放置一个node指针,该指针指向一个空白节点,该空白节点的下一个节点是链表中起始节点,而链表的尾节点的下一个节点为该空白节点。虽然list底层是一个环状双向链表,但通过这样设计后对外就表现出一个普通的双向链表,符合一般习惯。这样设计还有很多好处,比如快速得到链表的首尾节点。如下。

private:
        //指向空白节点
        link_type               node;
public:
        // 通过空白节点node完成
        iterator begin() const { return (link_type)(*node).next; }
        iterator end() const { return node;}
        bool empty() const { return node->next == node; }

下面我们看list内部是如何构造一个链表的。以我们对list的常用使用方法 list<int> lis(10)为例:

首先调用构造函数

        explicit list(size_type n)
        {
                empty_initialize();
                insert(begin(), n, T());
        }

该构造函数会先调用empty_initialize()为list分配一个空白节点,并设置前向后向指针

        void empty_initialize()
        {
                node = get_node();
                node->next = node;
                node->prev = node;
        }
        link_type get_node() { return list_node_allocator::allocate(1);}

然后构造函数会循环以插入相应个数的链表节点,每次插入时会分配一个节点大小的内存,然后对这块内存初始化,注意插入位置是在指定位置之前插入。由于list的内存模型和vector内存模型的区别,vector每次插入时由于可能会造成内存的重新配置,会造成原先所有的迭代器失效。而list的插入只是为新节点分配内存,并将其添加到链表中,对链表中其他节点的内存不会造成影响,所以list的插入则不会引起迭代器失效。如下。

template <typename T>
void
list<T>::insert(iterator position, size_type n, const T& x)
{
        for (; n > 0; --n)
                insert(position, x);
}

template
<typename T> void list<T>::insert(iterator position, const T& x)//posiiton之前插入 { link_type tmp = create_node(x); tmp->next = position.node; tmp->prev = position.node->prev; (link_type(position.node->prev))->next = tmp; position.node->prev = tmp; }
link_type create_node(
const T& x) { link_type p = get_node(); construct(&p->data, x); return p; }
link_type get_node() {
return list_node_allocator::allocate(1);}

(全文完)

附:
一款简易版STL的实现,项目地址:https://github.com/zinx2016/MiniSTL/tree/master/MiniSTL
 
 
原文地址:https://www.cnblogs.com/zxiner/p/7202558.html