list模拟实现

list模拟实现

🔥list的结构

首先,在模拟实现list之前我们先了解一些list的结构。

list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。

因此,我们若要实现list,则首先需要实现一个结点类 。而一个结点需要存储的信息有:要存储的数据、前一个结点的地址、后一个结点的地址

🔥节点类的实现

这里因为很多地方都要用到这个节点类,所以为了方便,定义成struct的类,这样,成员变量就都是共有的啦。

cpp 复制代码
template <class T>
struct ListNode
{
	ListNode<T>* _prev;
	ListNode<T>* _next;
	T _data;

	ListNode(const T& data = T())
		:_prev(nullptr)
		, _next(nullptr)
		, _data(data)
	{}
};

🔥list的实现

有了节点类之后我们应该怎么,定义一个链表呢?

我们知道,链表其实就是用一个指向头节点的指针管理起来的 ,所以,我们可以定义一个list类,它的成员变量是一个指向头节点的指针,因为,list的底层是一个带头双向循环链表,所以这里的指针,应该指向,带有哨兵位的头节点。

🌈构造函数

list是一个带头双向循环链表,在构造一个list对象时,首先要申请一个头结点,并让其前驱指针和后继指针都指向自己。

cpp 复制代码
list()
{
	_head = new node; //申请头结点
	_head->_next = _head; //头结点的后继指针指向自己
	_head->_prev = _head; //头结点的前驱指针指向自己
}

🌈拷贝构造

方式一:

我们先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面即可。

因为这里的申请头节点的这个操作经常用所以我们可以封装成一个函数。

cpp 复制代码
void empty_list()
{
	_head = new Node;
	_head->_prev = _head;
	_head->_next = _head;
}
cpp 复制代码
list(const list<T>& lt)
{
	empty_list();

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

方式二:

我们先实现一个使用迭代器区间构造的函数。

cpp 复制代码
template <class Iterator>
list(Iterator first, Iterator last)
{
	empty_list();//不加会出问题

	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

然后,我们先创建一个临时对象让他利用被拷贝对象的迭代器构造出来,然后再交换,被利用完后的临时对象会在栈帧结束后被清除。

我们先实现swap函数,很简单,交换头节点的指针就可以。

cpp 复制代码
void swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);
}
cpp 复制代码
list(const list<T>& lt)
{
	empty_list();

	list<T> tmp(lt.begin(), lt.end());

	swap(tmp);
}

🔥赋值重载

通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换即可。

cpp 复制代码
list<T>& operator=(list<T> tmp)
{
	swap(tmp);

	return *this;
}

🌈析构函数

首先,我们可以调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空即可

cpp 复制代码
void clear()
{
	iterator it = begin();

	while (it != end())
	{
		//it = erase(it);
		erase(it++);
	}
}
cpp 复制代码
~list()
{
	clear();

	delete _head;
	_head = nullptr;
}

🔥insert 和 erase

🌈insert

insert函数的作用时在指定迭代器的位置之前插入一个数据。

cpp 复制代码
iterator insert(iterator pos, const T& x)
{
	Node* node = pos._node;//记录当前节点
	Node* prev = node->_prev;//记录前驱节点

	Node* newnode = new Node(x);//记录要插入节点

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

	return iterator(newnode);
}

🌈erase

erase函数用来删除指定迭代器位置的数据。

cpp 复制代码
iterator erase(iterator pos)
{
	assert(pos != end());//防止删除头节点
	
	Node* del = pos._node;//记录要删除节点
	Node* prev = del->_prev;//记录前驱节点
	Node* next = del->_next;//记录后续节点

	prev->_next = next;
	next->_prev = prev;

	return iterator(next);//返回所给迭代器pos的下一个迭代器,防止迭代器失效
}

🔥尾插,尾删,头插,头删

这里直接复用insert和erase即可。

cpp 复制代码
// List的插入和删除
void push_back(const T& val) 
{ 
	insert(end(), val); 
}

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

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

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

🔥访问容器相关函数

cpp 复制代码
T& front()
{
	return _head->_next->_data;
}

const T& front() const
{
	return _head->_next->_data;
}

T& back()
{
	return _head->_prev->_data;
}

const T& back() const
{
	return _head->_prev->_data;
}

🔥其他函数

size

size函数用于获取当前容器当中的有效数据个数,因为list是链表,所以需要通过遍历的方式统计有效数据的个数。

cpp 复制代码
size_t size() const
{
	size_t count = 0;

	Node* pcur = _head->_next;
	while (pcur != _head)
	{
		count++;
		pcur = pcur->_next;
	}

	return count;
}

resize

resize函数的规则:

1,若当前容器的oldsize小于所给newsize,则尾插结点,直到oldsize等于newsize为止。

2,若当前容器的oldsize大于所给newsize,则只保留前newsize个有效数据。

cpp 复制代码
void resize(size_t newsize, const T& data = T())
{
	size_t oldsize = size();

	if (newsize <= oldsize)
	{
		while (newsize != oldsize)
		{
			pop_back();
			oldsize--;
		}
	}
	else
	{
		while (oldsize != newsize)
		{
			push_back(data);
			oldsize++;
		}
	}
}

🔥迭代器实现

对于,vector迭代器来说我们可以使用原生指针来实现,因为vector的空间时连续的,_start时指向第一个数据的指针,_start++就指向下一个位置了

但是对于list来说,它的数据不是连续存储的而是通过一个一个节点通过指针连接到一起的 ,所以,_head++,并不能到下一个数据的位置,但是可以通过_head = _head->_next实现,所以这里我们可以使用节点的指针单独封装一个类,通过运算符重载模拟指针的行为。

cpp 复制代码
template <class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	Node* _node;

	typedef ListIterator<T,Ref,Ptr> self;

	ListIterator(Node* node)
		:_node(node)
	{}

	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;
	}

	Ref operator*()
	{
		return _node->_data;
	}

	bool operator!=(const self& node)
	{
		return _node != node._node;
	}

	bool operator==(const self& node)
	{
		return _node == node._node;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}
};

🌈迭代器类的模板参数说明

cpp 复制代码
template<class T, class Ref, class Ptr>

在list的模拟实现当中,我们typedef了两个迭代器类型,普通迭代器和const迭代器

cpp 复制代码
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;

所以,迭代器类的模板参数列表当中的RefPtr分别代表的是引用类型和指针类型。

当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。

🌈begin和end

begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。

cpp 复制代码
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);
}

全部代码实现

cpp 复制代码
namespace hb
{
	template <class T>
	struct ListNode
	{
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;

		ListNode(const T& data = T())
			:_prev(nullptr)
			, _next(nullptr)
			, _data(data)
		{}
	};

	template <class T,class Ref,class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		Node* _node;

		typedef ListIterator<T,Ref,Ptr> self;

		ListIterator(Node* node)
			:_node(node)
		{}

		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;
		}

		Ref operator*()
		{
			return _node->_data;
		}

		bool operator!=(const self& node)
		{
			return _node != node._node;
		}

		bool operator==(const self& node)
		{
			return _node == node._node;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}
	};

	template <class T>
	struct ListConstIterator
	{
		typedef ListNode<T> Node;
		Node* _node;

		typedef ListConstIterator<T> self;

		ListConstIterator(Node* node)
			:_node(node)
		{}

		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;
		}

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

		bool operator!=(const self& node)
		{
			return _node != node._node;
		}

		bool operator==(const self& node)
		{
			return _node == node._node;
		}

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


	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;
		//typedef ListConstIterator<T> const_iterator;

		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_list()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

		list()
		{
			empty_list();
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();

			while (it != end())
			{
				//it = erase(it);
				erase(it++);
			}
		}



		/*list(const list<T>& lt)
		{
			empty_list();

			for (const auto& e : lt)
			{
				push_back(e);
			}
		}*/

		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_list();//不加会出问题

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list(const list<T>& lt)
		{
			empty_list();

			list<T> tmp(lt.begin(), lt.end());

			swap(tmp);
		}

		list<T>& operator=(list<T> tmp)
		{
			swap(tmp);

			return *this;
		}

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

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

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

			insert(end(),x);
		}

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

		void pop_back()
		{

			erase(--end());
		}

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

		iterator insert(iterator pos, const T& x)
		{
			Node* node = pos._node;
			Node* prev = node->_prev;

			Node* newnode = new Node(x);

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

			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			

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

			prev->_next = next;
			next->_prev = prev;

			return iterator(next);
		}

		bool empty() const
		{
			return _head == _head->_next;
		}

		T& front()
		{
			return _head->_next->_data;
		}

		const T& front() const
		{
			return _head->_next->_data;
		}

		T& back()
		{
			return _head->_prev->_data;
		}

		const T& back() const
		{
			return _head->_prev->_data;
		}

		size_t size() const
		{
			size_t count = 0;

			Node* pcur = _head->_next;
			while (pcur != _head)
			{
				count++;
				pcur = pcur->_next;
			}

			return count;
		}

		void resize(size_t newsize, const T& data = T())
		{
			size_t oldsize = size();

			if (newsize <= oldsize)
			{
				while (newsize != oldsize)
				{
					pop_back();
					oldsize--;
				}
			}
			else
			{
				while (oldsize != newsize)
				{
					push_back(data);
					oldsize++;
				}
			}
		}
		
	private:
		Node* _head;
};
相关推荐
微澜-16 分钟前
编译以前项目更改在x64下面时报错:函数“PVOID GetCurrentFiber(void)”已有主体
c++
YuanLiu_22722 分钟前
代码随想录算法训练营第十三天(递归遍历;迭代遍历;统一迭代;层序遍历)
java·数据结构·笔记·算法·leetcode
闻缺陷则喜何志丹24 分钟前
【C++动态规划】1411. 给 N x 3 网格图涂色的方案数|1844
c++·算法·动态规划·力扣·网格·数目·涂色
熬夜学编程的小王1 小时前
【C++篇】解锁C++模板的魔法:从万能钥匙到精准雕刻
c++·进阶模版·c++模版·类模版实例化·函数模版实例化
Octopus20771 小时前
【C++】读取数量不定的输入数据
开发语言·c++·笔记·学习
忘梓.1 小时前
C嘎嘎探索篇:栈与队列的交响:C++中的结构艺术
c语言·开发语言·c++·
f狐0狸x1 小时前
【数据结构实战篇】用C语言实现你的私有队列
c语言·数据结构·链表··队列
十五年专注C++开发1 小时前
C++中的链式操作原理与应用(一)
开发语言·c++·设计模式
PaLu-LI2 小时前
ORB-SLAM2源码学习:Initializer.cc:Initializer::CheckFundamental地图初始化——检查基础矩阵F并评分
c++·opencv·学习·线性代数·ubuntu·计算机视觉·矩阵
9毫米的幻想2 小时前
【Linux系统】—— 基本指令(四)
linux·c语言·c++·学习