Analysis of stl source code 04-list

One, list overview

  • Syntax for using list: https://blog.csdn.net/qq_41453285/article/details/105483054
  • In general: circular doubly linked list
  • Features:
    • The bottom layer is implemented using a linked list and supports two-way sequential access
    • Insertion and deletion at any position in the list are fast
    • Random access is not supported , in order to access an element, the entire container must be traversed
    • Compared with other containers, the additional memory overhead is large
  • Design purpose: to make the container insert and delete quickly at any position
  • When to use:
    • The container needs to constantly insert or delete elements in the middle
    • No matter whether it is deleted or added, the iterators, references, and pointers of the list will not be invalidated
  • Comparison with other containers:
vector Variable size array . Support fast random access. Inserting or deleting elements beyond the tail can be slow
and Deque . Support fast random access. Insert/delete at the beginning and end is fast
list Doubly linked list . Only supports two-way sequential access. Insertion and deletion at any position in the list are fast
forward_list Singly linked list . Only supports one-way sequential access. Insert and delete operations at any position in the linked list are fast
array Fixed size array . Support fast random access. Cannot add or delete elements
string A container similar to vector , but dedicated to storing characters. Random access is fast. Fast insertion or deletion at the tail

2. List node (__list_node)

  • Each node of the list is a structure. The following is the node structure of the list:

Source code 

template <class T>

struct __list_node {

    typedef void* void_pointer;

    void_pointer prev; //类型为void*。其实可设为__list_node<T>*

    void_pointer next;

    T data;

};

 

  • The figure below is what the structure looks like

Three, the iterator of the list

  • List can no longer use native indicators as iterators like vectors, because its nodes are not guaranteed to exist continuously in the storage space
  • The list iterator must be able to point to the nodes of the list, and be able to do the correct increment, decrement, value, member access and other actions . The so-called "correct increment, decrement, value, and member access of the list iterator" action refers to: point to the next node when incrementing, point to the previous node when decrementing, and the data value of the node is taken when the value is taken, and when the member is taken What is taken is the member of the node, as shown in the following figure:

  • Since list is a double linked-list (double linked-list), the iterator must have the ability to move forward and backward . So list provides Bidirectional Iterators
  • List has the following important properties:
    • Neither insert nor splice will cause the original list iterator to fail . This is not true in the vector, because the insertion action of the vector may cause memory reconfiguration and cause all the original iterators to fail.
    • Even the delete action of the list element (erase), only the iterator "pointing to the deleted element" is invalid, other iterators are not affected in any way

Iterator source code

template<class T, class Ref, class Ptr>

struct list_iterator {

    typedef list_iterator<T, T&, T*> iterator;
    
    typedef list_iterator<T, Ref, Ptr> self;


    typedef bidirectional_iterator_tag iterator_category;

    typedef T value_type;

    typedef Ptr pointer;

    typedef Ref reference;

    typedef list_node<T>* link_type;

    typedef size_t size_type;

    typedef ptrdiff_t difference_type;


    link_type node; // 迭代器内部当然要有一个原生指标,指向list的节点


    // constructor

    list_iterator(link_type x) : node(x) {}

    list_iterator() {}

    list_iterator(const iterator& x) : node(x.node) {}


    bool operator==(const self& x) const { return node == x.node; }

    bool operator!=(const self& x) const { return node != x.node; }

    // 以下对迭代器取值(dereference),取的是节点的数据值

    reference operator*() const { return (*node).data; }


    // 以下是迭代器的成员存取(member access)运算子的标准作法

    pointer operator->() const { return &(operator*()); }


    //对迭代器累加1,就是前进一个节点

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

    }

    self operator++(int) {

        self tmp = *this;

        ++*this;

        return tmp;

    }


    //对迭代器递减1,就是后退一个节点

    self& operator--() {

        node = (link_type)((*node).prev);

        return *this;

    }


    self operator--(int) {

        self tmp = *this;

        --*this;

        return tmp;

    }

};

 

Fourth, the data structure of the list

  • The list is not only a bidirectional serial, but also a circular doubly linked list . So it only needs a pointer to fully represent the entire linked list

link_type, node node

  • node node is a pointer to the last node of the list
template <class T, class Alloc = alloc> //默认使用alloc为配置器

class list {

protected:

    typedef __list_node<T> list_node;

public:

    typedef list_node* link_type;

protected:

    link_type node; // 只要一个指针,便可表示整个环状双向链表

    ...

};

 

functions such as begin() and end()

  • If the indicator node is made to point to a blank node deliberately placed at the end, the node can meet the STL requirements for the "close before and open" interval and become a last iterator, as shown in the following figure. In this way, it can be easily completed with several functions:
iterator begin() { return (link_type)((*node).next); }


iterator end() { return node; }


bool empty() const { return node->next == node; }


size_type size() const {

    size_type result = 0;

    distance(begin(), end(), result); // 全局函式,第 3章。
    
    return result;

}


// 取头节点的内容(元素值)

reference front() { return *begin(); }


// 取尾节点的内容(元素值)

reference back() { return *(--end()); }

5. List construction and memory management (constructor, push_back, insert)

List memory management (list_node_allocator)

  • List uses alloc as the space configurator by default , and additionally defines a list_node_allocator accordingly, in order to more conveniently use node size as the configuration unit:
template <class T, class Alloc = alloc> //默认使用alloc为配置器

class list {

protected:

    typedef __list_node<T> list_node;

    // 专属之空间配置器,每次配置一个节点大小:

    typedef simple_alloc<list_node, Alloc> list_node_allocator;

    ...

};
  • Thus, list_node_allocator(n) means to configure n node spaces. The following four functions are used to configure, release, construct, and destroy a node:
protected:

    // 配置一个节点并传回

    link_type get_node() { return list_node_allocator::allocate(); }


    // 释放一个节点

    voidput_node(link_typep){list_node_allocator::deallocate(p);}


    // 产生(配置并构造)一个节点,带有元素值

    link_type create_node(const T& x) {

        link_type p = get_node();

        construct(&p->data, x);//全局函数,构造/析构基本工具

        return p;

    }


    // 摧毁(解构并释放)一个节点

    void destroy_node(link_type p) {

        destroy(&p->data); //全局函数,构造/析构基本工具

        put_node(p);

    }

 

Constructor

  • The list provides many constructors, one of which is the default constructor, which allows us to make an empty list without specifying any parameters:
public:

    list() { empty_initialize(); } //产生一个空链表

protected:

    void empty_initialize() {

        node = get_node(); //配置一个节点空间,令node指向它

        node->next = node; //令node头尾都指向自己,不设元素值

        node->prev = node;

    }
  • When the list is empty, the prev and next of the node point to itself

push_back、insert

  • When we insert a new element at the end of the list with push_back() , this function calls insert() internally:
void push_back(const T& x) { insert(end(), x); }
  • insert() is an overloaded function , which has many forms, the simplest of which is as follows, which meets the above requirements. First configure and construct a node, and then perform appropriate pointer actions at the end to insert the new node:
//函数目的:在迭代器 position 所指位置插入一个节点,内容为x

iterator insert(iterator position, const T& x) {

    link_type tmp = create_node(x);//产生一个节点(设妥内容为x)

    //调整双向指针,使 tmp插入进去

    tmp->next = position.node;

    tmp->prev = position.node->prev;

    (link_type(position.node->prev))->next = tmp;

    position.node->prev = tmp;

    return tmp;

}
  • Therefore, if the program inserts five nodes (the values ​​are 0, 1, 2, 3, 4) in a row, the state of the list is as shown in the figure below

  • If we want to insert a new node somewhere in the list, we must first determine the placement position. For example, if I want to insert a node with a data value of 99 at a node with a data value of 3, I can do this:
ilite = find(il.begin(), il.end(), 3);

if (ilite!=0)

il.insert(ilite, 99);
  • The find() operation will be explained later. The state of the list after insertion is shown in the figure below. Note that after the insertion is complete, the  new node will be in front of the node pointed to by the Pacesetter Iterator (marking the insertion point)-this is the STL standard specification for "insertion actions" . Since the list is not like a vector, it is possible to perform reconfiguration and data movement operations when the space is insufficient, so all the iterators before the insertion are still valid after the insertion.

Six, the element operation of the list

push_front、push_back

//插入一个节点,做为头节点

void push_front(const T& x) { insert(begin(), x); }


//插入一个个节点,做为尾节点

void push_back(const T& x) { insert(end(), x); }

 

erase

//移除迭代器position所指节点

iterator erase(iterator position) {

    link_type next_node = link_type(position.node->next);

    link_type prev_node = link_type(position.node->prev);

    prev_node->next = next_node;

    next_node->prev = prev_node;

    destroy_node(position.node);

    return iterator(next_node);

}
  •  Since list is a two-way circular linked list, as long as we handle the marginal conditions well, insert elements (push_front and push_back) at the head or tail, and the action is almost the same. Remove elements at the head or tail (pop_front and pop_back). ), the action is almost the same. To erase (erase) an element pointed to by an iterator is just to do some pointer movement, which is not complicated. If the image above is searched and removed again, the situation will be as shown in the image below
ite = find(ilist.begin(), ilist.end(), 1);

if (ite!=0)

    cout << *(ilist.erase(ite)) << endl;

 

pop_front、pop_back 

//移除头节点

void pop_front() { erase(begin()); }


//移除尾节点

void pop_back() {

    iterator tmp = end();

    erase(--tmp);

}

 

clear 

// 清除所有节点(整个链表)

template <class T, class Alloc>

void list<T, Alloc>::clear()

{

    link_type cur = (link_type) node->next; //begin()

    while (cur != node) { //遍历每一个节点

        link_type tmp = cur;

        cur = (link_type) cur->next;

        destroy_node(tmp); // 销毁(析构并释放)一个节点

    }


    //恢复node原始状态

    node->next = node;
    
    node->prev = node;

}

 

remove 

//将数值为value的所有元素移除

template <class T, class Alloc>

void list<T, Alloc>::remove(const T& value) {

    iterator first = begin();

    iterator last = end();

    while (first != last) { //遍历每一个节点

        iterator next = first;

        ++next;

        if (*first == value)

            erase(first); //找到就移除

        first = next;

    }

}

 

unique 

//移除数值相同的连续元素。注意,只有“连续而相同的元素”,才会被移除剩一个

template <class T, class Alloc>

void list<T, Alloc>::unique()

{

    iterator first = begin();

    iterator last = end();

    if (first == last)

        return; //空链表,什么都不必做

    iterator next = first;

    while (++next != last) { //遍历每一个节点

        if (*first == *next) //如果在此区段中有相同的元素

            erase(next); //移除之

        else

            first = next; //调整指针

    next = first; //修正区段范围

    }

}

 

transfer

  • The list provides a so-called transfer action (transfer): before moving a continuous range of elements to a specific position. Technically very simple, just move the pointer between nodes
  • This action lays a good foundation for other complex actions such as splice, sort, merge, etc.
  • The following is the source code of transfer, transfer is not a public interface:
protected:

//将[first,last)内的所有元素搬移到position之前

void transfer(iterator position, iterator first, iterator last) {

    if (position != last) {

        (*(link_type((*last.node).prev))).next = position.node; // (1)

        (*(link_type((*first.node).prev))).next = last.node; // (2)

        (*(link_type((*position.node).prev))).next = first.node; // (3)

        link_type tmp = link_type((*position.node).prev); // (4)

        (*position.node).prev = (*last.node).prev; // (5)

        (*last.node).prev = (*first.node).prev; // (6)

        (*first.node).prev = tmp; // (7)

    }
    
}
  • The above seven actions are shown in the following figure:

splice

  • The above transfer is not a public interface. What the list publicly provides is the so-called splice action: moving a continuous range of elements from one list to another (or the same) list at a certain point
  • The following is a demonstration case:
int iv[5] = { 5,6,7,8,9 };

list<int> ilist2(iv, iv+5);


//假设ilist的内容为0 2 99 3 4

ite = find(ilist.begin(), ilist.end(), 99);


ilist.splice(ite,ilist2); // 0 2 5 6 7 8 9 99 3 4

ilist.reverse(); // 4 3 99 9 8 7 6 5 2 0

ilist.sort(); // 0 2 3 4 5 6 7 8 9 99
  • It is easy to see the effect. The figure below shows the splicing action. The technology is very simple, just the pointer movement between nodes, these actions have been completely done by transfer ()

  • In order to provide various interface flexibility , list::splice has many versions:
public:

//将x接合于position所指位置之前。x必须不同于*this

void splice(iterator position, list& x) {

    if (!x.empty())

        transfer(position, x.begin(), x.end());

}


//将i所指元素接合于position所指位置之前。position和i可指向同一个list

void splice(iterator position, list&, iterator i) {

    iterator j = i;

    ++j;

    if (position == i || position == j)
        return;

    transfer(position, i, j);

}


//将[first,last) 内的所有元素接合于 position 所指位置之前

//position 和[first,last)可指向同一个list,

//但position不能位于[first,last)之内

void splice(iterator position, list&, iterator first, iterator last) {

    if (first != last)

        transfer(position, first, last);

}

 

  • The following is the source code of merge(), reverse(), sort(). With transfer() in hand, these actions are not difficult to complete

merge()

// merge()将x合并到*this身上。两个lists的内容都必须先递增排序

template <class T, class Alloc>

void list<T, Alloc>::merge(list<T, Alloc>& x) {

    iterator first1 = begin();

    iterator last1 = end();

    iterator first2 = x.begin();

    iterator last2 = x.end();


    // 注意:前提是,两个lists都已经经过递增排序

    while (first1 != last1 && first2 != last2)

        if (*first2 < *first1) {

            iterator next = first2; transfer(first1,

            first2, ++next);

            first2 = next;

        }

        else

            ++first1;

    if (first2 != last2)

        transfer(last1, first2, last2);

}

reverse() 

//reverse()将*this的内容逆向重置

template <class T, class Alloc>

void list<T, Alloc>::reverse() {

    //以下判断,如果是空链表,或仅有一个元素,就不做任何动作

    //使用 size() == 0 || size() == 1 来判断,虽然也可以,但是比较慢

    if (node->next == node || link_type(node->next)->next == node)

        return;


    iterator first = begin();

    ++first;


    while (first != end()) {

        iterator old = first;

        ++first;

        transfer(begin(), old, first);

    }

}

sort()

//list不能使用STL算法sort(),必须使用自己的sort() member function,

//因为STL算法 sort()只接受RamdonAccessIterator.

//本函数采用 quick sort


template <class T, class Alloc>

void list<T, Alloc>::sort() {

    // 以下判断,如果是空链表,或仅有一个元素,就不做任何动作

    // 使用 size() == 0 || size() == 1 来判断,虽然也可以,但是比较慢

    if (node->next == node || link_type(node->next)->next == node)

        return;


    //一些新的lists,做为中介数据存放区

    list<T, Alloc> carry;

    list<T, Alloc> counter[64];

    int fill = 0;


    while (!empty()) {

        carry.splice(carry.begin(), *this, begin());

        int i = 0;

        while(i < fill && !counter[i].empty()) {

            counter[i].merge(carry);

            carry.swap(counter[i++]);

        }

    carry.swap(counter[i]);

    if (i == fill)

        ++fill;

}


for (int i = 1; i < fill; ++i)

    counter[i].merge(counter[i-1]);

    swap(counter[fill-1]);

 

Guess you like

Origin blog.csdn.net/www_dong/article/details/113845160