[C++]——Introduction and simulation implementation of list

1 Introduction

We have learned string and vector before, today we will learn another container in C++ - list, the bottom layer of list is the leading two-way circular linked list.

2. Introduction to list

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 in both directions.
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. In the node, pointers point to the previous element and the next element.
3. list is very similar to forward_list: the main difference is that forward_list is a single linked list, which can only be iterated forward, which makes 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, it must be iterated from a known position (such as the head or tail) to this position, iterating over this position requires linear time overhead; the list also requires some additional space to hold the associated information for each node (this may be an important issue for large lists that store elements of smaller types the elements of)

3. Common interface of list

3.1 The constructor of list

function name Function 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)
在这里插入代码片void Test1()
{
    
    
	list<int> lt1;
	list<int> lt2(10, 1);
	list<int> lt3(lt2);
}

insert image description here

3.2 The use of iterators

Here, the iterator can be temporarily understood as a pointer, which points to a certain node in the list.

function name Function Description
begin+end Returns an iterator to the first element + returns an iterator to the next position of the last element
rbegin+rend Return the reverse_iterator of the first element, which is the end position, and return the reverse_iterator of the next position of the last element, which is the begin position
void Test2()
{
    
    
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_back(5);

	list<int>::iterator it = lt1.begin();
	
	while (it != lt1.end())
	{
    
    
		cout << *it << " ";
		it++;
	}
	cout << endl;

	it = lt1.begin();
	while (it != lt1.end())
	{
    
    
		(*it)++;
		cout << *it << " ";
		it++;
	}
	cout << endl;

	for (auto e : lt1)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

insert image description here

3.3 List space management

function name Function Description
empty Check whether the list is empty, return true, otherwise return false
size Returns the number of valid nodes in the list
void Test3()
{
    
    
	list<int> lt1;
	list<int> lt2;
	lt2.push_back(1);
	lt2.push_back(2);
	lt2.push_back(3);
	lt2.push_back(4);
	lt2.push_back(5);

	cout << lt1.empty() << endl;
	cout << lt2.size() << endl;

}

insert image description here

3.4 List node access

function name Function Description
front Returns a reference to the value in the first node of the list
back Returns a reference to the value in the last node of the list
void Test4()
{
    
    
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_back(5);

	cout << lt1.front() << endl;
	cout << lt1.back() << endl;

	int& a = lt1.front();
	int& b = lt1.back();
	a++;
	b++;

	for (auto e : lt1)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}

insert image description here

3.5 Add, delete, check and modify list

function name Function 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
void Test5()
{
    
    
	list<int> lt1;
	lt1.push_front(1);
	lt1.push_front(2);
	lt1.push_front(3);
	lt1.push_front(4);
	lt1.push_front(5);
	for (auto e : lt1)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	lt1.pop_front();
	lt1.pop_front();
	lt1.pop_front();
	for (auto e : lt1)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	list<int> lt2;
	lt2.push_back(1);
	lt2.push_back(2);
	lt2.push_back(3);
	lt2.push_back(4);
	lt2.push_back(5);
	for (auto e : lt2)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	lt2.pop_back();
	lt2.pop_back();
	lt2.pop_back();
	for (auto e : lt2)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	list<int> lt3;
	lt3.push_back(1);
	lt3.push_back(2);
	lt3.push_back(3);
	lt3.push_back(4);
	lt3.push_back(5);
	list<int>::iterator pos = find(lt3.begin(), lt3.end(), 3);
	lt3.insert(pos, 10);//迭代器不会失效
	lt3.insert(pos, 20);
	for (auto e : lt3)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	//lt3.erase(pos);//迭代器会失效
	pos = lt3.erase(pos);
	pos = lt3.erase(pos);
	for (auto e : lt3)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	lt1.swap(lt3);
	for (auto e : lt1)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;
	for (auto e : lt3)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

	lt2.clear();
	for (auto e : lt2)
	{
    
    
		cout << e << ' ';
	}
	cout << endl;

}

insert image description here

4. The problem of list iterator invalidation

As mentioned earlier, 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.

Iterator invalidation:

void Test6()
{
    
    
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_back(5);
	list<int>::iterator it = lt1.begin();
	while (it != lt1.end())
	{
    
    
		//错误——迭代器失效
		//erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
		lt1.erase(it);
		++it;
	}
}

insert image description here

After modification:

void Test7()
{
    
    

	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	lt1.push_back(5);
	list<int>::iterator it = lt1.begin();
	while (it != lt1.end())
	{
    
    
		it = lt1.erase(it);//正确
	}
}

5. List simulation implementation

#pragma once

#include <assert.h>

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

		list_node(const T& x = T())
			:_data(x)
			, _next(nullptr)
			, _prev(nullptr)
		{
    
    }
	};

	// typedef __list_iterator<T, T&, T*>             iterator;
	// typedef __list_iterator<T, const T&, const T*> const_iterator;

	// 像指针一样的对象
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
    
    
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;

		typedef bidirectional_iterator_tag iterator_category;
		typedef T value_type;
		typedef Ptr pointer;
		typedef Ref reference;
		typedef ptrdiff_t difference_type;


		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{
    
    }

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

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

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

		//T* operator->() 
		Ptr operator->()
		{
    
    
			return &(operator*());
		}

		// ++it
		iterator& operator++()
		{
    
    
			_node = _node->_next;
			return *this;
		}

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

		// --it
		iterator& operator--()
		{
    
    
			_node = _node->_prev;
			return *this;
		}

		// it--
		iterator operator--(int)
		{
    
    
			iterator tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
	};

	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;

		const_iterator begin() const
		{
    
    
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
    
    
			return const_iterator(_head);
		}

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

		iterator end()
		{
    
    
			return iterator(_head);
		}

		list()
		{
    
    
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		void push_back(const T& x)
		{
    
    
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(x);

			 _head          tail  newnode
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;

			insert(end(), x);
		}

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

		iterator insert(iterator pos, const T& x)
		{
    
    
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return iterator(newnode);
		}

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

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

		iterator 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;

			return iterator(next);
		}

	private:
		Node* _head;
	};

	void test_list1()
	{
    
    
		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())
		{
    
    
			cout << *it << " ";
			++it;
		}
		cout << endl;

		it = lt.begin();
		while (it != lt.end())
		{
    
    
			*it *= 2;
			++it;
		}
		cout << endl;

		for (auto e : lt)
		{
    
    
			cout << e << " ";
		}
		cout << endl;
	}

	struct Pos
	{
    
    
		int _a1;
		int _a2;

		Pos(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{
    
    }
	};

	void test_list2()
	{
    
    
		int x = 10;
		int* p1 = &x;

		cout << *p1 << endl;

		Pos aa;
		Pos* p2 = &aa;
		p2->_a1;
		p2->_a2;

		list<Pos> lt;
		lt.push_back(Pos(10, 20));
		lt.push_back(Pos(10, 21));

		list<Pos>::iterator it = lt.begin();
		while (it != lt.end())
		{
    
    
			//cout << (*it)._a1 << ":" << (*it)._a2 << endl;
			cout << it->_a1 << ":" << it->_a2 << endl;

			++it;
		}
		cout << endl;
	}

	void Func(const list<int>& l)
	{
    
    
		list<int>::const_iterator it = l.begin();
		while (it != l.end())
		{
    
    
			//*it = 10;

			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

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

		Func(lt);
	}

	void test_list4()
	{
    
    
		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())
		{
    
    
			cout << *it << " ";
			++it;
		}
		cout << endl;

		it = lt.begin();
		while (it != lt.end())
		{
    
    
			*it *= 2;
			++it;
		}
		cout << endl;

		for (auto e : lt)
		{
    
    
			cout << e << " ";
		}
		cout << endl;

		lt.push_front(10);
		lt.push_front(20);
		lt.push_front(30);
		lt.push_front(40);

		lt.pop_back();
		lt.pop_back();

		for (auto e : lt)
		{
    
    
			cout << e << " ";
		}
		cout << endl;

		auto pos = find(lt.begin(), lt.end(), 4);
		if (pos != lt.end())
		{
    
    
			lt.insert(pos, 40);
			//lt.insert(pos, 30);
			*pos *= 100;
		}

		for (auto e : lt)
		{
    
    
			cout << e << " ";
		}
		cout << endl;
	}
}

6. The 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

7. Ending

We have learned about the list container of C++ here. Several containers of C++ are very important knowledge points. They are very simple and convenient to use, but the most important thing is that we need to understand the underlying principles and master their respective advantages, disadvantages and differences. point, so that it can be used flexibly in future study and work.
Finally, I would like to thank you for your patient reading and support. Friends who think this article is well written can follow and support it three times. If you have any questions or there are mistakes in this article, you can private message me or leave a message in the comment area Discussion, thanks again everyone.

Guess you like

Origin blog.csdn.net/qq_43188955/article/details/131067279