list的模拟实现

一.list的简单介绍

list是一个带头双向循环的链表,通过头结点分隔开首末尾。它和vector的使用方法类似,可以进行头插尾插,++/--等操作,区别在于list的结点在内存上不是连续的。list属于双向迭代器即可以++/--,不能想vector一样可以进行随机访问。

二.list模拟实现的三个类

1.结点类

根据list的特点带头双向循环,我们可以得知,他需要一个前置结点prev,以及后置结点next,保证能向前向后找到结点。 每个结点存有数据data,我们可以用模板T来进行复用。

cpp 复制代码
template <class T>
	struct list_node
	{
		list_node(const T& x = T())//模板有x时情况
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
		{}

		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;
	};

2.迭代器类

我们知道迭代器分为普通迭代器和const迭代器,在实现string类和vector类时我们并没有单独对迭代器这一类进行实现,这是因为vector和string类在底层空间上是连续的,可以直接进行++/--,解引用操作。但list在底层中是由指针来连接的,所以直接++/--解引用无法实现我们想要的效果。所以我们要单独提出迭代器对++/--解引用进行重载运算符操作。

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

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

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

	T& operator++()//前置
	{
		_node = _node->_next;
		return *this;
	}

	T& operator++(int)//后置,后置++区分前置要加入括号
	{
		Self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	T& operator--()//前置
	{
		_node = _node->_prev;
		return *this;
	}

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

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

	bool operator!=(const T& it)
	{
		return _node != it._node;
	}

	bool operator==(const T& it)
	{
		return _node == _it._node;
	}
};

如上我们对迭代器进行了初步的实现,里面的实现内容较为简单就不一一展开叙述。这时候又有了另一个问题,如果迭代器是const类型的该怎么办呢?还是像上面这样,重新封装一个const_iterator类吗?

这样操作固然是没有问题的,但是还不够简略。

我们可以仔细想想,普通迭代器与const迭代器的区别不就是多了一个const吗?所以我们可以考虑用模板来套用实现。

cpp 复制代码
	template<class T,class Ref,class Ptr>//提供模板参数,为const类型提供模板参数,ref代替引用,ptr代替*,复用
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		Node* _node;

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

		Ref 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 tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}

		bool operator==(const Self& it)
		{
			return _node == _it._node;
		}
	};

我们将原先的T& 用模板Ref来替代,T* 用模板Ptr来代替。这样就对代码进行了缩减。所以用ref和ptr模板我们就不需要关心是不是const类型迭代器,只要传就是什么,我们就无需关心。

3.链表类

cpp 复制代码
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()
		{
			return list_iterator(_head->_next);
		}

		iterator end()
		{
			return list_iterator(_head);
		}

		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

		list()
		{
			empty_init();
		}

		list(initializer_list<T> it)
		{
			for (auto& e : it)
			{
				push_back(e);
			}
		}

		list(const list<T>& it)
		{
			empty_init();
			for (auto& e : it)
			{
				push_back(e);
			}
		}

		~list()
		{
			clear();
		}

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

		list<T>& operator=(list<T> it)
		{
			swap(it);
			return *this;
		}

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

		void push_back(const T& x)
		{
			Node* newnode = new Node(x);

			Node* tail = _head->_prev;
			newnode->_prev = tail;
			tail->_next = newnode;
			newnode->_next = _head;
			_head->_prev = newnode;
		}

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

		void push_back(const T& x)
		{
			insert(end(), x);
		}

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

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

		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			cur->_prev = newnode;
			newnode->_next = cur;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* nextNode = cur->_next;
			Node* prevNode = cur->_prev;

			prevNode->_next = nextNode;
			nextNode->_prev = prevNode;

			delete cur;
			return iterator(nextNode);
		}

	private:
		Node* _head;
	};
}

上述只需要注意一点,就是erase的用法。erase在官方库中是有返回值的,在使用时,例如析构时需要用变量来接收erase的返回值,否则容易导致迭代器失效。

链表部分的实现不难,基本都是先前vector类实现的功能相差无几,构造函数,拷贝函数,析构函数,迭代器的运用,以及增删等操作。

三.总结

本篇文章最关键的内容就是对于模板的复用,在面对代码相似度较高,实现功能类似的情况下,我们需要优先考虑模板的复用。

下面放上完整代码:

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;

namespace wu
{
	template <class T>
	struct list_node
	{
		list_node(const T& x = T())//模板有x时情况
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
		{}

		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;
	};

	template<class T,class Ref,class Ptr>//提供模板参数,为const类型提供模板参数,ref代替引用,ptr代替*,复用
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self;
		Node* _node;

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

		Ref 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 tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}

		bool operator==(const Self& it)
		{
			return _node == _it._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;

		iterator begin()
		{
			return list_iterator(_head->_next);
		}

		iterator end()
		{
			return list_iterator(_head);
		}

		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

		list()
		{
			empty_init();
		}

		list(initializer_list<T> it)
		{
			for (auto& e : it)
			{
				push_back(e);
			}
		}

		list(const list<T>& it)
		{
			empty_init();
			for (auto& e : it)
			{
				push_back(e);
			}
		}

		~list()
		{
			clear();
		}

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

		list<T>& operator=(list<T> it)
		{
			swap(it);
			return *this;
		}

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

		void push_back(const T& x)
		{
			Node* newnode = new Node(x);

			Node* tail = _head->_prev;
			newnode->_prev = tail;
			tail->_next = newnode;
			newnode->_next = _head;
			_head->_prev = newnode;
		}

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

		void push_back(const T& x)
		{
			insert(end(), x);
		}

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

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

		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			cur->_prev = newnode;
			newnode->_next = cur;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* nextNode = cur->_next;
			Node* prevNode = cur->_prev;

			prevNode->_next = nextNode;
			nextNode->_prev = prevNode;

			delete cur;
			return iterator(nextNode);
		}

	private:
		Node* _head;
	};
}
相关推荐
dokii121 分钟前
leetcode102 二叉树的层次遍历 使用队列实现二叉树广度优先遍历
数据结构·深度优先·图论
ylfhpy4 小时前
Java面试黄金宝典19
java·开发语言·数据结构·算法·面试·面经
姜威鱼4 小时前
蓝桥杯python编程每日刷题 day 20
数据结构·算法·蓝桥杯
LIUJH12334 小时前
哈希冲突 及 双哈希
开发语言·数据结构·c++·算法·哈希算法
代码AC不AC5 小时前
【数据结构】栈 与【LeetCode】20.有效的括号详解
数据结构·学习·leetcode·练习·
Phoebe鑫5 小时前
数据结构每日一题day5(顺序表)★★★★★
数据结构·算法
Vitalia5 小时前
并查集(Union-Find)数据结构详解
数据结构·并查集
java-王森7 小时前
List中多个数据相同,保留最新日期数据
linux·windows·list
PlumCarefree7 小时前
通过Map类和List类的List<Map<>>组合类体会JSON
java·数据结构·json·list
Yhame.7 小时前
【 C 语言实现顺序表的基本操作】(数据结构)
c语言·开发语言·数据结构