Linked list design of STL design, block and component analysis, iterator design ideas

content

Preface.

1. Think about the design of the iterator of the list

2. Analysis and understanding of important function prototypes, with these first write out to see the effect

Analysis of the necessary functions of the iterator framework

Analysis of the necessary functions of the List framework

3. Build a general framework

4. Block analysis of function details (similar to vector)

range construction

swap: very pure and simple exchange, exchange your members to me, I will become you, you will become me

Copy construction (because of swap, I initialize myself to nullptr to avoid wild pointers, and then exchange the tmp object constructed by multiplexing the range construction code to construct itself)

assignment overloading

Move construction (snatch the heap resources of the dying object for construction)

After writing the insert() + erase() interface, it is equivalent to writing six interfaces

insert()

push_back() reuse insert function

push_front() reuse insert function

erase();

pop_back() reuse erase

pop_front() reuse erase

5. Overall code + test

6. Summary


Preface.

  • Welcome to Xiaojie's handwritten STL chapter. Xiaojie will try his best to lead you to analyze the design of components such as function interface iterators of various important containers in STL in a relatively simple spoken language based on what he has learned and simple understanding for a long time. , I hope you can support Xiaojie, thank you very much.  
  • The wonderful previous link is attached as follows

Block analysis from function prototype to block implementation of C++ STL (vector) .csdn.net/weixin_53695360/article/details/123248476?spm=1001.2014.3001.5501

1. Think about the design of the iterator of the list

  • First of all, regarding the design of the iterator of the list, it is no longer as simple as the vector, because the List is not a continuous storage space to store elements, and there is no way to access the elements as directly as the ++ operation of the native pointer in the vector. To access post-order elements,   but iterator is such a class that can support ++ -- * operations, we need to be able to traverse and access elements in the entire container through the operation of ++ iterators
  • Because of the above requirements, the node LIstNode* pnode in our List needs to be managed by an iterator class specially designed for it, so that the   pnode can access the next element through the ++ operation, -- the operation can go to access the previous element
  • So the general idea came out, a struct iterator iterator class encapsulates and manages a ListNode* pointer, the function is a smart pointer class, manages a ListNode* pointer, aligns and performs various operator overloading: so that our ListNode* pointer can be passed through ++ -- The operation implements the traversal of the container
  • Node class

  •  Iterator class: Only the important components of the iterator are pasted here. To clarify the idea, it is quite easy to write the operator overloaded functions of other iterators.

  •  First of all: we set the iterator to three template parameters, namely T Ref Ptr, is it necessary to do so? The answer is definitely yes. It is impossible to design three template parameters in the STL source code for no reason.
  • Because the post-sequence needs to use ListIterator<T, Ref, Ptr> and ListNode<T>, but it is a bit troublesome to write with templates, so aliases are created, and the self alias represents the iterator body type

  •  The solution to the above problem is actually here. The core key is the generation of two iterators, one is const_iterator and the other is iterator. Once we set it as three template parameters, when we pass in const, he will follow the The template of the template automatically generates a const class for us, so we no longer need to design a new class for const, there is no need to do this manual expense, and there is no need to write a new const iterator management if there is a template. class

2. Analysis and understanding of important function prototypes, with these first write out to see the effect

Analysis of the necessary functions of the iterator framework

  • operator * overloading is to take out the data in it
  • opeartor-> overloading is to take out the address of the data
  • != and == Everyone knows, to determine whether it is an iterator

  •  ++ -- necessary operations for operating iterators, representing forward and backward a node

Analysis of the necessary functions of the List framework

  • Writing a default construct is simple, a virtual node with no data acts as the header, and the front and rear pointers point to the header itself.

  • Insert a node at the end, three processes
  1. Get the tail node pointer pTail and create a new newnode node
  2. pTail is connected to newnode, newnode becomes the new tail
  3. To form a loop, the tail node of newnode needs to be connected to head

  •  The above simply writes that for_each uses iterators as template parameters, not for anything else, just to let everyone feel why iterators    can be used as the glue between various containers and algorithms, and there is no way without iterators Traversal of containers is supported . . .             
  • Because there is no iterator to manage the object pointer, the smart pointer class allows it to support the ++ -- * operation to traverse the entire container generically, and perform algorithm func processing on the elements in the container,      (which all traversal algorithms of STL do not have The law is fulfilled, and they are all paralyzed)

3. Build a general framework

#include <iostream>
#include <list>
#include <assert.h>
using namespace std;

namespace tyj {
	//链表节点的设计, 双向循环链表
	template<class T>
	struct ListNode {
		ListNode(const T& _val = T())
			: pre(nullptr)
			, next(nullptr)
			, val(_val) {
		}
		ListNode<T>* pre;
		ListNode<T>* next;
		T val;
	};

	//分析一下三个模板??? 为啥要三个,其实这个是STL源码里面这样设计的
	template<class T, class Ref, class Ptr>
	struct ListIterator {
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr > self;
		Node* pnode;	//Iterator管理的指针

		ListIterator(Node* _pnode = nullptr)
			: pnode(_pnode) { 
		}
		Ref operator*() {
			return pnode->val;	//*重载访问val
		}

		Ptr operator->() {		//支持->访问
			return &pnode->val;
		}

		bool operator!=(const self& obj) const {
			return pnode != obj.pnode;
		}
		bool operator==(const self& obj) const {
			return pnode == obj.pnode;
		}

		self& operator++() {	//前置++ -- 操作返回本体
			pnode = pnode->next;
			return *this;
		}
		self operator++(int) {
			self before(*this);	//返回的是之前的
			pnode = pnode->next;
			return before;
		}


		self& operator--() {
			pnode = pnode->pre;
			return *this;
		}

		self operator--(int) {
			self before(*this);
			pnode = pnode->pre;
			return before;
		}

	};

	template<class T>
	class List {
		typedef ListNode<T> Node;//
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
		//此处体现出来了  Ref  Ptr模板化的好处了	
		List() : head(new Node) {
			head->pre = head->next = head;//双向链表的最初状态
		}

		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 push_back(const T& val) {
			Node* pTail = head->pre;
			Node* newnode = new Node(val);
			//连接到尾部
			pTail->next = newnode;
			newnode->pre = pTail;
			//成环连接到head上
			newnode->next = head;
			head->pre = newnode;
		}

	private:
		Node* head;//头部指针指向头部结点

	};

	template<class InputIterator, class Function>
	void for_each(InputIterator first, InputIterator last, Function f) {
		while (first != last) {
			f(*first++);
		}
	}
	template<class T>
	struct Print {
		void operator()(const T& val) const {
			cout << val << " ";
		}
	};
}

template<class T>
void PrintList(tyj::List<T>& lt) {
	tyj::for_each(lt.begin(), lt.end(), tyj::Print<T>());
    cout << endl;
}

int main() {

	tyj::List<int> lt;
	for (int i = 0; i < 5; ++i) {
		lt.push_back(i);
	}
	PrintList(lt);

	return 0;
}

4. Block analysis of function details (similar to vector)

  • range construction

  •  Both vector and list are implemented in this way, traversing the incoming range, and then calling the push_back multiplexing code to implement the incoming, a common routine.
  • swap prepares code for reuse
void swap(List& lt) {
	::swap(head, lt.head);
}
  • swap: very pure and simple exchange, exchange your members to me, I will become you, you will become me

  • Copy construction (because of swap, I initialize myself to nullptr to avoid wild pointers, and then exchange the tmp object constructed by multiplexing the range construction code to construct itself)

List(const List& lt) 
	: head(nullptr) {
	List tmp(lt.begin(), lt.end());
	swap(tmp);	//换, 复用范围构造
}
  • assignment overloading

//直接复用拷贝构造出来的lt
List& operator=(List lt) {
	head = nullptr;
	swap(lt);
	return *this;
}
  • The lt object constructed by pure multiplexing copy, you are a temporary object anyway, and it needs to be destructed when it comes out of the scope. I will directly replace your underlying heap resource structure this, and reuse the copy structure ( essentially, the range structure is reused ). ) 
  • Move construction (snatch the heap resources of the dying object for construction)

List(List&& lt) {
    head = nullptr;
	swap(lt);
}
  • Move construction, directly change, do nothing else, why it can be like this, copy construction needs to reuse range range construction to construct a tmp to change, but move construction is direct change,   
  • Core reason: The parameter passed in by the move construction is an rvalue, what is an rvalue, a dying object, a temporary object, since the parameter is a dying object, I can directly use its underlying heap resources to construct itself

  • After writing the insert() + erase() interface, it is equivalent to writing six interfaces

  • insert()

  • insert(pos, val); insert a node newnode whose value is val before the position of the pos iterator
  • push_back() reuse insert function

void push_back(const T& val) {
	insert(end(), val);
    //在end()前插入一个newnode结点
}
  • push_front() reuse insert function

void push_front(const T& val) {
	insert(begin(), val);
    //在begin前插入一个newnode结点    
}
  • erase();

The summary of erase(pos) is three sentences:

  1. Take the next node before and after pos
  2. delete pos
  3. The pre and next nodes are connected 
  • pop_back() reuse erase

void pop_back() {
	erase(--end());
    //删除end()前一个迭代器
    //end() == head 
    //--end() == head->pre == pTail
}
  • pop_front() reuse erase

void pop_front() {
	erase(begin());
    //begin() head->next;
    //head->next == firstnode
    //head是一个空头结点, firstnode才是真实的第一个元素
}

5. Overall code + test

#include <iostream>
#include <list>
#include <algorithm>
#include <assert.h>
using namespace std;

namespace tyj {
	//链表节点的设计, 双向循环链表
	template<class T>
	struct ListNode {
		ListNode(const T& _val = T())
			: pre(nullptr)
			, next(nullptr)
			, val(_val) {
		}
		ListNode<T>* pre;
		ListNode<T>* next;
		T val;
	};

	//分析一下三个模板??? 为啥要三个,其实这个是STL源码里面这样设计的
	template<class T, class Ref, class Ptr>
	struct ListIterator {
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr > self;
		Node* pnode;	//Iterator管理的指针

		ListIterator(Node* _pnode = nullptr)
			: pnode(_pnode) { 
		}
		Ref operator*() {
			return pnode->val;	//*重载访问val
		}

		Ptr operator->() {		//支持->访问
			return &pnode->val;
		}

		bool operator!=(const self& obj) const {
			return pnode != obj.pnode;
		}
		bool operator==(const self& obj) const {
			return pnode == obj.pnode;
		}

		self& operator++() {	//前置++ -- 操作返回本体
			pnode = pnode->next;
			return *this;
		}
		self operator++(int) {
			self before(*this);	//返回的是之前的
			pnode = pnode->next;
			return before;
		}


		self& operator--() {
			pnode = pnode->pre;
			return *this;
		}

		self operator--(int) {
			self before(*this);
			pnode = pnode->pre;
			return before;
		}

	};

	template<class T>
	class List {
		typedef ListNode<T> Node;
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
		//此处体现出来了  Ref  Ptr模板化的好处了	
		List() : head(new Node) {
			head->pre = head->next = head;//双向链表的最初状态
		}

		template<class InputIterator>
		List(InputIterator first, InputIterator last)
			: head(new Node) {
			head->pre = head->next = head;
			while (first != last) {
				push_back(*first++);
			}
		}

		void swap(List& lt) {
			::swap(head, lt.head);
		}

		List(const List& lt)
			: head(nullptr) {
			List tmp(lt.begin(), lt.end());
			swap(tmp);	//换, 复用范围构造
		}

		//直接复用拷贝构造出来的lt
		List& operator=(List lt) {
			head = nullptr;
			swap(lt);
			return *this;
		}

		List(List&& lt) {
			swap(lt);
		}

		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);//虚拟头部结点
		}

		//在pos位置插入一个val 
		void insert(iterator pos, const T& val) {
			assert(pos.pnode);//先断言结点位置存在, 不存在就无法插入
			Node* cur = pos.pnode;//先拿取到结点指针
			Node* pre = cur->pre;
			Node* newnode = new Node(val);//创建新的结点

			pre->next = newnode;
			newnode->pre = pre;
			//新的结点连接pre
			newnode->next = cur;
			cur->pre = newnode;
			//新的结点连接cur
		}

		void push_back(const T& val) {
			//Node* pTail = head->pre;
			//Node* newnode = new Node(val);
			连接到尾部
			//pTail->next = newnode;
			//newnode->pre = pTail;
			成环连接到head上
			//newnode->next = head;
			//head->pre = newnode;
			insert(end(), val);
		}
		 
		void push_front(const T& val) {
			insert(begin(), val);
		}


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

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

		//删除pos迭代器位置元素返回下一个元素
		iterator erase(iterator pos) {
			assert(pos.pnode);//存在才可以做删除操作
			assert(pos != end());
			//拿取到前面一个结点和后一个结点
			Node* pre = pos.pnode->pre;
			Node* next = pos.pnode->next;

			//删除现在iterator
			delete pos.pnode;

			pre->next = next;
			next->pre = pre;
			return iterator(next);
		}
		void clear() {
			Node* p = head->next, *q;
			while (p != head) {
				q = p->next;
				delete p;
				p = q;
			}
			delete head;
		}

		size_t size() {
			size_t ans = 0;
			Node* p = head->next;
			while (p != head) {
				ans += 1;
				p = p->next;
			}
			return ans;
		}

		~List() {
			if (head != nullptr)
				clear();
			head = nullptr;
		}
	private:
		Node* head;//头部指针指向头部结点
	};

	template<class InputIterator, class Function>
	void for_each(InputIterator first, InputIterator last, Function f) {
		while (first != last) {
			f(*first++);
		}
	}
	template<class T>
	struct Print {
		void operator()(const T& val) const {
			cout << val << " ";
		}
	};
}

template<class T>
void PrintList(tyj::List<T>& lt) {
	tyj::for_each(lt.begin(), lt.end(), tyj::Print<T>());
	cout << endl;
}


// 测试List的构造
void TestList1()
{
	tyj::List<int> l1;
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	tyj::List<int> l3(array, array + sizeof(array) / sizeof(array[0]));
	PrintList(l3);
	tyj::List<int> l4(l3);
	PrintList(l4);
	l1 = l4;
	PrintList(l1);
}

void TestList2()
{
	// 测试PushBack与PopBack
	tyj::List<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	PrintList(l);
	l.pop_back();
	l.pop_back();
	PrintList(l);
	l.pop_back();
	cout << l.size() << endl;
	// 测试PushFront与PopFront
	l.push_front(1);
	l.push_front(2);
	l.push_front(3);
	PrintList(l);
	l.pop_front();
	l.pop_front();
	PrintList(l);
	l.pop_front();
	cout << l.size() << endl;
}

void TestList3()
{
	int array[] = { 1, 2, 3, 4, 5 };
	tyj::List<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto pos = l.begin();
	l.insert(l.begin(), 0);
	PrintList(l);

	++pos;
	l.insert(pos, 2);
	PrintList(l);
	l.erase(l.begin());
	l.erase(pos);
	PrintList(l);
	// pos指向的节点已经被删除,pos迭代器失效
	cout << *pos << endl;
	auto it = l.begin();
	while (it != l.end())
	{
		it = l.erase(it);
	}
	cout << l.size() << endl;
}

int main() {

	//tyj::List<int> lt;
	//for (int i = 0; i < 5; ++i) {
	//	lt.push_back(i);
	//}

	//PrintList(lt);
	//int arr[5] = { 1, 2, 3, 4, 5 };
	//tyj::List<int> lt2(arr, arr + 5);

	//PrintList(lt2);
	/*TestList1();*/

	/*TestList2();*/

	TestList3();
	return 0;
}

6. Summary

  • As far as list is compared to vector, list can better reflect the importance of iterator design
  • In essence, the bottom layer of STL's list is a two-way circular linked list (data structure), the most valuable part of which is the design of iterator. How the iterator glues the container + algorithm is particularly important
  • First, we need to encapsulate a ListNode structure to write a linked list:    
  • We need to manage the pointers of ListNode*, so we need to design the ListIterator class to support operator overloading of various pointers, to support specific container traversal methods, and glue algorithms
  •  Lessons from writing STL or writing something small
  • First, see if there is a certain framework or official documents. If there is, divide the overall framework into components, design and implement important components separately, read documents for important interfaces, and analyze and implement them.
  • Write a simple test of the framework. After the framework has no problems, we can see small effects and joy, and then slowly nibble on the key interfaces. . After writing the interface, it should be tested in time
  • Finally, the overall code is tested under various special scenarios. . find debug

Thank you for reading Xiaojie's article. Xiaojie will continue to introduce some STL design and writing ideas according to his own mastery level. If you think Xiaojie's writing is not bad, please pay attention and support. Thank you very much. I wish you all the best in your studies and majors, and all those who work will be promoted and raised.

Guess you like

Origin blog.csdn.net/weixin_53695360/article/details/123647344