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

1.基本框架

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

cpp 复制代码
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.注意拷贝构造与赋值重载的区别是:拷贝构造是使用一个已存在的对象初始化一个未存在的对象,而赋值重载是使用一个已存在的对象初始化另一个已存在的对象

cpp 复制代码
	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析构函数

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

cpp 复制代码
~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

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

2.迭代器

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

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

2.2两种迭代器

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

cpp 复制代码
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由于直接释放了节点,那么指向被释放节点的迭代器就也被释放,称为迭代器失效,不可以再使用该迭代器

cpp 复制代码
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直接使用下标[]来随机访问节点,所以我们要重载++、--、!=、==来对节点操作,即++返回本节点下一节点的地址以达到遍历节点的效果,--返回本节点上一节点的地址来达到反向遍历,重载*可以直接访问该节点的数据而不用对该节点地址解引用

cpp 复制代码
//类模版可以打印不同类型的数据,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原来的前一个节点,最后返回新节点的地址即可,保证迭代器不会失效

cpp 复制代码
//模拟实现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位置设置为头结点与尾节点以达到头插与尾插即可

cpp 复制代码
//复用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

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

cpp 复制代码
//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即可,将目标节点设置为首尾节点以达到头删与尾删的效果

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

void pop_front()
{
	rease(begin());
}
相关推荐
泉崎2 分钟前
11.7比赛总结
数据结构·算法
你好helloworld3 分钟前
滑动窗口最大值
数据结构·算法·leetcode
何曾参静谧34 分钟前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
AI街潜水的八角43 分钟前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
白榆maple1 小时前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少1 小时前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋2 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
程序员勋勋2 小时前
【自动化测试】如何在jenkins中搭建allure
职场和发展·jenkins·测试覆盖率
何曾参静谧2 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
咕咕吖3 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展