首页 > 编程语言 >深入了解链表 list 之的模拟实现 list (C++)

深入了解链表 list 之的模拟实现 list (C++)

时间:2024-09-04 21:54:40浏览次数:17  
标签:node head const list C++ 链表 return prev

1.基本框架

关于链表我们知道其是一个双向循环结构,并且由许多节点组成,各个节点之间内存空间不一定连续,每个节点均有前驱指针与后继指针,下面我们使用类模版来实现一个适用于存储大部分数据类型的链表,由下面代码我们可以看到一些基础框架与很简单的函数size返回长度与empty判断是否为空

namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;
		_size = 0;

		//构造函数
		list_node(const T& data = T()) //匿名对象保证默认构造可以是带参构造
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{}
	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		list()
		{
			//哨兵位,初始化时后继指针与前驱指针均指向自己
			_head = new Node;
			_head->next = _head;
			_head->prev = _head;
		}
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* head;
		size_t _size;
	};
}

1.1构造函数

这里我们主要实现的是构造函数以及拷贝构造函数、赋值重载,在下面还实现了一个特殊的构造函数list(initializer_list<T> il),是为了实现在链表初始化时直接初始化为一串数字,如下图中所实现的代码,initializer_list 可以在C++11的标准中查询到。

1.在实现构造函数时需要注意对空链表初始化保证哨兵位的存在,实现拷贝构造与赋值重载的目的就是防止编译器默认的拷贝构造与赋值重载属于浅拷贝,存在内存泄露的风险

2.注意拷贝构造与赋值重载的区别是:拷贝构造是使用一个已存在的对象初始化一个未存在的对象,而赋值重载是使用一个已存在的对象初始化另一个已存在的对象

	void empty_init()
	{
		//哨兵位,初始化时后继指针与前驱指针均指向自己
		_head = new Node;
		_head->next = _head;
		_head->prev = _head;

		_size = 0;
	}

	list()
	{
		empty_init();
	}
	
	//初始化该类型变量需要定义一个新的构造函数
	//initializer_list<int> lt = {1,2,3,4,5}
	//一个il是四个字节,其中包含了两个指针
	list(initializer_list<T> il)
	{
		empty_init();

		for (auto& e : il)
		{
			push_back(e);
		}
	}

	//拷贝构造
	//lt2(lt1)
	list(const list<T>& lt)
	{
		empty_init();
		for (auto& e : lt)
		{
			push_back(e);
		}
	}
	//直接交换
	void swap(list<T>& lt)
	{
		std::swap(_head, lt._head);
		std::swap(_size, lt._size);
	}

	//赋值重载
	//lt3 = lt1
	list<T>& operator=(const list<T>& lt)
	{
		swap(lt);
		return *this;
	}

1.2析构函数 

析构函数就是删除除了哨兵位之外的节点,最后释放哨兵位并置空即可

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

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

2.迭代器

链表根据迭代器性质是一个双向迭代器,这意味着他只能进行 ++ 与 -- 的操作而不能进行 + 与 - 的操作,这时如果我们想使用一个迭代器来遍历一个链表,那么根据下面的代码我们可以知道如果直接对链表进行++、--、!=、== 操作就会报错,因为链表并非是一块连续存储的空间,所以我们可以对链表重载实现++、--、!=、== 来实现迭代器遍历,具体的迭代器实现我们下面详细讲解

//迭代器遍历
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
	cout << *it << " ";
	++it;
}

2.2两种迭代器

这里两种迭代器分别是普通迭代器与const迭代器,二者大致结构均相同,不同的是const迭代器的某些函数需要使其成为const类型的函数,所以为了避免代码冗余,我们可以直接创建类模版来实现两种迭代器,让编译器为我们区分应该使用哪一种迭代器即可,代码中的Ref与Ptr、Self都是根据不同的输入来由编译器判断是普通迭代器还是const迭代器

namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;
		_size = 0;

		//构造函数
		list_node(const T& data = T()) //匿名对象保证默认构造可以是带参构造
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

	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;

		iterator begin()
		{
			iterator it(_head->next);
			return it;
		}

		iterator end()
		{
			return _head;
		}

		const_iterator begin() const
		{
			iterator it(_head->next);
			return it;
		}

		const_iterator end() const
		{
			return _head;
		}

		void empty_init()
		{
			//哨兵位,初始化时后继指针与前驱指针均指向自己
			_head = new Node;
			_head->next = _head;
			_head->prev = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}
		
		//初始化该类型变量需要定义一个新的构造函数
		//initializer_list<int> lt = {1,2,3,4,5}
		//一个il是四个字节,其中包含了两个指针
		list(initializer_list<T> il)
		{
			empty_init();

			for (auto& e : il)
			{
				push_back(e);
			}
		}

		//拷贝构造
		//lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		//直接交换
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		//赋值重载
		//lt3 = lt1
		list<T>& operator=(const list<T>& lt)
		{
			swap(lt);
			return *this;
		}

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

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

		//迭代器
		template<class T,class Ref,class Ptr>
		struct list_iterator
		{
			typedef list_node<class T> Node;
			typedef list_iterator<class T,class Ref, class Ptr> Self;
			Node* _node;

			//构造函数
			list_iterator(Node* node)
				:_node(node)
			{}

			//重载*使其返回该节点的数据
			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 tmo(*this);
				_node = _node->_prev;

				return tmp;
			}

			//重载!=用来实现迭代器
			bool operator!=(const Self& s)
			{
				return _node != s._node;
			}
			//重载==用来实现迭代器
			bool operator==(const Self& s)
			{
				return _node == s._node;
			}
		};

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

2.2迭代器失效 

insert不失效,erase失效,在进行insert操作后是直接在目标节点前插入一个节点,此时迭代器的指向不会失效,而erase由于直接释放了节点,那么指向被释放节点的迭代器就也被释放,称为迭代器失效,不可以再使用该迭代器

namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;
		_size = 0;

		//构造函数
		list_node(const T& data = T()) //匿名对象保证默认构造可以是带参构造
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

	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;

		iterator begin()
		{
			iterator it(_head->next);
			return it;
		}

		iterator end()
		{
			return _head;
		}

		const_iterator begin() const
		{
			iterator it(_head->next);
			return it;
		}

		const_iterator end() const
		{
			return _head;
		}

		void empty_init()
		{
			//哨兵位,初始化时后继指针与前驱指针均指向自己
			_head = new Node;
			_head->next = _head;
			_head->prev = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}
		
		//初始化该类型变量需要定义一个新的构造函数
		//initializer_list<int> lt = {1,2,3,4,5}
		//一个il是四个字节,其中包含了两个指针
		list(initializer_list<T> il)
		{
			empty_init();

			for (auto& e : il)
			{
				push_back(e);
			}
		}

		//拷贝构造
		//lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		//直接交换
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		//赋值重载
		//lt3 = lt1
		list<T>& operator=(const list<T>& lt)
		{
			swap(lt);
			return *this;
		}

		void push_back(const T& x)
		{
			//创建新节点,保留原来尾节点
			Node* newnode = new Node(x);
			Node* tail = _head->prev;

			//更新尾节点
			tail->next = newnode;
			newnode->prev = tail;

			//将新的尾节点接入链表
			newnode->next = _head;
			_head->prev = newnode;

			++_size;
		}

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

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

		//迭代器
		template<class T,class Ref,class Ptr>
		struct list_iterator
		{
			typedef list_node<class T> Node;
			typedef list_iterator<class T,class Ref, class Ptr> Self;
			Node* _node;

			//构造函数
			list_iterator(Node* node)
				:_node(node)
			{}

			//重载*使其返回该节点的数据
			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 tmo(*this);
				_node = _node->_prev;

				return tmp;
			}

			//重载!=用来实现迭代器
			bool operator!=(const Self& s)
			{
				return _node != s._node;
			}
			//重载==用来实现迭代器
			bool operator==(const Self& s)
			{
				return _node == s._node;
			}
		};

		//使用类模版避免代码冗余
		const迭代器
		//template<class T>
		//struct list_const_iterator
		//{
		//	typedef list_node<T> Node;
		//	typedef list_const_iterator<T> Self;
		//	Node* _node;

		//	//构造函数
		//	list_iterator(Node* node)
		//		:_node(node)
		//	{}

		//	//重载*使其返回该节点的数据
		//	const T& operator*()
		//	{
		//		return _node->data;
		//	}

		//	//重载->
		//	const 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 tmo(*this);
		//		_node = _node->_prev;

		//		return tmp;
		//	}

		//	//重载!=用来实现迭代器
		//	bool operator!=(const Self& s)
		//	{
		//		return _node != s._node;
		//	}
		//	//重载==用来实现迭代器
		//	bool operator==(const Self& s)
		//	{
		//		return _node == s._node;
		//	}
		//};

		//模拟实现insert,在pos位置之前插入数据
		iterator insert(iterator pos, const T& x)
		{
			//prev newnode cur
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			//插入新节点newnode
			newnode->_next = cur;
			cur->_prev = newnode;
			newnode->_prev = prev;
			prev->_next = newnode;

			++_size;

			return newnode;
		}
		//复用insert实现push_front(push_back同样可以复用)
		//头插
		push_front()
		{
			insert(begin(), x);
		}
		//尾插
		push_back(const T& x)
		{
			创建新节点,保留原来尾节点
			//Node* newnode = new Node(x);
			//Node* tail = _head->prev;

			更新尾节点
			//tail->next = newnode;
			//newnode->prev = tail;

			将新的尾节点接入链表
			//newnode->next = _head;
			//_head->prev = newnode;

			//++_size;

			insert(end(), x);
		}

		//erase 删除指定位置数据
		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 next;
		}

		//复用erase实现头删尾删
		void pop_back()
		{
			erase(--end());
		}

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

		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* head;
		size_t _size;
	};
}

2.3模拟重载迭代器中的操作符 

由于链表属于链式结构式不连续存储的结构,所以不能像string/vector直接使用下标[]来随机访问节点,所以我们要重载++、--、!=、==来对节点操作,即++返回本节点下一节点的地址以达到遍历节点的效果,--返回本节点上一节点的地址来达到反向遍历,重载*可以直接访问该节点的数据而不用对该节点地址解引用

//类模版可以打印不同类型的数据,vector/list/string等
template<class Container>
void print_container(const Container& con)
{
	//const iterator 迭代器本身不能修改
	//const_iterator 迭代器指向内容不能修改

	//加上typename保证编译器能识别 Container::const_iterator 是一种数据类型
	typename Container::const_iterator it = con.begin();

	//迭代器遍历
	while (it != con.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//范围 for 遍历
	for (auto& e : con)
	{
		cout << e << " ";
	}
	cout << endl;
}

namespace bit
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;
		_size = 0;

		//构造函数
		list_node(const T& data = T()) //匿名对象保证默认构造可以是带参构造
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

	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;

		iterator begin()
		{
			iterator it(_head->next);
			return it;
		}

		iterator end()
		{
			return _head;
		}

		const_iterator begin() const
		{
			iterator it(_head->next);
			return it;
		}

		const_iterator end() const
		{
			return _head;
		}

		void empty_init()
		{
			//哨兵位,初始化时后继指针与前驱指针均指向自己
			_head = new Node;
			_head->next = _head;
			_head->prev = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}
		
		//初始化该类型变量需要定义一个新的构造函数
		//initializer_list<int> lt = {1,2,3,4,5}
		//一个il是四个字节,其中包含了两个指针
		list(initializer_list<T> il)
		{
			empty_init();

			for (auto& e : il)
			{
				push_back(e);
			}
		}

		//拷贝构造
		//lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		//直接交换
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		//赋值重载
		//lt3 = lt1
		list<T>& operator=(const list<T>& lt)
		{
			swap(lt);
			return *this;
		}

		void push_back(const T& x)
		{
			//创建新节点,保留原来尾节点
			Node* newnode = new Node(x);
			Node* tail = _head->prev;

			//更新尾节点
			tail->next = newnode;
			newnode->prev = tail;

			//将新的尾节点接入链表
			newnode->next = _head;
			_head->prev = newnode;

			++_size;
		}

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

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

		//迭代器
		template<class T,class Ref,class Ptr>
		struct list_iterator
		{
			typedef list_node<class T> Node;
			typedef list_iterator<class T,class Ref, class Ptr> Self;
			Node* _node;

			//构造函数
			list_iterator(Node* node)
				:_node(node)
			{}

			//重载*使其返回该节点的数据
			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 tmo(*this);
				_node = _node->_prev;

				return tmp;
			}

			//重载!=用来实现迭代器
			bool operator!=(const Self& s)
			{
				return _node != s._node;
			}
			//重载==用来实现迭代器
			bool operator==(const Self& s)
			{
				return _node == s._node;
			}
		};

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

3.常用接口

3.1insert

随机插入接口,在pos节点之前插入数据,即首先开辟一个新节点,将新节点后继指针指向pos节点,将pos前驱指针指向新节点,将pos原来的前一个节点的后继指针指向新节点,将新节点前驱指针指向pos原来的前一个节点,最后返回新节点的地址即可,保证迭代器不会失效

//模拟实现insert,在pos位置之前插入数据
iterator insert(iterator pos, const T& x)
{
	//prev newnode cur
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(x);

	//插入新节点newnode
	newnode->_next = cur;
	cur->_prev = newnode;
	newnode->_prev = prev;
	prev->_next = newnode;

	++_size;

	return newnode;
}

3.2push_back/push_front

头插、尾插接口,可以直接复用insert函数,将pos位置设置为头结点与尾节点以达到头插与尾插即可

//复用insert实现push_front(push_back同样可以复用)
//头插
push_front()
{
	insert(begin(), x);
}
//尾插
push_back(const T& x)
{
	创建新节点,保留原来尾节点
	//Node* newnode = new Node(x);
	//Node* tail = _head->prev;

	更新尾节点
	//tail->next = newnode;
	//newnode->prev = tail;

	将新的尾节点接入链表
	//newnode->next = _head;
	//_head->prev = newnode;

	//++_size;

	insert(end(), x);
}

3.3erase

删除接口,删除指定节点就直接将该节点前后两节点连接起来然后释放需要释放的节点即可,最后返回被删除节点的下一个节点,保证可以持续删除

//erase 删除指定位置数据
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 next;
}

3.4pop_back/pop_front

头删、尾删接口,直接复用erase即可,将目标节点设置为首尾节点以达到头删与尾删的效果

//复用erase实现头删尾删
void pop_back()
{
	erase(--end());
}

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

 

标签:node,head,const,list,C++,链表,return,prev
From: https://blog.csdn.net/2301_80689220/article/details/141828368

相关文章

  • 数据结构——单链表查询、逆序、排序
    1、思维导图2、查、改、删算法//快慢排序法找中间值intmid_link(Link_t*plink){Link_Node_t*pfast=plink->phead;Link_Node_t*pslow=pfast;intm=0;while(pfast!=NULL){pfast=pfast->pnext;++m;if(m%......
  • 单向链表与双向链表
    内存泄漏:手动申请的空间没有得到及时释放,导致内存发生内存泄漏(循环)    可以使用valgrind命令判断有无发生内存泄漏快慢指针法找中间节点:链表倒置: 链表插入排序: 单向链表与双向链表区别:一、单向链表:    1.单向链表的每个节点包含两部分信息:一部分是......
  • Linkedlist源码详解
    介绍LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。这样看来,LinkedList简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用LinkedList,一方面是因为Java官方已经声明不建议使用Stack类......
  • linkedlist
    data=newLinkedList[BASE];这行代码的意思是初始化一个LinkedList对象的数组。具体解释如下:数组声明:LinkedList[]表示data是一个数组,这个数组将存储LinkedList类型的对象。在这个上下文中,它将存储LinkedList<Integer>对象,用于存储整数。大小指定:newLinkedList......
  • C++机试——查找组成一个偶数最近的两个素数
    题目描述任意一个偶数(大于2)都可以由2个素数组成,组成偶数的2个素数有很多种情况,本题目要求输出组成指定偶数的两个素数差值最小的素数对。数据范围:输入的数据满足 4≤n≤1000 4≤n≤1000 输入描述:输入一个大于2的偶数输出描述:从小到大输出两个素数思路      ......
  • C++语言基础--代码框架
    引入    工欲善其事,必先利其器。我们在编写C++代码之前,一定要了解到C++的代码框架。代码框架可以说是我们所有的C++代码都一定具备的。本章将详细解析C++的代码框架。代码框架#include<cstdio>#include<iostream>usingnamespacestd;intmain(){return......
  • C++:异常
    文章目录什么是异常?异常: 报错:一、异常的处理方式1.抛出异常2.捕获异常二、标准异常三、自定义异常什么是异常?异常: 异常这个概念可能会有一些陌生,但是str.at(i)我们并不陌生,当i值越界时就会产生一个异常语句:terminatecalledafterthrowinganinstanceof......
  • windows C++ 并行编程-并发和UWP(三)
    控制执行线程Windows运行时使用COM线程模型。在此模型中,根据对象处理其同步的方式,对象被托管在不同的单元中。线程安全对象托管在多线程单元(MTA)中。必须通过单个线程访问的对象托管在单线程单元(STA)中。在具有UI的应用程序中,ASTA(应用程序STA)线程负责发送窗......
  • windows C++ 并行编程-并发和UWP(一)
    本文介绍当在通用Windows运行时(UWP)应用中使用任务类生成基于Windows线程池的异步操作时要谨记的一些要点。异步编程的使用是Windows运行时应用模型中的关键组成部分,因为它能使应用保持对用户输入的响应。可以启动长期运行的任务,而不必阻止UI线程,并且可以在以后接......
  • linux C++基于共享内存的同步机制
    无缘进程间同步,本来打算使用有名信号量进行同步,但是有名信号量的初始化会受进程启动顺序影响,故使用共享内存进行封装,封装后的使用方法类似二值信号量,代码如下:1#include<sys/ipc.h>//ipc:inter-processcommunication进程通信2#include<sys/shm.h>//shm:shareme......