yo! Here is a simple analog implementation of the STL::list class

Table of contents

foreword

important interface implementation

frame

default member function

iterator (important)

1 Introduction

2.list iterator class implementation 

3. Call implementation in the list class 

CRUD

postscript


foreword

        We know that the vector in stl corresponds to the sequence table in the data structure, the string class corresponds to the string, and the list class we will talk about today corresponds to the leading two-way linked list, not the single-linked list. The basic operations of the leading two-way linked list are in the data structure course I have already learned it, so the common interface that I will talk about today is not the focus, but the focus is on the implementation of the iterator of the list.

        We also know that the iterators of string and vector are native pointers. Can the iterators of lists be implemented if native pointers are used? The answer is no, because the data of the list is not a continuous storage space, and elements cannot be accessed like a pointer. However, in order to keep the use of all container iterators consistent, how can we implement the iterator of the list to pass ++ like a native pointer? , -- to control, here reflects the importance of packaging , let's take a look!

important interface implementation

  • frame

As can be seen from the code below, a node class template         is implemented to store nodes, and a linked list class template is used to store linked lists.

①The use of class templates is because the storage elements can be freely specified, instead of fixing the element type of each linked list through typedef as before;

②The node class template uses struct instead of class, because the default permission of struct is public, the linked list class template can freely access its member variables, and the default permission of class is private, of course, it is also possible to specify public permission with class,

In the node class template, the element is initialized through the constructor. The member of the linked list class template is a node pointer, and a node can be applied for as the head node in the constructor.

code:

template <class T>
struct ListNode
{
	//构造函数用来创节点
	ListNode(const T& x = T())
		:_data(x)
		, _prev(nullptr)
		, _next(nullptr)
	{

	}

	T _data;
	ListNode<T>* _prev;
	ListNode<T>* _next;
};

template <class T>
class List
{
	typedef ListNode<T> Lnode;  //作用:下面写ListNode<T>较麻烦,typedef一下,使用Lnode较方便

public:

    //...

private:
	Lnode* _head;
};
  • default member function

        Because member variables need to be initialized before all constructors (to apply for space for the head node and empty the left and right pointers), a function empty_init is encapsulated and called directly before each constructor,

        The implementation of all default member functions is exactly the same as that of string and vector. If you don’t understand it, you can refer to the previous article. It’s not the point and I won’t repeat it here.

code:

	//创建并初始化头节点,放于构造函数的最前面
	void empty_init()
	{
		_head = new Lnode();
		_head->_next = _head->_prev = _head;
	}

    //构造函数
	List()
	{
		empty_init();
	}

	//普通拷贝构造函数
	List(const List& L)   //注意:类外必须是List<类型名>,类里可以是List,但建议List<T>
	{
		empty_init();
		auto it = L.begin();
		while (it != L.end())
		{
			push_back(*it);
			it++;
		}
	}

	void swap(List<T>& L)
	{
		std::swap(_head, L._head);
	}

	//传迭代器区间构造函数
	template <class InputIterator>  
	List(InputIterator first, InputIterator last)
	{
		empty_init();

		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

    //拷贝构造函数
	//现代写法
	List(const List<T>& L)
	{
		empty_init();
		List<T> tmp(L.begin(), L.end());
		swap(tmp);
	}

    //赋值运算符重载
	//现代写法
	List<T>& operator=(const List<T>& L)
	{
		List<T> tmp(L.begin(), L.end());
		swap(tmp);
		return *this;
	}
	//更狠的现代写法
	List<T>& operator=(List<T> L)   //直接传一个拷贝过来,相当于上面的tmp,函数结束自动释放
	{
		swap(L);
		return *this;
	}

    //清除除了头节点之外所有的节点
   	void clear()
	{
		auto it = begin();
		while (it != end())
		{
			it = erase(it);
		}
	}

    //析构函数
	~List()
	{
		clear();
		_head->_next = _head->_prev = nullptr;
		delete _head;
		_head = nullptr;
	}
  • iterator (important)

1 Introduction

        Remember the iterator implementation of vector and string? typedef T* iterator; typedef const T* const_iterator; , it’s just that the original pointer is correct, and then the typedef can be used, because the pointer can access elements in a continuous address space ++ or --, but the node pointer in the linked list ++, -- is it possible to access elements? The answer is no, but in order to keep the iterators consistent, you should encounter linked list iterators using ++, -- to achieve the same effect, so I thought of operator overloading , overloading ++, --, * into the effect we want to achieve, and to achieve operator overloading, it must be encapsulated into a class.

        From the introduction, we can see the difference between the implementation of list and the container iterator learned before. The list iterator is implemented by encapsulating it into a class, but there are two kinds of iterators (not to mention the reverse iterator for now), one One kind of ordinary iterator, one kind of const iterator, the implementation of the two iterators should be roughly the same, with a small part different (for example, the dereference of the const iterator should return a variable that cannot be const type), then we should first write The implementation of a good ordinary iterator, and then copy and paste it into a const iterator and then modify it? leak! Big leak! After being exposed to the concept of templates, you should be able to think of using templates here.

2.list iterator class implementation 

1) frame 

        See the code below to implement the __list_iterator class template of the iterator of the list. T, Ref, and Ptr in the parameter list are data types, references of this type, and pointers of this type (for example, T is int, Ref is int&, Ptr It is int*) (why pointer parameters are required are explained in the operator->overload), the parameters filled in are different classes, here the iterator of the list needs two classes, a common iterator class, and a const The iterator class can be defined in the list class implementation.

        For the implementation of the list iterator class template, the member variable is a node pointer , and the constructor is passed in a node pointer to initialize a list iterator, and there is no need to provide a destructor.

code:

template <class T, class Ref, class Ptr>
struct __list_iterator
{
    //注意:这两个typedef只是因为ListNode<T>、__list_iterator<T, Ref, Ptr>很麻烦写,所以简化一下,也方便理解
	typedef ListNode<T> Lnode;
	typedef __list_iterator<T, Ref, Ptr> iterator;

    //构造函数
    __list_iterator(Lnode* p)
		:_node(p)
	{

	}

    //...

	Lnode* _node;   //链表中的迭代器表示节点的指针
};

2) Relational operator overloading

        Judging that two iterators are not equal means judging whether the node pointer as a member variable is the same pointer variable, which is easy to understand, plus const can be called regardless of ordinary list objects or const list objects.

code:

    bool operator==(const iterator& it) const
	{
		return _node == it._node;
	}
	bool operator!=(const iterator& it) const
	{
		return !(*this == it);
	}

 3) Operator ++, -- overloading

        For the list class, the ++ of the iterator is the iterator to access the next node, -- is the iterator to access the previous node, and it is not difficult to pay attention to the realization of the pre-position and post-position.

code:

    iterator& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	iterator operator++(int)
	{
		iterator tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	iterator& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	iterator operator--(int)
	{
		iterator tmp(_node);
		_node = _node->_prev;
		return tmp;
	}

4) Operator * overloading

        The dereferencing of the iterator in the list class is to access the data of this node, and return the reference type, so that the value can be manipulated. The Ref of the ordinary iterator is a common reference type, which can be read and written. The Ref of the const iterator is const Reference type, only readable but not writable.

        Note: There is no need to set this member function to a const type, just like the author wondered when he was a beginner, if Ref is const T&, shouldn’t it correspond to a const member function (Ref operator*() const)? In fact, the iterator of the list class uses a class template, and different parameters mean different classes, directly separating the two iterators. If it is an ordinary iterator, Ref will be passed in T&, and the reference will be returned when the dereference overload is called. , readable and writable, if it is a const iterator, Ref will pass in a const T&, and when the dereference overload is called, a const reference will be returned, which can only be read but not written.

code:

	Ref operator*()
	{
		return _node->_data;
	}
	

 5) Operator -> Overloading

        Under normal circumstances, the operator -> can dereference the structure pointer and then take its members. For the list iterator whose bottom layer is a node pointer, -> is to dereference the iterator and take its members, so if _data is a self Define the type, then -> can take its members, for example, _data is a custom type POS, which has two members, one x and one y, then it->x, it->y represent the POS taken by the iterator x, y, test as shown in the figure below,

        Carefully observe the implementation of -> operator overloading. It->x should be written as it->->x, because it-> returns a custom type pointer, and then -> x returns its members. Here, the compiler actually does processing, omitting a -> to improve readability.

        The Ptr here is the pointer variable of the data type, and its address is returned, which needs to be input from the class template parameter list just like the reference form.

code:

    Ptr operator->()
	{
		return &(_node->_data);
	}

 test:

3. Call implementation in the list class 

        After implementing the list class iterator class template of __list_iterator, different classes are defined by entering different template parameters in the list class. Here, ordinary iterator classes and const iterator classes are required. The following code, begin() is to return the first The iterator of the meta node, and end() returns the next position iterator of the last element node, which is the head node iterator.

 code:

    typedef __list_iterator<T, T&, T*> iterator;  //普通迭代器类
	typedef __list_iterator<T, const T&, const T*> const_iterator;   //const迭代器类

	iterator begin()
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	const_iterator end() const
	{
		return const_iterator(_head);
	}
  • CRUD

        After understanding the implementation of the iterator of the list, adding, deleting, modifying and querying the list must be easy. Here is a basic insertion and deletion operation. Combined with the previous knowledge in the data structure, the implementation of insert and erase should be written quickly. It is worth noting that insert returns the iterator of the newly inserted node, erase returns the iterator of the next position of the deleted node, and the tail insertion, tail deletion, head insertion, and head deletion can be directly reused.

code:

	iterator insert(iterator pos, const T& x)
	{
		Lnode* newNode = new Lnode(x);
		pos._node->_prev->_next = newNode;
		newNode->_prev = pos._node->_prev;
		newNode->_next = pos._node;
		pos._node->_prev = newNode;

		return iterator(newNode);  //返回插入位置的迭代器
	}
    
    //尾插
	void push_back(const T& x)
	{
		insert(end(), x);
	}

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

	iterator erase(iterator pos)
	{
		assert(pos != end());
		Lnode* tmp = pos._node->_next;
		pos._node->_prev->_next = pos._node->_next;
		pos._node->_next->_prev = pos._node->_prev;
		delete pos._node;
		return iterator(tmp);
	}

    //尾删
	void pop_back()
	{
		erase(--end());
	}

    //头删
	void pop_front()
	{
		erase(begin());
	}

postscript

        In the implementation of the list class, the default member functions, operator overloading, and addition, deletion, and modification are no longer the focus. The focus is on the implementation of the iterator, because it is different from the implementation of the iterator in string and vector, and it is not that simple. The above summary It's very clear, if you don't understand, you can private me or write in the comment area, come on, goodbye!


Guess you like

Origin blog.csdn.net/phangx/article/details/132114384