Six major components of STL for getting started with C++--In-depth analysis and simulation implementation of List source code

Article directory

Preface

1. Reading the List source code

2. Simulation implementation of List common interfaces

1. Define a list node

2. Implement an iterator

2.2const iterator

3. Define a linked list and common interfaces for implementing linked lists

3. List and Vector

Summarize


Preface

The simulation implementation presented in this article has been tested correctly by local vs. The file has been uploaded to gitee, address: list: imitating the list of stl - Gitee.com


1. Reading the List source code

First we read the source code. I read the source code in the following way: first find the definition of a single node, and then find the main member functions in the list.

The definition of list_node linked list node is as follows: it has three members, prev, next pointer, and data field.

Implement the interface of the linked list: add, delete, modify, and check. Recall that when we implemented the sequence list in C language, we used pointers to implement it. When using C++, the simulation implementation of string also uses iterators. Iterators simulate the behavior of pointers, but if a linked list directly uses pointer++, it may not necessarily be able to access the next node, so we need to encapsulate the native pointer, the iterator, to achieve access to the list.

Reading the source code, the iterator _list_iterator has three template parameters. I guess T should be the type, Ref is the reference, and Ptr is the pointer. Why are there three template parameters? Analyze later. This iterator implements a native iterator, a const iterator, const calls a const object, and then there is self. I don’t understand what it means for the time being. Continue down.

 After reading this, some parameters have been redefined. Pay attention here:

  • typedef _list_node<T> * link_type;  
  • link_type node;
  • _list_iterator(link type x):node(x){}

Like the C language, a pointer to a node is defined here as the smallest unit to implement an iterator.

Continue reading, here are some operator overloaded functions, comparing whether nodes are equal, and references. Note that the return value quoted here uses reference. It can be seen from the above that Ref is redefined as reference, so you can guess here that ref is the return value. quoted value

 The operator overloading function is implemented here, using iterators to access the previous and next nodes. Note that the return value here is self. I read earlier that self is a template parameter of _list_iterator. Compared with the previous implementation of the date class, the date class++ returned a date class object. An iterator is used here for ++, and I guess what is returned here is an iterator.

Continue to look at the use of iterators: in the member function of the list, see that begin: points to the next head node, and end points to the head node. rbegin points to the head node, rend points to the next one of the head nodes.

Determine whether the linked list is empty: Determine whether the next node of the head node points to the head node

Calculate the size of the linked list: calculate based on begin and end

 As well as the most important insertion in the linked list, find the position of pos through the iterator, build a Node based on T, and then insert. Insert can be reused for both head and tail plugs. When deleting the header plug, first keep the current pointer pointing situation, and then modify it. After the modification is completed, delete the node.

 Read on, and when you reach the main part of the list, there are two template parameters. I don’t understand what alloc is for the moment.

 A member function for creating nodes is implemented here, create_node: Create a node through a parameter x of type T.

empty_initialize empty initialization, only creates one node

2. Simulation implementation of List common interfaces

By reading the source code above, we will imitate some common interfaces that implement list.

1. Define a list node

template<class T>
struct list_node
{
    T _data;
    list_node<T> * _next;
    list_node<T> * _prev;
}

2. Implement an iterator

 Iterators are either native pointers, or custom types that encapsulate native pointers to simulate the behavior of pointers.

list uses a node pointer to construct an iterator

template<class T>
struct _list_iterator
{
    typedef list_node<T> node;
    
    typedef _list_iterator<T> self;

    //定义一个指针,指向链表
    node * _node;

    //构造函数 用一个节点指针去构造迭代器
    _list_iterator(node * n)
        :_node(n)
    {
    }

    //实现迭代器的功能
    //指针++向后遍历,就如日期类,返回的还是一个日期类对象;迭代器++,返回一个迭代器对象
     self& operator++()
    {
        _node = _node->_next;
        return *this;
    }
    
    self& operator++(int) //实现前置++
    {
        self tmp(*this);
        _node = _node->_next;
        return tmp;
    }

     self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }
    
    self& operator++(int) //实现前置++
    {
        self tmp(*this);
        _node = _node->_prev;
        return tmp;
    }

    T& operator*()
    {
        return _ndoe->data;
    }

    bool operator == (const self& s)
    {
        return _node == s._node;
    }

    bool operator !=(const self &s)
    {
        return _node != s._node;
    }

    

2.2const iterator

Suppose we pass a const object,

  • void print_list(const list<int>&lt)。
  • The type of the object is a const list<int> *, which cannot call ordinary iterators. It is a classic permission amplification, so we need to add a const to this. At this time, it becomes const* _head. The pointer itself cannot be changed, but the content pointed to can be changed. That is, we can still change the object.
  • In the stl library, it uses const to modify *this, and the return value is also a const_iterator. However, it is redundant to use const to modify *this and the return value type for all member functions, so we add a template parameter class Ref here.
	template<class T>
	struct __list_const_iterator
	{
		typedef list_node<T> node;
		typedef __list_const_iterator<T> self;
		node* _node;

		__list_const_iterator(node* n)
			:_node(n)
		{}

        //保护返回的值不能被修改
		const T& operator*()
		{
			return _node->_data;
		}

        //... 其他成员函数都相同
	};
template<class T,class Ref, class Ptr>

struct _list_iterator
{
    typedef list_node<T> node;
    typedef _list_iterator<T,Ref,Ptr> self;
    node  * _node;
    
    _list_iterator(node * n)
        :_node(n)
    {}

    Ref operator*()
    {
        return _ndoe->data;
    }
    
    //注意这里 如果我定义了一个AA类型的链表,通过迭代器去访问,指针类型为AA*
    // 现在要访问它的成员 可以这样: *(it)._a1; 
    //也可以it->->a1 一个是运算符重载调用,it是自定义类型,无法直接使用箭头,it-> 就相当于运算符重载operator-> AA*
    //一个是找成员 
    //这里为了增强可读性 省略了一个箭头 it->_a1;
    Ptr operator->()
    {
        return &_node->data;
    }
    
    self& operator--()
    {
    }
    //其余运算符操作类似
}

template<class T>
class list
{
    //注意这里模板参数 调用普通迭代器 T&传给ref, 调用const迭代器 const T& 传给ref 
    typedef _list_iterator<T,T&,T*> iterator;
    typedef _list_iterator<T,const T&, const T*> const_iterator;

    iterator begin()
    {
        return iterator(_head->_next);
    }

    const iterator begin()
    {
        return const_iterator(_head->_next);
    }
    //..其余类似
}

3. Define a linked list and common interfaces for implementing linked lists

template<class T>
struct _list
{
    typedef list_node<T> node;
    typedef _list_iterator<T> iterator;

    //list的成员
    node* _head;
    
    //list的构造 初始只有一个头节点
    list()
    {
        _head = new node;
        _head->_next = _head;
        _head->_prev = _head;
    }

    //list的一些成员函数 

    //通过迭代器定位begin和end

      iterator begin()
      {
        return iterator(_head->_next);
      }
        
      iterator end()
      {
            return iterator(_head);
       }
    //通过迭代器对指定pos位置进行增删改查
   void  insert(iterator pos, const T& x)
    {
        node new_node = new node(x);
        //iterator cur = pos;
        //这里是要通过pos的指针,找到这个节点 对pos进行解引用
        node * cur = pos._node;
        node * prev = cur->_prev;
      
        prev->_next = new_node;
        new_node->_prev = prev;
        new_node->_next = cur;
        cur->_prev = new_node;
    }

    void push_back()
    {
        insert(end(),x);
    }
    
    void push_front()
    {
        insert(begin(),x);
    }
   void erase(iterator pos)
    {
        assert(pos!=end());
        node * cur = pos._node;
        node* prev = cur->_prev;
        node* next = cur->_next;

        prev->_next = next;
        next->_prev = prev;

        delete cur;
    }

    void pop_back()
    {
        erase(end());
    }
    
    void pop_front()
    {
        erase(begin());
    }
    
    //打印
    
    void print_list(const list<T> & lt)
    {
        iterator it = lt.begin();
        while(it != lt.end())
        {
            cout<<*it;
            ++it;
        }

        cout<<endl;
    }

}

3. Comparison between List and Vector

Both vector and list are very important sequence containers in STL. Due to the different underlying structures of the two containers, their characteristics and application scenarios are different.

vector list
underlying structure Dynamic sequence list, a continuous space Bidirectional circular linked list with head node
random access Supports random access, and the efficiency of accessing a certain element is O(1) Random access is not supported, and the efficiency of accessing an element is O(N)
Insertion and deletion Inserting and deleting elements at any position is inefficient and has a time complexity of O(N). Insertion may require expansion, opening up new space, copying elements, and releasing old space. Insertion and deletion at any position are efficient and do not require moving elements. Time complexity O(1)
Space utilization The bottom layer is continuous space, which is not easy to cause memory fragmentation, has high space utilization and high cache utilization. The underlying nodes are dynamically opened. Small nodes are prone to memory fragmentation, low space utilization, and low cache utilization.
Iterator native pointer Encapsulate native pointers (node ​​pointers)
Iterator invalid When inserting elements, all iterators must be reassigned, because inserting elements may cause expansion, causing the original iterators to become invalid. They may also become invalid when deleted. Need to reassign the iterator Inserting an element will not invalidate the iterator. Deleting an element will only invalidate the current iterator because that node has been deleted . Other iterators are not affected
scenes to be used Need efficient storage, support random access, do not care about insertion and deletion efficiency Lots of insert and delete operations, don't care about random access

Summarize

This article mainly reads the list content in the stl source code and simulates the implementation. The technology is limited, please correct me if there are any errors.

Guess you like

Origin blog.csdn.net/jolly0514/article/details/131964005