[C++] In-depth analysis and simulation implementation of STL list

Table of contents

Preface

1. Use of list

 1. Constructor

2. Iterator

3. Add, delete, check and modify

4. Use of other functions

2. Simulation implementation of list

 1. Creation of nodes

 2、push_back 和 push_front

 3. Ordinary iterator

 4. const iterator

 5. Add, delete, check and modify (insert, erase, pop_back, pop_front)

 6. Constructor and destructor

  6.1. Default structure

  6.2. Construct n val objects

  6.3. Copy structure

  6.4. Iterator interval construction

  6.5. Assignment operator overloading

  6.6. Destructor

3. List simulation implementation source code

4. The iterator of list is invalid

5. Comparison between list and vector


Preface

  1. A 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 independent of each other. The node points to its previous element and next element through pointers.
  3. list is very similar to forward_list: the main difference is that forward_list is a singly linked list and can only iterate forward, making it simpler and more efficient.
  4. Compared with other sequential containers (array, vector, deque), list usually performs more efficiently when inserting and removing elements at any position.
  5. Compared with other sequential containers, the biggest drawback of list and forward_list is that they do not support random access at any location.

1. Use of list

 1. Constructor

Constructor Interface Description
list (size_type n, const value_type& val = value_type()) The constructed list contains n elements with the value val
list() Construct an empty list
list (const list& x) copy constructor
list (InputIterator first, InputIterator last) Construct a list with elements in the range [first, last)
int main()
{
	// 默认构造
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	// 拷贝构造
	list<int> lt2(lt);
	// 构造 n 个节点
	list<int> lt3(5, 1);
	// 迭代器区间构造
	list<int> lt4(lt.begin(), lt.end());

	return 0;
}

2. Iterator

function declaration Interface Description
begin + end Returns an iterator to the first element + an iterator to the next position of the last element
rbegin + rend Returns the reverse_iterator of the first element, which is the end position, and returns the reverse_iterator of the next position of the last element, which is the begin position.
int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt(a, a + 9);
	auto it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	return 0;
}

Iterators are generally used for traversing and searching; 

The use of reverse iterator is similar, except that the functions called are replaced by rbegin and rend.

Note: The reverse iterator also uses ++ for iteration. But the iterator range is still [rbegin, rend);

3. Add, delete, check and modify

function declaration Interface Description
push_front Insert an element with value val before the first element of 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 in two lists
clear Clear valid elements in the list
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,9 };
	list<int> lt(v.begin(), v.end());
	for (auto e : lt) cout << e << " ";
	cout << endl;

	lt.push_front(10);
	lt.push_back(20);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	lt.pop_front();
	lt.pop_back();
	for (auto e : lt) cout << e << " ";
	cout << endl;

	auto pos = find(lt.begin(), lt.end(), 5);
	lt.insert(pos, 50);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	pos = find(lt.begin(), lt.end(), 8);
	lt.erase(pos);
	for (auto e : lt) cout << e << " ";
	cout << endl;

	return 0;
}

4. Use of other functions

function declaration Interface Description
empty Check whether the list is empty, return true, otherwise return false
size Returns the number of valid nodes in the list
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

2. Simulation implementation of list

 1. Creation of nodes

template<class T>
struct list_node//节点
{
	list_node<T>* _next;
	list_node<T>* _prev;
	T _data;
    // 构造函数
	list_node(const T& x = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};

   Since the data stored by nodes may be of any type, we need to define the nodes as template classes. Here we need to write a default constructor for the default value so that we can directly initialize it when we create a new node in the main class. At the same time, we will set the two pointers to null and write the data into the data field.

 2、push_back 和 push_front

class list 
{
public:
	typedef list_node<T> node;
	
private:
	node* _head;
}
//尾插
void push_back(const T& x) const
{
	node* new_node = new node(x);
	node* tail = _head->_prev;
	//链接节点之间的关系
	tail->_next = new_node;
	new_node->_prev = tail;
	new_node->_next = _head;
	_head->_prev = new_node;
}
//头插
void push_front(const T& x)
{
	node* head = _head->_next;
	node* new_node = new node(x);

	_head->_next = new_node;
	new_node->_prev = _head;
	new_node->_next = head;
	head->_prev = new_node;
}

 The head insertion and tail insertion simulated here are also very simple, because it is the same as the two-way circular linked list we used in the data structure before. We only need to find the head or tail, and then link the relationship between the four nodes.

 3. Ordinary iterator

Note: The iterator of list is a custom type, not a native pointer node*.

Iterators are custom types, among which *, ++, etc. are all implemented through operator overloading.

So we need overloaded symbols: *, ->, prefix ++, postfix ++, prefix --, postfix --, !=, == ;

template<class T>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator<T> self;
	node* _node;

	//构造函数
	__list_iterator(node* n)
		:_node(n)
	{}
	//重载*运算符
	T& operator*()
	{
		return _node->_val;
	}
	T* operator->()
	{
		return &_node->_data;
	}
	//重载前置++运算符
	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;
	}
	//重载!=运算符
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	//重载==运算符
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

 Here I implement a simple forward iterator, using a template parameter T to represent the type.

 After the ordinary iterator is encapsulated, we need to implement its begin() and end() methods in the list class. Since the name of an iterator is generally iterator, and for a range for, it can only be converted into an iterator for traversal through iterator. So here we typedef it as iterator.

template<class T>
class list//链表
{
	typedef list_node<T> node;
public:
	typedef __list_iterator<T> iterator;

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

	iterator end()
	{
		return iterator(_head);
	}
private:
	node* _head;
};

 4. const iterator

  The difference between const iterators and ordinary iterators is that the content pointed to by const iterators cannot be modified, but its pointer can be modified.

template<class T>
class list//链表
{
	typedef list_node<T> node;
public:
	typedef __list_const_iterator<T> const_iterator;

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

	const_iterator end()
	{
		return const_iterator(_head);
	}
private:
	node* _head;
};

  Our best approach is to add two template parameters in the class template of __list_iterator, and then change the second parameter to the type of T& and const T& in the two typedefs of the list class. Essentially, this is to let the compiler The const iterator class is automatically instantiated based on the difference in the Ref passed in, and we also need to overload a -> operator because the list may store a custom type. If this custom type has multiple members For variables, we need to use -> to dereference and access member variables. We still need to distinguish between ordinary iterators and const iterators, so we added another template parameter Ptr. The specific solutions are as follows:

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 _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	...
};

Then, finally use it in the linked list class as follows:

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()
	{
		return iterator(_head->_next);//匿名对象的返回
	}
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator end() const
	{
		return const_iterator(_head);
	}
private:
	node* _head;
};

 5. Add, delete, check and modify (insert, erase, pop_back, pop_front)

// 指定位置插入
void insert(iterator pos, const T& x)
{
	node* cur = pos._node;
	node* prev = cur->_prev;
	node* new_node = new node(x);

	prev->_next = new_node;
	new_node->_prev = prev;
	new_node->_next = cur;
	cur->_prev = new_node;
}
// 指定位置删除
iterator erase(iterator pos)
{
	assert(pos != end());

	node* prev = pos._node->_prev;
	node* next = pos._node->_next;

	prev->_next = next;
	next->_prev = prev;
	delete pos._node;

	return iterator(next);
}
// 尾删
void pop_back()
{
	erase(--end());
}
// 头删
void pop_front()
{
	erase(begin());
}

 6. Constructor and destructor

  6.1. Default structure

  Since null will be initialized frequently later, it is encapsulated here to facilitate subsequent calls.

void empty_init()//空初始化
{
	_head = new node;
	_head->_next = _head;
	_head->_prev = _head;
}
list()
{
	empty_init();
}

  6.2. Construct n val objects

//用n个val构造对象
list(int n, const T& val = T())
{
	empty_init();
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

  6.3. Copy structure

//拷贝构造传统写法
list(const list<T>& lt)
{
	empty_init();
	for (auto e : lt)
	{
		push_back(e);
	}
}
//拷贝构造现代写法
list(const list<T>& lt)
{
	empty_init();
	list<T> tmp(lt.begin(), lt.end());
	swap(tmp);
}

  6.4. Iterator interval construction

template <class Iterator>
list(Iterator first, Iterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

  6.5. Assignment operator overloading

//赋值运算符重载
list<T>& operator=(list<T> lt)//注意这里不能用引用
{
	swap(lt);
	return *this;
}

  6.6. Destructor

//要全部清理掉
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}
//不释放头结点
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
		//这样也可以
		//erase(it++);
	}
}

3. List simulation implementation source code

template<class T>
struct list_node//节点
{
	list_node<T>* _next;
	list_node<T>* _prev;
	T _data;

	list_node(const T& x = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{}
};
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 _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	//前置++
	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;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};
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()
	{
		return iterator(_head->_next);//匿名对象的返回
	}
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator end() const
	{
		return const_iterator(_head);
	}
	void empty_init()//空初始化
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	list()
	{
		empty_init();
	}
	//迭代器区间构造
	template <class Iterator>
	list(Iterator first, Iterator last)
	{
		empty_init();
		while (first != last)
		{
			push_back(*first);//push_back使用的前提是要有哨兵位的头结点
			++first;
		}
	}
	// 交换函数
	void swap(list<T>& tmp)
	{
		std::swap(_head, tmp._head);
	}
	//现代拷贝构造
	list(const list<T>& lt)
	{
		list<T> tmp(lt.begin(), lt.end());
		swap(tmp);
	}
	//现代赋值写法
	list<T>& operator=(list<T> lt)
	{
		swap(lt);
		return *this;
	}
	~list()//要全部清理掉
	{
		clear();
		delete _head;
		_head = nullptr;
	}
	void clear()//不释放头结点
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);
			//这样也可以
			//erase(it++);
		}
	}
	void insert(iterator pos, const T& x)
	{
		node* cur = pos._node;
		node* prev = cur->_prev;
		node* new_node = new node(x);

		prev->_next = new_node;
		new_node->_prev = prev;
		new_node->_next = cur;
		cur->_prev = new_node;
	}
	iterator erase(iterator pos)
	{
		assert(pos != end());

		node* prev = pos._node->_prev;
		node* next = pos._node->_next;

		prev->_next = next;
		next->_prev = prev;
		delete pos._node;

		return iterator(next);
	}
	//尾插
	void push_back(const T& x) const
	{
		//node* new_node = new node(x);
		//node* tail = _head->_prev;
		链接节点之间的关系
		//tail->_next = new_node;
		//new_node->_prev = tail;
		//new_node->_next = _head;
		//_head->_prev = new_node;
		insert(end(), x);
	}
	//头插
	void push_front(const T& x)
	{
		//node* head = _head->_next;
		//node* new_node = new node(x);

		//_head->_next = new_node;
		//new_node->_prev = _head;
		//new_node->_next = head;
		//head->_prev = new_node;
		insert(begin(), x);
	}
	//尾删
	void pop_back()
	{
		erase(--end());
	}
	//头删
	void pop_front()
	{
		erase(begin());
	}
private:
	node* _head;
};

4. The iterator of list is invalid

  When we use erase to delete, the iterator pointing to the deleted position becomes invalid, and using it again will cause the program to crash.

  Therefore, if you want to delete multiple times, you need to use the return value of erase to update the iterator after use, so that no errors will occur.

int main()
{
	vector<int> v = { 1, 2,3,5,4,6 };
	list<int> lt(v.begin(), v.end());
	list<int>::iterator pos = find(lt.begin(), lt.end(), 3);
	for (int i = 0; i < 3; i++)
	{
		pos = lt.erase(pos);   //利用erase的返回值更新迭代器
	}
	for (auto e : lt) cout << e << " ";
	cout << endl;
	return 0;
}

5. Comparison between list and vector

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 at any position is inefficient and requires moving elements. The time complexity is O(N). Capacity expansion may be required during insertion. Capacity expansion: open up new space, copy elements, and release old space, resulting in lower efficiency. Insertion and deletion at any position is highly efficient, no need to move elements, and the time complexity is 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 Original ecological pointer Encapsulate the original ecological pointer (node ​​pointer)
Iterator invalid When inserting elements, all iterators must be reassigned, because inserting elements may cause re-expansion, causing the original iterators to become invalid. When deleting, the current iterator needs to be reassigned, otherwise it will become invalid. Inserting an element will not cause the iterator to become invalid. When deleting an element, it will only cause the current iterator to become invalid. Other iterators will not be 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


If there are any shortcomings in this article, you are welcome to comment below and I will correct it as soon as possible.

 

Guess you like

Origin blog.csdn.net/m0_63198468/article/details/132572068