[C++] Simulation implementation of common interface of STL_list

insert image description here

1. Introduction and use of list

1.1 Introduction to list

list document introduction

  1. list is a sequential container that can be inserted and deleted at any position within a constant range, and the container can be iterated back and forth.
  2. The bottom layer of the list is a doubly linked list structure. Each element in the doubly linked list is stored in an independent node that is not related to each other, and the pointer points to the previous element and the next element in the node.
  3. list is very similar to forward_list: the main difference is that forward_list is a singly linked list that can only be iterated forward, making it simpler and more efficient.
  4. Compared with other serial containers (array, vector, deque), list usually has better execution efficiency for inserting and removing elements at any position.
  5. Compared with other sequential containers, the biggest defect of list and forward_list is that it does not support random access at any position. For example, to access the sixth element of the list, you must iterate from a known position (such as the head or tail) to the position over which iterating requires linear time overhead; the list also requires some additional space to hold the associated information for each node (this can be an important factor for large lists storing elements of smaller types )

1.2 Use of lists

There are many interfaces in the list, which is similar here. You only need to master how to use them correctly, and then go in-depth to study the principles behind them to achieve scalability.

2. list iterator

Here, we can temporarily understand the iterator as a pointer, which points to a node in the list.
We all know that list is not a continuous space. We save the address of the next location and go to the next one through the address, so we need to overload some operators.
Backward ++ (pre-post)
forward-- (pre-post)
dereferencing *, ->
equality judgment ==, !=

Here we create a class for the list node, and we can use this class directly later, and then write a default constructor to facilitate the opening of nodes later.

// List的节点类
template<class T>
struct ListNode
{
    
    
    ListNode(const T& val = T())
        :_pPre(nullptr)
        ,_pNext(nullptr)
        ,_val(val)
    {
    
    }

    ListNode<T>* _pPre;
    ListNode<T>* _pNext;
    T _val;
};


//List的迭代器类
template<class T, class Ref, class Ptr>
class ListIterator
{
    
    
    typedef ListNode<T>* PNode;
    typedef ListIterator<T, Ref, Ptr> Self;
public:
    ListIterator(PNode pNode = nullptr)
        :_pNode(pNode)
    {
    
    }

    ListIterator(const Self& l)
    {
    
    
        _pNode = l._pNode;
    }

    T& operator*()
    {
    
    
        return _pNode->_val;
    }

    T* operator->()
    {
    
    
        return &_pNode->_val;
    }

    Self& operator++()
    {
    
    
        _pNode = _pNode->_pNext;
        return *this;
    }

    Self& operator++(int)
    {
    
    
        Self tmp(*this);
        _pNode = _pNode->_pNext;

        return tmp;
    }

    Self& operator--()
    {
    
    
        _pNode = _pNode->_pPre;
        return *this;
    }

    Self& operator--(int)
    {
    
    
        Self tmp(*this);
        _pNode = _pNode->_pPre;

        return tmp;
    }

    bool operator!=(const Self& l)
    {
    
    
        return _pNode != l._pNode;
    }

    bool operator==(const Self& l)
    {
    
    
        return _pNode == l._pNode;
    }

//private:
    PNode _pNode;
};

3. The structure of the list

Constructor ( constructor ) Interface Description
list (size_type n, const value_type& val = value_type()) The constructed list contains n elements whose value is val
list() Construct an empty list
list(const list& x) copy constructor
list(InputIterator first, InputIterator last) Constructs a list with elements in the range [first, last)

We have written too much about the structure, and we will kill these interfaces directly.
Construct list with empty parameters:

list()
{
    
    
    _pHead = new Node;
    _pHead->_pNext = _pHead;
    _pHead->_pPre = _pHead;
}

Copy construct list:

list(const list<T>& l)
{
    
    
    _pHead = new Node;
    _pHead->_pNext = _pHead;
    _pHead->_pPre = _pHead;

    for (auto e : l)
    {
    
    
        push_back(e);
    }
}

n constructs with value val:

list(int n, const T& value = T())
{
    
    
    _pHead = new Node;
    _pHead->_pNext = _pHead;
    _pHead->_pPre = _pHead;
    
    for (int i = 0; i < n; i++)
    {
    
    
        push_back(value);
    }
}

Iterator range construction:

template <class Iterator>
list(Iterator first, Iterator last)
{
    
    
    CreateHead();
    while (first != last)
    {
    
    
        push_back(*first);
        ++first;
    }
}

4. Realization of list commonly used interfaces

What we have implemented is a bidirectional list with a leading loop, so the structure is:
insert image description here

Let's first implement the interface that gets the head and tail :

iterator begin()
{
    
    
    return _pHead->_pNext;
}
iterator end()
{
    
    
    return _pHead;
}
const_iterator begin() const
{
    
    
    return _pHead->_pNext;
}
const_iterator end() const
{
    
    
    return _pHead;
}

4.1 list capacity

function declaration Interface Description
empty Check whether the list is empty, return true, otherwise return false
size Returns the number of valid nodes in the list

The two interfaces here are relatively simple, and we will kill them directly.

size_t size() const
{
    
    
    int count = 0;

    const_iterator it = begin();
    while (it != end())
    {
    
    
        ++count;
        ++it;
    }
    return count;
}
bool empty() const
{
    
    
    return _pHead == _pHead->_pNext;
}

4.2 Insertion, deletion, exchange, cleanup

function declaration Interface Description
push_front Insert an element with value val before the first element of the list
pop_front delete the first element in the list
push_back Insert an element with value val at the end of the list
pop_back delete the last element in the list
insert Insert an element with value val in list position
erase Delete the element at list position
swap Swap the elements of two lists
clear Clear the valid elements in the list

4.2.1 insert at any position

insert image description here

Ideas:
1. We first create a new node, newnode, and assign it a value of x (the constructor in the list node class will be called here); 2. Write down the
node prev before the position of pos, and assign the three nodes newnode, prev, and pos Connect;
3. Return the iterator of newnode.
insert image description here

Code:

// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
    
    
    PNode cur = pos._pNode;
    PNode prev = cur->_pPre;

    PNode newnode = new Node(val);
    prev->_pNext = newnode;
    newnode->_pPre = prev;
    newnode->_pNext = cur;
    cur->_pPre = newnode;

    return newnode;
}

4.2.2 push_front plug

insert image description here

For the plug-in, we can directly reuse the plug-in code.
Head insertion is to insert an element at the head of the linked list, so it is insert(begin(), x);

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

4.2.3 push_back tail plug

insert image description here

The same is true for tail insertion, and the insert code is directly reused.
Because our list is a two-way leading circular linked list, insert(end(), x) can be inserted directly before the end. end() returns the head node, and the head node is a sentinel node, so inserting before end() is tail inserting.

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

4.2.4 erase Any position delete

insert image description here

Ideas:
1. Write down the nodes before and after pos, prev, next;
2. Connect prev and next to release the pos position node;
3. After the meal, pos the next position node.

// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
    
    
    PNode cur = pos._pNode;
    PNode prev = cur->_pPre;
    PNode next = cur->_pNext;

    delete cur;
    prev->_pNext = next;
    next->_pPre = prev;

    return next;
}

4.2.5 pop_front header deletion

insert image description here

For head deletion, we can reuse the erase code directly.
To delete the header, just delete the header directly, erase(begin()).

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

4.2.6 pop_back tail deletion

insert image description here

Tail deletion also reuses the erase code, that is, deletes the last node of the list, erase(–end()).

void pop_back() {
    
     erase(--end()); }

Why here is –end(), here end() returns the head node, because the tail node is to be deleted, so –end() is needed, which is the real tail node.

4.2.7 swap()

insert image description here

The swap exchange of the list only needs to exchange the head nodes of the two linked lists, because it is stored in a chain, and the head pointer can be replaced. The exchange provided in the library can be reused directly.

void swap(list<T>& l) {
    
     std::swap(_pHead, l._pHead); }

4.2.8 clear

For the clear of the linked list, we need to release every valid node, so we traverse it and reuse erase.

void clear()
{
    
    
    iterator it = begin();
    while (it != end())
    {
    
    
        it = erase(it);
    }
}

5. List iterator invalidation problem

As mentioned earlier, here you can temporarily understand the iterator as similar to a pointer. The invalidation of the iterator means that the node pointed to by the iterator is invalid, that is, the node is deleted. Because the underlying structure of the list is a two-way circular linked list with the leading node, the iterator of the list will not be invalidated when it is inserted into the list. It will only be invalidated when it is deleted, and only the iteration pointing to the deleted node will be invalidated. , other iterators are not affected.

int main()
{
    
    
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		//这里如果先删掉了,再去更新迭代器已经被失效影响了
		lt.erase(it);
		++it;
	}

	return 0;
}

insert image description here

Amendment:

int main()
{
    
    
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		//这里如果先删掉了,再去更新迭代器已经被失效影响了
		lt.erase(it++);
	}

	return 0;
}

6. Comparison between list and vector

Both vector and list are very important sequential containers in STL. Due to the different underlying structures of the two containers, their characteristics and application scenarios are different. The main differences are as follows:

vector list
underlying structure Dynamic sequence table, a continuous space Doubly linked circular list with head node
random access Support random access, the efficiency of accessing an element is O(1) Random access is not supported, and the efficiency of accessing an element is O(N)
insert and delete Insertion and deletion at any position is inefficient, and elements need to be moved. The time complexity is O(N). When inserting, it may need to increase capacity. Increase: open up new space, copy elements, and release old space, resulting in lower efficiency Insertion and deletion at any position are highly efficient, do not need to move elements, and the time complexity is O(1)
Space utilization The bottom layer is a continuous space, which is not easy to cause memory fragmentation, high space utilization rate, and high cache utilization rate The underlying nodes are dynamically opened, and small nodes are likely to cause memory fragmentation, low space utilization, and low cache utilization
iterator Original ecological pointer Encapsulate the original ecological pointer (node ​​pointer)
iterator invalidation When inserting elements, all iterators must be reassigned, because inserting elements may cause re-expansion, causing the original iterator to become invalid. When deleting, the current iterator needs to be reassigned or it will become invalid Inserting an element will not invalidate the iterator. When deleting an element, only the current iterator will be invalidated, and other iterators will not be affected
scenes to be used Requires efficient storage, supports random access, and does not care about insertion and deletion efficiency Lots of insert and delete operations, don't care about random access

Guess you like

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/132646628