C++list Simulation Implementation

Posted by alexk1781 on Sat, 25 Dec 2021 05:04:24 +0100

The greatest advantage of hard work is that you can choose the life you want instead of being forced to let it be.

πŸŽ“ list introduction

1. List is a sequential container that can be inserted and deleted anywhere in the * * constant range O(1) * *, and the container can iterate back and forth, but the list container is not suitable for sorting.
2. The bottom layer of list is a circular two-way linked list structure. Each element in the two-way linked list is stored in independent nodes that are not related to each other. In the node, the pointer points to the previous element and the latter element.

πŸŽ“ Constructor

In C++98, the list constructor plus the copy constructor are the following four. Let's simulate and implement all the constructors here. Of course, we do not use the space configurator to create a space like the standard library. We use the new operator, but the principle is to create a new space.

🐱 ‍ πŸ’» non-parameter constructor

Here, we simulate the default constructor. The default value is T(), which means that the default value of this type is 0, and the char type is' \ 0 '. The user-defined type will call its constructor to initialize this anonymous object to get the default value.

list()		//Parameterless default constructor
{
	this->m_head = new node(T());
	this->m_head->m_next = this->m_head;	//Create a head node and connect the head and tail of the head node to form a circular structure
	this->m_head->m_prev = this->m_head;
}

πŸ– Parameterized constructor

list(size_t size, const T& val=T())	//n element constructors
{
	this->m_head = new node(T());	//Create a head node and connect the head and tail of the head node to form a circular structure
	this->m_head->m_next = this->m_head;
	this->m_head->m_prev = this->m_head;
	while (size--)
	{
		this->push_back(val);
	}
}

be careful:

  • 1. The constructor knows that it needs space for storing n data, so we use the new operator to open up the space, and then use push_ The back() function inserts the val value.
  • 2. The constructor also needs to implement two overloaded functions. If the overloaded function is not implemented, the compiler will call the template interval constructor we want to simulate below when we originally want to call n element constructors.
list(long size, const T& val = T())	//n element constructors
{
	assert(size > 0);
	this->m_head = new node(T());	//Create a head node and connect the head and tail of the head node to form a circular structure
	this->m_head->m_next = this->m_head;
	this->m_head->m_prev = this->m_head;
	while (size--)
	{
		this->push_back(val);
	}
}

list(int size, const T& val = T())	//n element constructors
{
	assert(size > 0);
	this->m_head = new node(T());	//Create a head node and connect the head and tail of the head node to form a circular structure
	this->m_head->m_next = this->m_head;
	this->m_head->m_prev = this->m_head;
	while (size--)
	{
		this->push_back(val);
	}
}

It can be seen that the difference between these two overloaded functions is that their parameter size types are different, but this is necessary. Otherwise, when we use the following code, the compiler will preferentially match the template interval constructor.

ZJ::list<int>L(2,1);	//The overloaded version is not implemented and will call the template interval constructor

In addition, an error is reported because the constructor 2 refers to the parameters first and last (*) (but the int type cannot be dereferenced).

πŸš€ Template interval constructor

Finally, the template interval constructor we have been talking about above. Because the iterator interval can be the iterator interval of other containers, and the type of iterator received by the function is uncertain, we need to design the constructor as a function template and insert the data of the iterator interval into the container one by one in the function body.

template <class InputIterator>
list(InputIterator first, InputIterator last)		//Interval structure
{
	this->m_head = new node(T());	//Create header node
	this->m_head->m_next = this->m_head;
	this->m_head->m_prev = this->m_head;
	while (first != last)
	{
		node* newnode = new node(*first);
		node* tail = this->m_head->m_prev;
		tail->m_next = newnode;
		newnode->m_prev = tail;
		newnode->m_next = this->m_head;
		this->m_head->m_prev = newnode;
		++first;
	}
}

be careful:

  • 1. The interval must be closed before open [obj.begin(), obj.end());

πŸŽ“ copy constructor

The idea of copy construction is easy for us to think of. It is to traverse the obj linked list of the object we want to copy, give the value of each obj element to each corresponding node of this object, and link each node.

list(const list<T>& obj)		//copy constructor 
{
	this->m_head = new node(T());	//Create a head node and connect the head and tail of the head node to form a circular structure
	this->m_head->m_next = this->m_head;
	this->m_head->m_prev = this->m_head;
	const_iterator it = obj.begin();
	while (it != obj.end())
	{
		node* newnode = new node(it.m_pnode->m_val);	//Create a new node and assign a value to it

		node* tail = this->m_head->m_prev;
		tail->m_next = newnode;
		newnode->m_prev = tail;
		newnode->m_next = this->m_head;
		this->m_head->m_prev = newnode;
		++it;
	}
}

πŸŽ“ Assignment operator overload

According to the history of our previous blog, here we use modern writing methods, call the copy constructor with obj to create a temporary object temp, and exchange the temp with the pointer of the object pointed to by our this pointer.

list<T>& operator=(const list<T>& obj)		//Assignment operator overload
{
	if (this != &obj)	//Prevent self assignment
	{
		list<T>temp(obj);
		this->swap(temp);
	}
	return *this;		//Return to yourself
}

be careful:

  • 1. When the above temp temporary object has an if statement, it will automatically call the destructor to release memory, so it will not cause memory leakage and other problems.

πŸŽ“ Destructor

Destructor here, I'm a little lazy. I reuse the pop we want to simulate and implement below_ The general idea of the front () function is to delete nodes one by one from beginning to end, and delete the head nodes to empty.

~list()		//Destructor
{
	iterator it = this->begin();

	while (it != this->end())	//Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase()
	{
		++it;
		this->pop_front();
	}

	delete this->m_head;
	this->m_head = nullptr;
}

πŸŽ“ iterator

In C + +, our iterators are as follows:

Next, I'll just simulate the begin() and end() iterators.

iterator begin()
{
	return iterator(this->m_head->m_next);
}

const_iterator begin()	const
{
	return const_iterator(this->m_head->m_next);
}

iterator end()
{
	return iterator(this->m_head);
}

const_iterator end()	const
{
	return const_iterator(this->m_head);
}

Above, we simulated and implemented our iterator, and there are ordinary iterators and const iterators. But we also need to understand the principle of iterators. In our previous blog, we said that not every iterator is a native pointer, and our list encapsulates a pointer variable to achieve the result of implementing iterator.

typedef list_iterator<T,T&,T*> iterator;		//Ordinary iterator
typedef list_iterator<T,const T&,const T*> const_iterator;	//Constant iterator

Some people may wonder that the above two iterators are not the same, but their names are different. Why not directly add const to the type, but specify the const attribute on the template parameter? We should know that const objects can only call constant functions, but can we support + +, -- when we use std::list? If it is a const object, it can only call constant functions. Once it becomes a const function, our const iterator cannot perform + +, –, '*' and so on. The effect we want to achieve is that we can perform + +, – and so on, but we can only not the value of its elements. So we encapsulate a template pointer here. We control the reading operation of the template through different parameters.

πŸ—Ό Iterator constructor

Is to pass a pointer and assign it to our member variable m_pnode.

list_iterator(node* pnode)
			:m_pnode(pnode)
		{}

🏝 Iterator relational operator overloading

Because we want to implement the iterator related traversal operations, such as the following code:
ZJ::list<int>::iterator it = L1.begin();
it != L1.end()

bool operator!=(const myself&obj) const		//Reload= number
{
	return this->m_pnode != obj.m_pnode;
}

bool operator==(const myself& obj) const	//Overload = = number
{
	return this->m_pnode == obj.m_pnode;
}

🧸 Iterator + + -- operator overloading

The following returned myself type is actually the type of our iterator class, but we typedef it. The code is as follows:

typedef list_iterator<T, Ref, Ptr>myself;

Here, the pre and post overloads should be separated. The post needs to use an int placeholder to occupy the position, otherwise an error will occur.

myself& operator++()		//Overload front++
{
	//Since our list is a two-way circular linked list, our + +, is to point to the next node
	this->m_pnode = this->m_pnode->m_next;
	return *this;
}

myself operator++(int)		//Heavy load post++
{
	const myself temp(*this);
	this->m_pnode = this->m_pnode->m_next;
	return temp;
}

myself& operator--()		//Overload front--
{
	//Because our list is a two-way circular linked list, our -- is to get the iterator of the previous node
	this->m_pnode = this->m_pnode->m_prev;
	return *this;
}

myself operator--(int)		//Heavy load post--
{
	const myself temp(*this);
	this->m_pnode = this->m_pnode->m_prev;
	return temp;
}

πŸ—½ Iterator * operator overload

Because we know that Ref is composed of two types, one is T &, and the other is const T &, when our object is const object, we can control it not to modify it.

Ref operator* ()		//Heavy load*
{
	return this->m_pnode->m_val;
}

πŸͺ Iterator - > operator overload

Why overload the - > operator? This is because if we store custom types in the list, our iterator cannot use - > to get its members.

Ptr operator->()		//Overload - >
{
	return &this->m_pnode->m_val;
}

🐱 ‍ πŸ‘“ summary

At this point, someone may ask why we don't write copy constructors and destructors of iterators?
A: This is because our iterator is used to traverse some or all of the elements in the container. Each iterator object represents the determined address in the container, and the decomposition and copy of these node elements are not under our control. The nodes should be under our list control, so the shallow copy provided by the compiler by default is enough.

πŸŽ“ Capacity correlation function

πŸ±β€πŸ‰empty()

  • Function: used to obtain whether the container is empty.
  • Return value: bool.
bool empty()	const		//Air judgment
{
	return this->begin() == this->end();	//This is the case when begin() and end() point to the header node at the same time
}

πŸŽƒsize()

  • Function: it is used to obtain the number of elements.
  • Return value: size_ T (unsigned integer).
  • Note: however, this interface is not commonly used in linked lists. If the interface is called frequently, it will cause performance degradation.
//Traverse the linked list to get the number of elements. Why use traverse the linked list?
//This is because we seldom know the number of elements when using the linked list, but if we call the interface frequently, the performance will be degraded
//At this point, we should add a variable size to record the number of elements in the list class
//If there are elements, the insertion will increase and the deletion will decrease
size_t size()	const	//Get the number of elements
{
	size_t size = 0;
	const_iterator it = this->begin();
	while(it!=this->end())
	{
		++it;
		++size;
	}
	return size;
}

πŸŽ“ Element access related functions

πŸŽ‹back()

  • Function: get the value of the last element.
  • Return value: a reference to the stored type.
T& back()
{
	assert(!this->empty());
	return this->m_head->m_prev->m_val;
}

const T& back()   const
{
	assert(!this->empty());
	return this->m_head->prev->m_val;
}

πŸŽ‘front()

  • Function: get the value of the first element.
  • Return value: a reference to the stored type.
T& front()
{
	assert(!this->empty());
	return  this->m_head->m_next->m_val;
}

const T& front() const
{
	assert(!this->empty());
	return this->m_head->m_next->m_val;
}

πŸŽ“ Modify correlation function

πŸ₯™push_back()

  • Function: insert an element at the end.
  • Return value: void (no return value).
void push_back(const T& val)	//Tail insertion
{
	node* tail = this->m_head->m_prev;	//Point to tail node
	node* newnode = new node(val);		//Create a new node
	newnode->m_next = tail->m_next;		//M of the new node_ Next points to the header node
	newnode->m_prev = tail;				//Add new node m_prev points to the tail node
	tail->m_next = newnode;				//And the m of the original tail node_ Next points to newnode
	this->m_head->m_prev = newnode;		//And the m of the head node_ Prev points to the new tail
}

πŸ±β€πŸš€push_front()

  • Function: insert an element in the header.
  • Return value: void (no return value).
void push_front(const T& val)	//Head insert
{
	node* newnode = new node(val);	//Create a new node
	node*  next = this->m_head->m_next;	//Point to the first node
	newnode->m_next = next;		//M of the new node to be created_ Next points to our original first element, next
	this->m_head->m_next = newnode;	//M of our head node_ Next points to the newly created node
	newnode->m_prev = this->m_head;	//M of the new node_ Prev points to the header node
	next->m_prev = newnode;	//The original m of the first node element_ Prev points to the newly created node
}

🚩pop_back()

  • Function: delete an element at the end.
  • Return value: void (no return value).
void pop_back()		//Tail deletion
{
	assert(!empty());	//Assert that if the list is already empty, it cannot be deleted
	node* tail = this->m_head->m_prev;	//First find the tail node we want to delete
	node* prev = tail->m_prev;		//Then find the previous node of the tail node to be deleted
	this->m_head->m_prev = prev;	//M of the head node_ Prev points to the new tail prev
	prev->m_next = this->m_head;	//Then m of prev_ Next points to the header node
	tail->m_next = tail->m_prev = nullptr;	//Add the member of the element to be deleted to nullptr
	tail->m_val = T();		//Set the value of the element to be deleted as the default value of the anonymous object
	delete tail;			//Delete tail node
}

πŸ±β€πŸpop_front()

  • Function: delete an element in the header.
  • Return value: void (no return value).
void pop_front()	//Header deletion
{
	assert(!empty());	//Assert that if the list is already empty, it cannot be deleted
	node* delnode = this->m_head->m_next;	//First find the first node we want to delete
	node* next = delnode->m_next;			//Find the location of the next node to delete the node
	this->m_head->m_next = next;			//Then m of the head node_ Next points to our new first node
	next->m_prev = this->m_head;			//And then we put the m of our new first node_ Prev points to the header node
	delnode->m_next = delnode->m_prev = nullptr;	//Add the member of the element to be deleted to nullptr
	delnode->m_val = T();		//Set the value of the element to be deleted as the default value of the anonymous object
	delete delnode;		//Delete the first node
}

🎐insert()

In c++98, our insert() function has the following three versions:

Let's simulate it:

Inserts an element at the specified location

  • Function: insert an element at the specified position.
  • Return value: iterator of the position of the inserted element.
//Insert the element to the specified location and return the iterator of the inserted element
iterator insert(iterator pos, const T& val)
{
	assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
	node* newnode = new node(val);		//Create a new node

	node* cur = pos.m_pnode;		//Record the pointer of the current node
	node* prev = cur->m_prev;		//Record the pointer of the previous node of the current node

	newnode->m_next = cur;
	newnode->m_prev = prev;
	prev->m_next = newnode;
	cur->m_prev = newnode;

	return iterator(newnode);		//Returns an iterator of an anonymous object constructed from the node of the currently inserted element
}

Insert n identical elements at the specified location

  • Function: insert an element at the specified position.
  • Return value: void (no return value).
void insert(iterator pos, size_t n, const T& val)	//Insert n Vals
{
	assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
	while (n--)
	{
		node* newnode = new node(val);

		node* cur = pos.m_pnode;
		node* prev = cur->m_prev;

		newnode->m_prev = prev;
		prev->m_next = newnode;
		newnode->m_next = cur;
		cur->m_prev = newnode;
	}
}

void insert(iterator pos, int n, const T& val)	//Insert n Vals
{
	assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
	assert(n > 0);
	while (n--)
	{
		node* newnode = new node(val);

		node* cur = pos.m_pnode;
		node* prev = cur->m_prev;

		newnode->m_prev = prev;
		prev->m_next = newnode;
		newnode->m_next = cur;
		cur->m_prev = newnode;
	}
}

void insert(iterator pos, long n, const T& val)	//Insert n Vals
{
	assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
	assert(n > 0);
	while (n--)
	{
		node* newnode = new node(val);

		node* cur = pos.m_pnode;
		node* prev = cur->m_prev;

		newnode->m_prev = prev;
		prev->m_next = newnode;
		newnode->m_next = cur;
		cur->m_prev = newnode;
	}
}

Note: the insert here is inserted at the specified position. Why do you want to implement three overloaded versions? This is the same type as the constructor problem above, which is related to the construction of template interval, so I won't go into details here.
Inserts an element in the interval at the specified position

  • Function: insert elements in the interval at the specified position.
  • Return value: void (no return value).
  • be careful:
    • 1. The section must be closed before opening;
template <class InputIterator>
void insert(iterator pos, InputIterator first, InputIterator last)	//Interval insertion
{
	assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
	while (first != last)
	{
		node* newnode = new node(*first);

		node* cur = pos.m_pnode;
		node* prev = cur->m_prev;

		newnode->m_prev = prev;
		prev->m_next = newnode;
		newnode->m_next = cur;
		cur->m_prev = newnode;
		++first;
	}
}

🎍erase()

There are two versions of erase() in C++98. One is to delete the specified position, and the other is to delete the elements in the interval. As shown in the figure below:

Delete specified location

  • Function: delete the element at the specified position.
  • Return value: deletes the iterator of the next node of the element at the specified location.
//Delete the element at the specified position and return the iterator of the next element, but note that:
//If the last element is deleted, the head node is returned, that is, the iterator at end()
iterator erase(iterator pos)			
{
	assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
	assert(pos != end());				//Assert that the element in the list is empty and delete the header node
	
	node* next = pos.m_pnode->m_next;
	node* prev = pos.m_pnode->m_prev;

	prev->m_next = next;
	next->m_prev = prev;

	pos.m_pnode->m_next = pos.m_pnode->m_prev = nullptr;
	pos.m_pnode->m_val = T();
	delete pos.m_pnode;

	return iterator(next);		
}

Delete elements within the interval

  • Function: delete elements in the interval.
  • Return value: the iterator that deletes the next node of the element in the interval. That is, return the last iterator.
  • be careful:
    • 1. The section must be closed before opening;
iterator erase(iterator first, iterator last)		//Interval deletion
{
	node* prev = first.m_pnode->m_prev;
	node* next = last.m_pnode;

	while (first != last)
	{
		node* cur = first.m_pnode;
		++first;
		cur->m_next = cur->m_prev = nullptr;
		cur->m_val = T();
		delete cur;
		cur = nullptr;
	}

	prev->m_next = next;
	next->m_prev = prev;

	return iterator(next);
}

πŸ’Έclear()

  • Function: used to clear the values of all elements in our list, but not to delete the list object.
  • Return value: void (no return value).
	void clear()		//Empty elements instead of deleting the whole linked list
	{
		iterator it = this->begin();	

		while (it != this->end())	
		//Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase()
		{
			++it;
			this->pop_front();
		}
	}

🧨swap()

  • Function: swap, as the name suggests, means exchange. Here, we use this swap as our member function to exchange two list linked lists.
  • Return value: void (no return value).
void swap(list<T>& obj)
{
	node* temp = this->m_head;
	this->m_head = obj.m_head;
	obj.m_head = temp;
}

🍜 The complete code is as follows

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using  namespace std;

namespace ZJ
{
	template<class T>	
	class list_node		//node
	{
	public:
		list_node(T val=T())		//Constructor
			:m_val(val)
			,m_prev(nullptr)
			,m_next(nullptr)
		{}
	public:
		T m_val;		//value
		list_node<T>* m_prev;	//Pointer to previous
		list_node<T>* m_next;	//Pointer to next

	};

	template<class T,class Ref,class Ptr>		//Encapsulate a node pointer and make it into an iterator class
	class list_iterator
	{
	public:
		typedef list_node<T> node;
		typedef list_iterator<T, Ref, Ptr>myself;
		list_iterator(node* pnode)
			:m_pnode(pnode)
		{}

		Ref operator* ()		//Heavy load*
		{
			return this->m_pnode->m_val;
		}

		Ptr operator->()		//Overload - >
		{
			return &this->m_pnode->m_val;
		}

		bool operator!=(const myself&obj) const		//Reload= number
		{
			return this->m_pnode != obj.m_pnode;
		}

		bool operator==(const myself& obj) const	//Overload = = number
		{
			return this->m_pnode == obj.m_pnode;
		}

		myself& operator++()		//Overload front++
		{
			this->m_pnode = this->m_pnode->m_next;
			return *this;
		}

		myself operator++(int)		//Heavy load post++
		{
			const myself temp(*this);
			this->m_pnode = this->m_pnode->m_next;
			return temp;
		}

		myself& operator--()		//Overload front--
		{
			this->m_pnode = this->m_pnode->m_prev;
			return *this;
		}

		myself operator--(int)		//Heavy load post--
		{
			const myself temp(*this);
			this->m_pnode = this->m_pnode->m_prev;
			return temp;
		}

	public:
		node* m_pnode;
	};

	template<class T>
	class list
	{
	public:
		typedef list_node<T> node;		
		typedef list_iterator<T,T&,T*> iterator;		//Ordinary iterator
		typedef list_iterator<T,const T&,const T*> const_iterator;	//Constant iterator

	public:
		list()		//Parameterless default constructor
		{
			this->m_head = new node(T());
			this->m_head->m_next = this->m_head;
			this->m_head->m_prev = this->m_head;
		}

		list(size_t size, const T& val=T())	//n element constructors
		{
			this->m_head = new node(T());
			this->m_head->m_next = this->m_head;
			this->m_head->m_prev = this->m_head;
			while (size--)
			{
				this->push_back(val);
			}
		}

		list(long size, const T& val = T())	//n element constructors
		{
			assert(size > 0);
			this->m_head = new node(T());
			this->m_head->m_next = this->m_head;
			this->m_head->m_prev = this->m_head;
			while (size--)
			{
				this->push_back(val);
			}
		}

		list(int size, const T& val = T())	//n element constructors
		{
			assert(size > 0);
			this->m_head = new node(T());
			this->m_head->m_next = this->m_head;
			this->m_head->m_prev = this->m_head;
			while (size--)
			{
				this->push_back(val);
			}
		}

		template <class InputIterator>
		list(InputIterator first, InputIterator last)		//Interval structure
		{
			this->m_head = new node(T());
			this->m_head->m_next = this->m_head;
			this->m_head->m_prev = this->m_head;
			while (first != last)
			{
				node* newnode = new node(*first);
				node* tail = this->m_head->m_prev;
				tail->m_next = newnode;
				newnode->m_prev = tail;
				newnode->m_next = this->m_head;
				this->m_head->m_prev = newnode;
				++first;
			}
		}
		
		list(const list<T>& obj)		//copy constructor 
		{
			this->m_head = new node(T());
			this->m_head->m_next = this->m_head;
			this->m_head->m_prev = this->m_head;
			const_iterator it = obj.begin();
			while (it != obj.end())
			{
				node* newnode = new node(it.m_pnode->m_val);

				node* tail = this->m_head->m_prev;
				tail->m_next = newnode;
				newnode->m_prev = tail;
				newnode->m_next = this->m_head;
				this->m_head->m_prev = newnode;
				++it;
			}
		}

		list<T>& operator=(const list<T>& obj)		//Assignment operator overload
		{
			if (this != &obj)
			{
				list<T>temp(obj);
				this->swap(temp);
			}
			return *this;
		}


		~list()		//Destructor
		{
			iterator it = this->begin();

			while (it != this->end())	//Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase()
			{
				++it;
				this->pop_front();
			}

			delete this->m_head;
			this->m_head = nullptr;
		}

		iterator begin()
		{
			return iterator(this->m_head->m_next);
		}

		const_iterator begin()	const
		{
			return const_iterator(this->m_head->m_next);
		}

		iterator end()
		{
			return iterator(this->m_head);
		}

		const_iterator end()	const
		{
			return const_iterator(this->m_head);
		}

		bool empty()	const		//Air judgment
		{
			return this->begin() == this->end();	//This is the case when begin() and end() point to the header node at the same time
		}

		//Traverse the linked list to get the number of elements. Why use traverse the linked list?
		//This is because we seldom know the number of elements when using the linked list, but if we call the interface frequently, the performance will be degraded
		//At this point, we should add a variable size to record the number of elements in the list class
		//If there are elements, the insertion will increase and the deletion will decrease
		size_t size()	const	//Get the number of elements
		{
			size_t size = 0;
			const_iterator it = this->begin();
			while(it!=this->end())
			{
				++it;
				++size;
			}
			return size;
		}

		T& back()
		{
			assert(!this->empty());
			return this->m_head->m_prev->m_val;
		}

		const T& back()   const
		{
			assert(!this->empty());
			return this->m_head->prev->m_val;
		}

		T& front()
		{
			assert(!this->empty());
			return  this->m_head->m_next->m_val;
		}

		const T& front() const
		{
			assert(!this->empty());
			return this->m_head->m_next->m_val;
		}

		void push_front(const T& val)	//Head insert
		{
			node* newnode = new node(val);
			node*  next = this->m_head->m_next;
			newnode->m_next = next;
			this->m_head->m_next = newnode;
			newnode->m_prev = this->m_head;
			next->m_prev = newnode;
		}

		void push_back(const T& val)	//Tail insertion
		{
			node* tail = this->m_head->m_prev;
			node* newnode = new node(val);
			newnode->m_next = tail->m_next;
			newnode->m_prev = tail;
			tail->m_next = newnode;
			this->m_head->m_prev = newnode;
		}


		//Insert the element to the specified location and return the iterator of the inserted element
		iterator insert(iterator pos, const T& val)
		{
			assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
			node* newnode = new node(val);		//Create a new node

			node* cur = pos.m_pnode;		//Record the pointer of the current node
			node* prev = cur->m_prev;		//Record the pointer of the previous node of the current node

			newnode->m_next = cur;
			newnode->m_prev = prev;
			prev->m_next = newnode;
			cur->m_prev = newnode;

			return iterator(newnode);		//Returns an iterator of an anonymous object constructed from the node of the currently inserted element
		}

		void insert(iterator pos, size_t n, const T& val)	//Insert n Vals
		{
			assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
			while (n--)
			{
				node* newnode = new node(val);

				node* cur = pos.m_pnode;
				node* prev = cur->m_prev;

				newnode->m_prev = prev;
				prev->m_next = newnode;
				newnode->m_next = cur;
				cur->m_prev = newnode;
			}
		}

		void insert(iterator pos, int n, const T& val)	//Insert n Vals
		{
			assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
			assert(n > 0);
			while (n--)
			{
				node* newnode = new node(val);

				node* cur = pos.m_pnode;
				node* prev = cur->m_prev;

				newnode->m_prev = prev;
				prev->m_next = newnode;
				newnode->m_next = cur;
				cur->m_prev = newnode;
			}
		}

		void insert(iterator pos, long n, const T& val)	//Insert n Vals
		{
			assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
			assert(n > 0);
			while (n--)
			{
				node* newnode = new node(val);

				node* cur = pos.m_pnode;
				node* prev = cur->m_prev;

				newnode->m_prev = prev;
				prev->m_next = newnode;
				newnode->m_next = cur;
				cur->m_prev = newnode;
			}
		}

		template <class InputIterator>
		void insert(iterator pos, InputIterator first, InputIterator last)	//Interval insertion
		{
			assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
			while (first != last)
			{
				node* newnode = new node(*first);

				node* cur = pos.m_pnode;
				node* prev = cur->m_prev;

				newnode->m_prev = prev;
				prev->m_next = newnode;
				newnode->m_next = cur;
				cur->m_prev = newnode;
				++first;
			}
		}

		void pop_front()	//Header deletion
		{
			assert(!empty());	//Assert that if the list is already empty, it cannot be deleted
			node* delnode = this->m_head->m_next;
			node* next = delnode->m_next;
			this->m_head->m_next = next;
			next->m_prev = this->m_head;
			delnode->m_next = delnode->m_prev = nullptr;
			delnode->m_val = T();
			delete delnode;
		}

		void pop_back()		//Tail deletion
		{
			assert(!empty());	//Assert that if the list is already empty, it cannot be deleted
			node* tail = this->m_head->m_prev;
			node* prev = tail->m_prev;
			this->m_head->m_prev = prev;
			prev->m_next = this->m_head;
			tail->m_next = tail->m_prev = nullptr;
			tail->m_val = T();
			delete tail;
		}


		//Delete the element at the specified position and return the iterator of the next element, but note that:
		//If the last element is deleted, the head node is returned, that is, the iterator at end()
		iterator erase(iterator pos)			
		{
			assert(pos.m_pnode != nullptr);		//Error asserting whether iterator is a null pointer
			assert(pos != end());				//Assert that the element in the list is empty and delete the header node
			
			node* next = pos.m_pnode->m_next;
			node* prev = pos.m_pnode->m_prev;

			prev->m_next = next;
			next->m_prev = prev;

			pos.m_pnode->m_next = pos.m_pnode->m_prev = nullptr;
			pos.m_pnode->m_val = T();
			delete pos.m_pnode;

			return iterator(next);		
		}

		iterator erase(iterator first, iterator last)		//Interval deletion
		{
			node* prev = first.m_pnode->m_prev;
			node* next = last.m_pnode;

			while (first != last)
			{
				node* cur = first.m_pnode;
				++first;
				cur->m_next = cur->m_prev = nullptr;
				cur->m_val = T();
				delete cur;
				cur = nullptr;
			}

			prev->m_next = next;
			next->m_prev = prev;

			return iterator(next);
		}

		void clear()		//Empty elements instead of deleting the whole linked list
		{
			iterator it = this->begin();	

			while (it != this->end())	//Reuse the header deletion we write, delete one by one, of course, you can also reuse the tail deletion pop_back() and erase()
			{
				++it;
				this->pop_front();
			}
		}

		void swap(list<T>& obj)
		{
			node* temp = this->m_head;
			this->m_head = obj.m_head;
			obj.m_head = temp;
		}
	private:
		node* m_head;		//Head pointer
	};
}


If there are any mistakes, please point them out. Thank you!!!
END...

Topics: C++ STL