[C++ Advanced Road] Basic use and simulation implementation of list

Preliminary Understanding

 Before the formal explanation, let’s briefly understand the list

①Definition

template < class T, class Alloc = allocator<T> > class list;

There are also two template parameters here, the first is the instantiated type , and the second is the spatial configurator .

②Underlying principle

The list in the library is implemented using a headed two-way circular linked list .

  • forward_list is a singly linked list

Insert image description here

  • The head node with the sentinel bit is to reserve space for the end() iterator .

③Classification of iterators

Insert image description here

1. Input iterator
2. Output iterator
3. One-way iterator - forward_list, unorder_xxx
4. Two-way iterator - list/map/set
5. Random access iterator - vector/string/deque

The relationship between:
Insert image description here

  • From left to right are contained relationships.

Insert image description here

1. Basic use

1. Insert node elements

	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);

	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
	
	list<int>::iterator it = lt.begin();
	for (size_t i = 0; i < 5; i++)
	{
    
    
		it++;
	}
	//在第五个位置的前面插入10
	//第五个位置——是下标为5,也就是第六个元素。
	lt.insert(it,10);


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

Insert image description here

  • Note: Because list does not support random access, there is no +, +=, [] operator overloading , but ++, - - are supported .

  • The iterator here does not expire after insertion , because the insertion of the node is a single application and a single release . There is no expansion phenomenon.

2. Delete node elements

void test_list()
{
    
    
	list<int> lt;
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(90);
	lt.push_back(6);
	lt.push_back(7);


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

	//删除所有的偶数结点
	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
    
    
		if (*it % 2 == 0)
		{
    
    
			it = lt.erase(it);
		}
		else
		{
    
    
			it++;
		}
	}

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

}
  • After erase is released, the iterator is definitely invalid because the space it points to is invalid. Therefore, the library uses the return value to update the iterator.

3. Merge two ordered linked lists

  • Prerequisites must be in order .
void test_list()
{
    
    
	list<int> lt;
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	lt.push_back(7);


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

	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	lt1.push_back(9);

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

	//将lt1合并到lt上
	lt.merge(lt1);
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

}

Insert image description here

4. Transfer a part of one linked list to another linked list

interface:


void splice (iterator position, list& x);
//将x链表转移到另一条链表的pos位置之前
void splice (iterator position, list& x, iterator i);
//将x链表的某个位置的结点移到另一条链表的pos之前
void splice (iterator position, list& x, iterator first, iterator last);
//将x链表的[first,last)的区间移到另一条链表的pos位置之前。

Note: iterators/iterator ranges cannot overlap!

list<int> lt;
	lt.push_back(2);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);
	lt.push_back(7);


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

	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(3);
	lt1.push_back(5);
	lt1.push_back(7);
	lt1.push_back(9);

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

	//将lt1转移到到lt的前面
	lt.splice(lt.begin(),lt1);
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	//将lt的后半部分转移到lt1上
	list<int>::iterator it = lt.begin();
	for (size_t i = 0; i < 5; i++)
	{
    
    
		it++;
	}
	lt1.splice(lt1.begin(), lt ,it, lt.end());

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

	//将lt1的第一个结点移到lt的最前面
	lt.splice(lt.begin(), lt1, lt1.begin());

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

5. Sort the linked list and remove duplicates

  • The premise of unique deduplication is ordering .
	list<int> lt;
	lt.push_back(2);
	lt.push_back(1);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(2);
	lt.push_back(6);
	lt.push_back(4);
	
	lt.sort();
	for (auto e : lt)
	{
    
    
		cout << e << " ";
	}
	cout << endl;

	lt.unique();

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

6.Comparison of vector and list sorting

void test_list2()
{
    
    
	list<int> lt;
	vector<int> v;
	//设置随机数起点
	srand((unsigned)time(nullptr));
	size_t datas_num = 1000000;
	for (size_t i = 0; i < datas_num; i++)
	{
    
    
		int n = rand();
		lt.push_back(n);
		v.push_back(n);
	}
	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();
	cout <<"vector:" << (end1 - begin1) << endl;

	int begin2 = clock();
	lt.sort();
	int end2 = clock();
	cout <<"list:" << (end2 - begin2) << endl;
}

Insert image description here

  • Result: The sorting of the list is a bit frustrating, but if the amount of data is relatively small, you can use the sorting provided by the list. If the amount of data is relatively large, then copy the data of the list to the vector, sort it, and then copy it back.
  • Note: Testing under debug may yield different results, because vector is implemented using recursive quick sorting. If debug information is added under debug, the overhead of stack frames will be larger, thus affecting time efficiency. The underlying principle of list is merging. Although it is also recursive, less debugging information may be added, so it will be faster. The realease version is the hairstyle version that has been optimized and has better performance.

2. Simulation implementation

①Key points

  • 1. In order to do this 不与库里面的list冲突, we need to 命名空间implement the classes we implement封装
  • 2. The framework we implement here is 带头双向循环链表implemented according to the data structure.
  • 3. For understanding, the following interface is 分开讲解yes, I will give it at the end 源码.

②Basic framework

template<class  T>
struct list_node
{
    
    
	list_node(const T& val = T())
		:_val(val)
		,_next(nullptr)
		,_prev(nullptr)
	{
    
    }

	T _val;
	list_node* _next;
	list_node* _prev;
};

template<class T>
class list
{
    
    
	typedef list_node<T> Node;
public:
	
private:
	Node* _head;
	size_t _size;
};

③Iterator

  • This is the most critical part of the list!

Let's first talk about how to implement it. The iterator must be a pointer to a node, but it can complete ++ operations and dereference operations. So what should we do? The answer is actually very simple—— 用类进行封装,通过运算符重载实现相应的功能.

Simple iterator framework:

template<class T>
struct list_iterator
{
    
    
	typedef list_iterator<T> iterator;	
	Node* _node;
};

Constructor

list_iterator(Node* val = nullptr)
	:_node(val)
{
    
    }
list_iterator(const iterator& lt)
	:_node(lt._node)
{
    
    }
  • We can use the default generated destructor, but never write destructor to release the space pointed by the iterator, because the space pointed by the iterator belongs to the list. Should be managed by list.

The remaining operations are to overload ++, --,* in the class.

++

//前置
iterator& operator ++() 
{
    
    
	_node = _node->_next;
	return *this;
}
//后置
iterator operator ++(int)
{
    
    
	list_iterator tmp(_node);
	_node = _node->_next;
	return tmp;
}

- -

//前置
iterator& operator --()
{
    
    
	_node = _node->_prev;
	return *this;
}
//后置
iterator operator --(int)
{
    
    
	list_iterator tmp(_node);
	_node = _node->_prev;
	return tmp;
}

*

T& operator *()
{
    
    
	return _node->_val;
}

In this way, the basic function of the iterator is realized, but how to implement the const iterator?

Is that so?

typedef const list_iterator<T> const_iterator;

In this way, the member variables of the iterator cannot be modified, that is, they cannot be ++ or - -, which does not meet the requirements. What we want is that the returned value cannot be modified .

Generally, we will make another copy and change the copied iterator, but we can also set the second template parameter so that it can be defined like this outside.

template<class Tclass Ref> struct list_iterator;
//里面的运算符重载——*
Ref operator *()
{
    
    
	return _node->_val;
}

//list里面可以这样定义

typedef list_iterator<T,const T&> const_iterator;
typedef list_iterator<T,T&> iterator;
  • This problem is solved.

->

There is a second question, if the iterator points to a custom type, such as:

struct A
{
    
    
	A(int x = 0)
		:_a1(x)
	{
    
    }
	int _a1;
};

If you want to access its members, dereferencing them directly will not work.

We can do this:

iterator it = lt.begin();
(*it)._a1;

access its elements.

We can also overload -> for access:

T* operator ->()
{
    
    
	return &(_node->_val);
}
iterator it = lt.begin();
it->_a1;

This actually omits an arrow . This omitted arrow is actually for the sake of appearance, and the compiler will automatically add it during processing.

What if it is a const object? In fact, the processing method is the same, plus a template parameter.

Updated template, and iterator:

template<class T,class Ref,class Ptr> struct list_iterator;
Ptr operator ->()
{
    
    
	return &(_node->_val);
}
typedef list_iterator<T,const T&,const T*> const_iterator;
typedef list_iterator<T,T&,T*> iterator;

The full version of the iterator is:

template<class T,class Ref,class Ptr>
//第一个参数:为了确定Node的类型;
//第二个是为了实现*解引用的const与非const返回值
//第三个是为实现Node成员为结构体对象的指针的访问对象。
struct list_iterator
{
    
    
	typedef list_node<T> Node;
	typedef list_iterator<T, Ref, Ptr> self;
	typedef list_iterator<T, T&, T*> iterator;


	list_iterator(Node* val = nullptr)
		:_node(val)
	{
    
    }
	list_iterator(const iterator& lt)
		:_node(lt._node)
	{
    
    }


	//重载++
	self& operator ++() 
	{
    
    
		_node = _node->_next;
		return *this;
	}
	//后置++,为了区别需要重载一下,这里的参数实际上没啥用
	self operator ++(int)
	{
    
    
		list_iterator tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	self& operator --()
	{
    
    
		_node = _node->_prev;
		return *this;
	}
	self operator --(int)
	{
    
    
		list_iterator tmp(_node);
		_node = _node->_prev;
		return tmp;
	}
	bool operator != (const self& it) const
	{
    
    
		return _node != it._node;
	}
	bool operator == (const self& it) const//const和非const都能用
	{
    
    
		return _node == it._node;
	}		
	Ptr operator ->()
	{
    
    
		return &(_node->_val);
	}
	Ref operator *()
	{
    
    
		return _node->_val;
	}
	Node* _node;
};

iterator in list

iterator begin() 
{
    
    
	return _head->_next; 
	//隐式类型转换
}
const_iterator begin()const
{
    
    
	return _head->_next;
	
}
iterator end()
{
    
    
	return _head;
}
const_iterator end()const
{
    
    
	return _head;
}

④insert

//在pos之前插入
void insert(iterator pos, const T& val)
{
    
    
	Node* newnode = new Node(val);
	Node* cur = pos._node;
	Node* prev = cur->_prev;

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

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


	_size++;
}

⑤erase

Node* 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;

	_size--;
	return next;
}

⑥push_back

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

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

	//_head->_prev = newnode;
	//tail->_next = newnode;
	insert(end(), val);
}

⑦push_front

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

⑧pop_front

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

⑨pop_back()

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

⑩Constructor

//默认构造函数
list()
{
    
    
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

//拷贝构造
list(const list<T>& lt)
{
    
    
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;

	for (auto e : lt)
	{
    
    
		push_back(e);
	}
}

⑪clear

void clear()
{
    
    
	iterator it = begin();
	while (it != end())
	{
    
    
		it = erase(it);
	}
}

⑫Destructor

~list()
{
    
    
	clear();
	delete _head;
}

⑬Assignment overloading

void swap(list &tmp)
{
    
    
	std::swap(_head, tmp._head);
	std::swap(_size, tmp._size);
}
list& operator = (list tmp)
{
    
    
	swap(tmp);
	return *this;
}

The remaining interfaces are too simple and will not be listed here. If you are interested, please see the source code.

Source code

namespace my_list
{
    
    
	template<class  T>
	struct list_node
	{
    
    
		list_node(const T& val = T())
			:_val(val)
			,_next(nullptr)
			,_prev(nullptr)
		{
    
    }

		T _val;
		list_node* _next;
		list_node* _prev;
	};



	template<class T,class Ref,class Ptr>
	struct list_iterator
	{
    
    
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> self;
		typedef list_iterator<T, T&, T*> iterator;
		//这个看着确实有点奇怪,写这个为了实现const_iterator转换为
		//非const_itertaor,从而达到类型转换的效果。


		list_iterator(Node* val = nullptr)
			:_node(val)
		{
    
    }
		list_iterator(const iterator& lt)
			:_node(lt._node)
		{
    
    }


		//重载++
		self& operator ++() 
		{
    
    
			_node = _node->_next;
			return *this;
		}
		//后置++,为了区别需要重载一下,这里的参数实际上没啥用
		self operator ++(int)
		{
    
    
			list_iterator tmp(_node);
			_node = _node->_next;
			return tmp;
		}
		self& operator --()
		{
    
    
			_node = _node->_prev;
			return *this;
		}
		self operator --(int)
		{
    
    
			list_iterator tmp(_node);
			_node = _node->_prev;
			return tmp;
		}
		bool operator != (const self& it) const
		{
    
    
			return _node != it._node;
		}
		bool operator == (const self& it) const//const和非const都能用
		{
    
    
			return _node == it._node;
		}		
		Ptr operator ->()
		{
    
    
			return &(_node->_val);
		}
		//加上迭代器为it则调用此函数在底层上实际被翻译为
		//(it.operator->())->结构体成员,忽略了一个->,看起来更加的美观。


		Ref operator *()
		{
    
    
			return _node->_val;
		}
		Node* _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;

		list()
		{
    
    
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		list(const list<T>& lt)
		{
    
    
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto e : lt)
			{
    
    
				push_back(e);
			}
		}
		~list()
		{
    
    
			clear();
			delete _head;
		}
		void clear()
		{
    
    
			iterator it = begin();
			while (it != end())
			{
    
    
				it = erase(it);
			}
			//cout << _size << endl;
		}
		iterator begin() 
		{
    
    
			//return _head->_next;
			//隐式类型转换为list_itertor
			
			return _head->_next; 
		}
		const_iterator begin()const
		{
    
    
			//return _head->_next;
			//隐式类型转换为list_itertor

			return _head->_next;
		}
		iterator end()
		{
    
    
			return _head;
			//返回的是_head的拷贝,因此返回对象具有常属性
			
			//隐式类型转换为list_itertor
			//return itrator(_head->next); 
			//匿名对象
		}
		const_iterator end()const
		{
    
    
			return _head;
			//返回的是_head的拷贝,因此返回对象具有常属性

			//隐式类型转换为list_itertor
			//return itrator(_head->next); 
			//匿名对象
		}
		void push_back(const T& val)
		{
    
    
			//Node* tail = _head->_prev;
			//Node* newnode = new Node(val);

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

			//_head->_prev = newnode;
			//tail->_next = newnode;
			insert(end(), val);
		}
		//在pos之前插入
		void insert(iterator pos, const T& val)
		{
    
    
			Node* newnode = new Node(val);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

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

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


			_size++;
		}
		Node* 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;

			_size--;
			return next;
		}
		void pop_back()
		{
    
    
			erase(--end());
		}
		void pop_front()
		{
    
    
			erase(begin());
		}
		void push_front(const T& val)
		{
    
    
			insert(begin(), val);
		}
		size_t size()
		{
    
    
			return _size;
		}
		bool empty()
		{
    
    
			return _size == 0;
		}
		void swap(list &tmp)
		{
    
    
			std::swap(_head, tmp._head);
			std::swap(_size, tmp._size);
		}
		list& operator = (list tmp)
		{
    
    
			swap(tmp);
			return *this;
		}

	private:
		Node* _head;
		size_t _size;
	};
}

Summarize

 That’s it for today’s sharing. If you think the article is good,Give it a like and encourage it.! Us 下篇文章再见!

Guess you like

Origin blog.csdn.net/Shun_Hua/article/details/131783823