Introduction to the use of C + + entry List class + Advanced [simulation implementation]


Introduction to common interface functions

Constructor

ConstructorInterface description
list()Construct an empty list
list (size_type n, const value_type& val = value_type())The constructed list contains n elements with value val
list (const list& x)copy constructor
list (InputIterator first, InputIterator last)Construct a list with elements in the [first, last) interval
list<int> ls1();
//Construct an empty list object
list<int> ls2(10, 2);
//Create 10 objects with element value 2

list<int> ls3(ls2);
//copy constructor 
list<int> ls4(l3.begin(), l3.end());
//Create an ls4 object using an iterator interval

iterator

Function declarationInterface description
begin + endReturns the iterator of the first element + returns the iterator of the next position of the last element
rbegin + rendReturn the reverse_iterator of the first element, that is, the end position, and the next position of the last element


1. begin and end are forward iterators. Perform + + operations on the iterator, and the iterator moves backward
2. rbegin(end) and rend(begin) are reverse iterators. Perform + + operation on the iterator, and the iterator moves forward

Three traversal modes

list<int> ls; //Construct an empty list object
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);
	//Iterator traversal
	list<int>::iterator it = ls.begin();
	while (it != ls.end()) 
	{
		cout << *it << " ";
		it++;
	}
	//Range for traversal
	for(auto e : ls)
	{
		cout << e << " ";
	}
	
	//Use the array interval to construct an object and traverse the object
	int arr[] = { 8,4,5,11,23,66,89,45,8,65,65 };
	list<int>ls1(arr, arr + sizeof(arr) / sizeof(arr[0]));
	for (auto e : ls1) 
	{
		cout << e << " ";
	}
	cout << endl;

Common operation functions

Readers can use it in combination with documents

Function declarationInterface description
frontReturns a reference to the value in the first node of the list
backReturns a reference to the value in the last node of the list
push_frontInsert an element with value val before the first element of the list
pop_frontDelete the first element in the list
push_backInsert an element with value val at the end of the list
pop_backDelete the last element in the list
insertInsert the element with value val in the position of list
eraseDelete the element at list position
swapSwap elements in two list s
clearClear valid elements in the list
assignAssign new content to container
spliceMove the elements from x to the container and insert them into the location.
removeDelete the value val in the container. If there is no value, it will not be processed
uniqueOnly the continuously repeated values in a section will be deleted, provided that the container is ordered

front and back

front function prototype

reference front();  
//Returns a reference to the first element of the list
const_reference front() const;
//The reference type of the first element of the list returned by the function is const_reference, and the object attribute is not allowed to be modified

back function prototype

reference back();
//Returns a reference to the last element of the list
const_reference back() const;
//The function returns a const_reference, and the object properties are not allowed to be modified

be careful:
Member types reference and const_reference are reference types to container elements

Test:

void func() 
{
	list<int> ls;
	ls.push_back(11);
	ls.push_back(4);
	//Returns the first element of the list and the last element of the list, subtracting their values
	cout << ls.front() - ls.back() << endl;//7
}

push_front and pop_front

push_front

Function prototype:
void push_front (const value_type& val);
stay list A new element is inserted before the current first element

pop_front

void pop_front();
delete list The first element in the container, effectively reducing its size by 1

Test:

void func() 
{
	list<int> ls;
	for (int i = 0; i < 5; i++) 
	{
		ls.push_front(i);	//Insert five values into the list container
	}
	
	for (auto e : ls) 
	{
		cout << e << " ";  //4 3 2 1 0
	}
	
	cout << endl;
	for (int i = 0; i < 5; i++)
	{
		ls.pop_front(); //Header delete list data 
	}
	cout << ls.size() << endl;  //0
}

push_back and pop_back

push_back

void push_back (const value_type& val);
At the end of the list container, a new element is added after the current last element
 This effectively increases the size of the container 1.  

pop_back

void pop_back();
Deleting the last element in the list container effectively reduces the container size by 1.  

Test:

void func() 
{
	list<int> ls;
	for (int i = 0; i < 5; i++) 
	{
		ls.push_back(i);//Insert five values into the list container
	}
	
	for (auto e : ls) 
	{
		cout << e << " ";  //0 1 2 3 4
	}
	
	cout <<"\n After inserting values size: "<< ls.size() << endl;
	for (int i = 0; i < 5; i++)
	{
		cout << "When deleting a value size: " << ls.size() << endl;
		ls.pop_front(); //Tail delete list data 
	}
	
}

Operation results:

insert and erase

Three versions of insert

//Extends the container by inserting a new element before the element at the specified location.  
iterator insert (iterator position, 
			const value_type& val);
			
//Extend the container by inserting n new elements with value val before the element at the specified location.
void insert (iterator position, size_type n,
				 const value_type& val);
				 
//Extends the container by inserting the value of an iterator interval before the element at the specified location.			 
template <class InputIterator>
    void insert (iterator position, 
    InputIterator first, InputIterator last);

Test:

void func() 
{
	list<int> ls;
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);

	list<int> ::iterator it = ls.begin();
	it++;	//The second element of the list when it + + is finished
	ls.insert(it, 30); //Insert 30 before it position
	for (auto e : ls) { cout << e << " ";  } //1 30 2 3 4 
	cout << endl;

	it++; //it now points to the element with a value of 3
	//Insert a continuous value before the pos position
	int arr[] = { 10,20,30 };
	ls.insert(it,arr,arr + sizeof(arr) / sizeof(arr[0]));
	for (auto e : ls) { cout << e << " "; }   //1 30 2 10 20 30 3 4 
	cout << endl;

	it--;
	ls.insert(it, 5,1);
	for (auto e : ls) { cout << e << " "; }   //1 30 2 10 20 1 1 1 1 1 30 3 4 
	cout << endl;

}

effect:

erase

//Removes a single element (position) of the specified pos from the list container 
iterator erase (iterator position);
//Removes an element ([first, last]) that specifies an interval from the list container. 
iterator erase (iterator first, iterator last);

Return value:
Returns an iterator that points to the next element of the element deleted by the function call. If the operation deletes the last element in the sequence,

Test:

void func1() 
{
	list<int> ls;
	ls.push_back(1);
	ls.push_back(2);
	ls.push_back(3);
	ls.push_back(4);
	for (auto e : ls) { cout << e << " "; }
	cout << endl;
	cout << "Before removal capacity: " << ls.size() << endl;
	//Print after removing values
	list<int> ::iterator it = ls.begin();
	ls.erase(it); //Delete first value
	for (auto e : ls) { cout << e << " "; }
	cout << "\n After removal capacity: " << ls.size() << endl;

	it = ls.begin();
	//Remove remaining values
	ls.erase(it,ls.end()); //Delete the value of a section
	for (auto e : ls) { cout << e << " "; }
	cout << "\n After removal capacity: " << ls.size() << endl;
}

effect:

swap

There is also a swap function inside the list class. The swap function is often used to exchange elements in two list containers
Test:

void func2() 
{
	list<int>l1;  //1 2 
	l1.push_back(1);
	l1.push_back(2);

	list<int>l2;	//2 3 4
	l2.push_back(2);
	l2.push_back(3);
	l2.push_back(4);

	l1.swap(l2);
	for (auto e : l1) { cout << e << " "; } //2 3 4
	cout << endl;
	for (auto e : l2) { cout << e << " "; }// 1 2
}

From the running results, it can be seen that the swap function is indeed used to exchange elements in the container

Introduction to splice and remove functions

void func2() 
{
	list<int>L1, L2;
	L1.push_back(1);
	L1.push_back(2);
	L1.push_back(2);
	L1.push_back(3);
	L1.push_back(4);

	L2.push_back(10);

	list<int>::iterator it = L2.begin();
	L2.splice(it, L1);

	for (auto e : L2)
	{
		cout << e << " ";  //At this time, the elements in the L2 container are 1 2 2 3 4 10 
	}
	cout << endl;
	cout << " L1.size(): " << L1.size() << endl;//0
	cout << " L2.size(): " << L2.size() << endl;//6
	L2.remove(10);
	for (auto e : L2)
	{
		cout << e << " ";  //At this time, the elements in the L2 container are 1 2 2 3 4 10 
	}
	cout << endl;

	L2.remove(10);
	//Remove will only delete the existing elements in the container. If there is no such value, remove will not process
	for (auto e : L2)
	{
		cout << e << " ";  //At this time, the elements in the L2 container are 1 2 2 3 4 10 
	}
}

unique

Function: de duplication
Note: unique will only process all elements except the first element in consecutive equal element groups.

Test:

void func2() 
{
	int arr[] = {1,2,2,2,2,5,2,3,2,5};
	list<int> ls(arr, arr+ sizeof(arr) / sizeof(arr[0]) - 1);
	cout << "Container creation" << endl;
	for (auto e : ls)
	{
		cout << e << " ";//1 2 2 2 2 5 2 3 2
	}
	cout << "\n Start weight removal" << endl;
	ls.unique();
	for (auto e : ls) 
	{
		cout << e << " ";//1 2 5 2 3 2
	}
	cout << "\n--------------------------" << endl;
	//Phenomenon: the remaining duplicate values in the container are not removed
	//Reason 1: unique will only process all elements except the first element in consecutive equal element groups.
	//Reason 2: the array is out of order
	int arr1[] = { 1,2,2,2,2,5,2,3,2,5 };
	list<int> ls1(arr1, arr1 + sizeof(arr1) / sizeof(arr1[0]) - 1);
	cout << "\n Container creation" << endl;
	for (auto e : ls1)
	{
		cout << e << " ";//1 2 2 2 2 5 2 3 2
	}
	cout << "\n Start weight removal" << endl;
	ls1.sort(); //Sort before de duplication
	ls1.unique();
	for (auto e : ls1)
	{
		cout << e << " ";//1 2 3 5
	}
}

Discussion on iterator failure

The function of the program is to remove even values from the container

void func3() 
{
	int arr[] = { 1,2,3,4,5,6 };
	list<int> ls(arr,arr + sizeof(arr) / sizeof(arr[0]) - 1);
	list<int>::iterator it = ls.begin();
	while (it != ls.end()) 
	{
		if (*it % 2 == 0) 
		{
			ls.erase(it);
			//erase will delete the node it points to,
			//When this node is deleted, the space it points to will be deleted
			//It is recycled by the operating system, and the programmer has no permission to use it,
			//When dereferencing again, there will be illegal access and the program will crash
		}
		it++;
	}

	for (auto e : ls) 
	{
		cout << e << " "; // The expected results are: 1 3 5
	}
	
}

When the program runs, it crashes directly

Solution:

void func3() 
{
	int arr[] = { 1,2,3,4,5,6 };
	list<int> ls(arr,arr + sizeof(arr) / sizeof(arr[0]) - 1);
	list<int>::iterator it = ls.begin();
	while (it != ls.end()) 
	{
		if (*it % 2 == 0) 
		{
			it = ls.erase(it);
			//After deleting the value, the iterator of the next element of this element is returned,
			//	It is updated, even if the old value fails, it will not be affected
		}
		else 
		{
			it++;
		}
	}
	
}

list Simulation Implementation

Class declaration

template<class T>
	struct __list_node
	{
		__list_node<T>* prev;
		__list_node<T>* next;
		T data;
		//All default parameters are provided. Anonymous objects are used here,
		//T() will call the constructor of type T
		__list_node(const T& val = T())
			:prev(nullptr)
			,next(nullptr)
			,data(val)
		{ }
	};

Define the _list_node structure for creating nodes

Iterator class

The behavior used to simulate the native pointer is different from the native pointer, but the behavior of the simulation implementation is similar

template<class T,class Ref, class Ptr>
	struct __list_iterator 
	{
		typedef __list_iterator<T, Ref, Ptr> Self;
		typedef __list_node<T> Node;
		Node* _node;
		__list_iterator(Node* node)
			:_node(node) 
		{ }

		Ref operator*()
		{
			return _node->data;
		}
		Ptr operator->()
		{
			return &_node->data;
		}
		
		bool operator!=(const Self &it)
		{
			return it._node != _node;
		}
		Self operator++(int)
		{
			
			Self tmp((*this)._node);
			++(*this);
			return tmp;
		}
		Self& operator++()
		{
			_node = _node->next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->prev;
			return *this;
		}
		Self operator--(int)
		{
			
			Self tmp((*this)._node);
			--(*this);
			return tmp;
		}

	};

Template parameters

//Three template parameters are provided here
// T: Represents the type of data
//Ref: indicates the type of reference
//Ptr: indicates the pointer type
template<class T,class Ref, class Ptr>

Iterator framework

//The three template parameters have been explained earlier
template<class T,class Ref, class Ptr>
	struct __list_iterator 
	{
		typedef __list_iterator<T, Ref, Ptr> Self;
		//Aliasing iterator types with typedef is simpler,
		//Therefore, this method is recommended. In addition, it can also be used for different template parameter types
		//Generate different types of iterator objects, and readers can experience the wonderful use of them
		typedef __list_node<T> Node;
		
		Node* _node;//The _node here is used to record the position of the iterator at this time
		
		//Call the iterator's constructor with a pointer of type Node as a parameter
		//To initialize the member _node of the iterator's object
		__list_iterator(Node* node)
			:_node(node) //Initialize list mode
		{ }
		
	};

Here, I want readers to understand the framework of list first, and then look at the interface functions implemented later, so as to facilitate the overall learning

Operator * and operator - >

//Overload * operator
Ref operator*() //The type of Ref is related to the template parameter type and is referenced or often referenced
{
	return _node->data;
	//As mentioned above, _node is used to record the position of iterators,
	//Dereference to the pointer returns the value of the position
}
//Overload - > operator
Ptr operator->()  //The type of Ptr is related to the template parameter type, T * or const T*
{
	return &_node->data;
	//Returning a pointer to an object allows you to access its member variables through - > object
}

Front + + / - rear + + / –

//Post++
Self operator++(int) //Self: This is an iterator object
{
	//Call the iterator constructor to create a local iterator object with a pointer,
	//tmp will be destroyed when it is out of scope,
	Self tmp((*this)._node);
	++(*this); //Multiplex operator + + ()
	return tmp; //Post + + returns the value before + + and creates a temporary object in the middle for return
}

Self& operator++() // Self: This is an iterator object
{
	_node = _node->next;//Update the position of the iterator and go back
	return *this;  //Pre + + returns the value after + +
}

Self& operator--()//Self: This is an iterator object
{
	_node = _node->prev;//Update the position of the iterator and go ahead
	return *this; //Value before -- after -- after -- return
}

Self operator--(int)//Self: This is an iterator object
{
	//Create a local iterator object using a pointer
	Self tmp((*this)._node);
	--(*this); //Multiplex operator -- ()
	return tmp; //Post -- return -- previous value
}

list class

Class declaration

template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		list() 
		{
			_head = new Node();
			_head->prev = _head;
			_head->next = _head;
		}
		//copy constructor 
		list(const list<T>& ls) 
		{
			_head = new Node();
			_head->next = _head;
			_head->prev = _head;

			for (auto& e : ls) 
			{
				push_back(e);
			}
		}
		//operator=
		list<T>& operator=(list<T> ls)
		{
			
			swap(_head, ls._head);
			return *this;
		}
		//Returns the normal iterator
		iterator begin() 
		{
			return iterator(_head->next);
		}
		iterator end() 
		{
			return iterator(_head);
		}
		//const iterator
		const_iterator begin()const
		{
			return const_iterator(_head->next);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}
		//Insert node at specified pos position
		void Insert(iterator pos, const T& val)
		{	
			Node* cur = pos._node;
			Node* newNode = new Node(val);
			Node* prev = cur->prev;

			prev->next = newNode;
			newNode->prev = prev;
			newNode->next = cur;
			cur->prev = newNode;
		}
		//Tail insertion
		void push_back(const T& val)
		{
			Insert(end(),val);
		}
		//Head insert
		void push_Front(const T& val)
		{
			Insert(begin(), val);
		}

		iterator erase(iterator pos)
		{
			assert(pos._node != _head);
			Node* cur = pos._node;
			Node* prev = cur->prev;
			Node* next = cur->next;
			prev->next = next;
			next->prev = prev;
			delete cur;

			return next;
		}
		bool empty() { return _head->next == _head->prev; }
		//Tail deletion
		void Pop_back()
		{
			assert(!empty());
			erase(--end());
		}
		//Header deletion
		void Pop_Front() 
		{
			assert(!empty());
			erase(begin());
		}
		//Empty container
		void clear() 
		{
			iterator it = begin();
			while (it != end()) 
			{
				erase(it++);
			}
		}
		//Deconstruction
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		
	private:
		Node* _head;
	};

frame

template<class T>
class list
{
	//Recommended writing, concise
	typedef __list_node<T> Node;
public:
	//Normal iterator type: readable and writable
	typedef __list_iterator<T, T&, T*> iterator;
	//const iterator type: read only
	typedef __list_iterator<T, const T&, 
				const T*> const_iterator;
 
private:
	Node* _head;  //List is a two-way leading circular linked list structure,
				//So here we need to define a_ head node
};

Similarly, in order to facilitate later learning, we should first understand the framework and sort out a good idea for the overall understanding

begin and end

//Returns the normal iterator
iterator begin() 
{
	//Creates an iterator object using a pointer and returns a readable and writable iterator object
	//This iterator object records the location of the first node
	return iterator(_head->next);
}

iterator end() 
{
	//Creates an iterator object using a pointer and returns a readable and writable iterator object
	//This iterator object records the next location of the last node
	return iterator(_head);
}

//const iterator 
const_iterator begin()const
{
	//Creates an iterator object using a pointer and returns a read-only iterator object
	//This iterator object records the location of the first node
	return const_iterator(_head->next);
	
}

const_iterator end()const
{
	//Creates an iterator object using a pointer and returns a readable iterator object
	//This iterator object records the next location of the last node
	return const_iterator(_head);
}

The above methods will produce temporary objects, which can be understood by readers

Add, delete, check and modify

//Insert node at specified pos position
void Insert(iterator pos, const T& val)
{	
	Node* cur = pos._node;
	Node* newNode = new Node(val);
	Node* prev = cur->prev;

	prev->next = newNode;
	newNode->prev = prev;
	newNode->next = cur;
	cur->prev = newNode;
}
//Specify pos location to delete node
iterator erase(iterator pos)
{
	assert(pos._node != _head);
	Node* cur = pos._node;
	Node* prev = cur->prev;
	Node* next = cur->next;
	prev->next = next;
	next->prev = prev;
	delete cur;

	return next;
}

Head to tail insertion

//Tail insertion
void push_back(const T& val)
{
	Insert(end(),val);
}
//Head insert
void push_Front(const T& val)
{
	Insert(begin(), val);
}

Head deletion and tail deletion

bool empty() { return _head->next == _head->prev; }
//Tail deletion
void Pop_back()
{
	assert(!empty());
	erase(--end());
	//end() returns yes_ head->prev,
	// --end() multiplex operator -- ()
	//It can also be written in this form: erase (_head - > prev);  
}
//Header deletion
void Pop_Front() 
{
	assert(!empty());
	erase(begin()); 
	//begin() returns the iterator of the first node
	//erase deletes the first node
}

Empty container

void clear() //Use iterators to traverse and delete nodes
{
	iterator it = begin();
	while (it != end()) 
	{
		erase(it++); 
	}
}

Construction and copy construction

Constructor

list()  //Constructor creates a header node
{
	_head = new Node();
	_head->prev = _head;
	_head->next = _head;
}

copy constructor

//copy constructor 
list(const list<T>& ls) 
{
	//Create header node
	_head = new Node();
	_head->next = _head;
	_head->prev = _head;
	//While traversing ls objects, take out the nodes and insert them at the end_ Behind the head
	for (auto& e : ls) 
	{
		push_back(e);
	}
}

operator=

//operator=
list<T>& operator=(list<T> ls)
{
	//Traditional writing
	if (this != &ls) 
	{
		clear(); //Empty this object, leaving only the header node
		for (auto e : ls) 
		{
			push_back(e);
			 //Insert the node tail of ls object after this object
		}
	}
}

operator=

list<T>& operator=(list<T> ls)
{
	//Modern writing
	swap(_head, ls._head);
	//Swap the of this object_ Of head and ls objects_ head
	//_ head can connect nodes in ls objects
	return *this;
}

Destructor

~list()
{
	//Complete resource cleanup and release all nodes of the list object
	clear();  
	delete _head; 
	_head = nullptr;//Prevent wild pointer
}

Summarize the difference between vector and list:

vector is a dynamically growing array, which can support random access and some sorting, such as binary search, heap algorithm and so on
Disadvantages: the efficiency of header insertion and specified pos position insertion is low, because there is not enough space to move the data, and the capacity needs to be increased, which consumes performance

List is a two-way leading circular linked list. It is a linked list that inserts and deletes data at any position. The time efficiency is O(1)
Disadvantages: random access is not supported and traversal speed is slow

Summary: vector and list are two complementary containers

Tags: C++ Back-end

Posted on Sun, 21 Nov 2021 23:46:21 -0500 by ptbsG_Man