list模拟实现

前言

前面我们已经对string、vector、list的使用进行了介绍并对vector和string的底层做了模拟实现,本期我们来探索list的底层!

本期内容介绍

list迭代器模拟实现

list常用接口的模拟实现

基本框架的搭建

我们还是先来看看库里面是如何做的!(这里参考的是SGI版的源码)

人家先是搞了一个节点类,用于节点的开辟!里面的成员变量有三个:前驱和后继指针以及数据域!

下来是他的迭代器类,里面有一个_list_node<T> * 类型的成员变量!就是节点的指针!

这里你会有疑问:有个单独的节点类我理解,但我们前面的容器都没有单独迭代器好像也可以啊,这里怎么搞一个单独的迭代器呢?

前面的容器没有单独的迭代器的原因是无论是string还是vector他们的物理空间都是连续的,他们的原生指针就可以当迭代器来用!所以我们前面模拟实现的迭代器都是原生指针,但是链表的物理空间不一定连续(大概率不连续),所以我们得自己实现一下迭代器!!

再下来就是list类了,里面只有一个成员变量!是_list_node<int>* 类型的!实际上这个node就是哨兵位的头结点!

OK,我们先来搭建一个框架出来吧!

cpp 复制代码
namespace cp
{
	template<class T>
	struct _list_node
	{
		_list_node<T>* _next;//后继
		_list_node<T>* _prev;//前驱
		T _data;//数据域
	};

	template<class T>
	class _list_iterator
	{
	public:
		typedef _list_node<T> Node;

		Node* _node;//节点的指针
	};


	template<class T>
	class list
	{
	public:
		typedef _list_node<T> Node;

	private:
		Node* _head;//哨兵位的头结点
		size_t _size;//为了后期计算节点的数目方便,我们可以在这里搞一个成员变量记录
	};
}

为了我们后续申请节点方便我们可以在节点的类里面,搞一个搞一个构造函数!

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

	_list_node(const T& val = T())//全缺省的构造函数
		:_next(nullptr)
		,_prev(nullptr)
		,_data(val)
	{}
};

默认构造

默认构造很简单,就是申请一个哨兵位的头结点,并让头结的前驱和后继都指向自己!

cpp 复制代码
list()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
}

这样写固然没问题,但是待会拷贝构造也会有相同的操作!相同的代码出现了两份,有点冗余,所以我们把他封装成一个函数,作用就是搞一个头结点作为链表的初始化!

cpp 复制代码
void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
    _size = 0;
}

list()
{
	empty_init();
}

OK,我们先来搞个尾插!

push_back

实现思路: 和我们以前数据结构的做法一样!找到尾节点(这里就是头节点的前驱),在后面插入一个新节点,然后让他变成尾节点!

cpp 复制代码
void push_back(const T& val)
{
	Node* prev = _head->_prev;
	Node* newnode = new Node(val);
		
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = _head;
	_head->_prev = newnode;
	_size++;
}

OK,尾插了,现在链表有数据了,如何遍历成了问题!所以这里我们先来实现迭代器!

迭代器模拟实现

我们为了后期的类型写起来不那么长,我们在迭代器类里面可以对类型进行重命名!上面的节点类我们重命名为:Node,自身的迭代器类重命名为: Self

typedef**_list_node<T>Node;
typedef
_list_iterator<T>**Self;

OK,我们先来看看,我们平时是如何用迭代器来遍历的!

我们需要begin、end、!= 、* 和++由于链表的空间不连续所以这些都得手搓!

begin和end不是迭代器该考虑的,因为它也不知道你的开始和结束在什么位置!这两个是链表才知道的,所以,我们迭代器只负责上面的三个操作符,可以实现遍历链表即可!

*

我们只要返回,当前迭代器对象的成员节点(_node)的数据即可,可能要修改所以返回引用!

cpp 复制代码
T& operator*()
{
	return _node->_data;
}

前置++

让当前迭代器对象的成员节点(_node)指向下一个(它的后继),然后返回当前的迭代器对象!

cpp 复制代码
Self& operator++()
{
	_node = _node->_next;
	return *this;
}

!=

比较两个迭代器对象(当前迭代器对象和目标迭代器对象)的_node是否相同!注意这里一定比较的是节点的指针!!!

cpp 复制代码
bool operator!=(const Self& it)
{
	return _node != it._node;
}

OK,有了这些我们就可以遍历了!我们来试一下:

OK,没有问题,我们再把后置++和==来搞一下!

后置++

我们知道后置++等操作符都是返回的是改变以前的值!这里也是一样,所以我们先得拷贝一份当前迭代器对象,然后再让当前迭代器对象的_node到下一个(它的后继),最后返回那个拷贝即可!

cpp 复制代码
//后置++
Self operator++(int)
{
	Self tmp(*this);
	_node = _node->_next;
	return tmp;
}

==

比较当前迭代器对象和目标迭代器对象的_node是否相等!

cpp 复制代码
bool operator==(const Self& it)
{
	return _node == it._node;
}

这里与++对应的还有--因为是双向循环的,所以很好实现!

前置--

让当前迭代器对象的成员节点(_node)指向上一个(它的前驱),然后返回当前的迭代器对象!

cpp 复制代码
//前置--
Self& operator--()
{
	_node = _node->_prev;
	return *this;
}

后置--

还是和后置++一样,先拷贝当前的迭代器对象,然后在让当前的迭代器对象指向它的上一个(它的前驱),最后返回那个拷贝的迭代器对象即可!

cpp 复制代码
//后置--
Self operator--(int)
{
	Self tmp(*this);
	_node = _node->_prev;
	return tmp;
}

begin

这个不是迭代器的成员函数!迭代器不知道链表的起始在哪里!这个是链表才知道的,所以这两个是链表的成员函数!!!

实现思路:让头结点的后继(_next)去构造一个迭代器对象然后返回即可!

cpp 复制代码
iterator begin()
{
	return iterator(_head->_next);
}

end

实现思路:让头结点去构造一个迭代器对象然后返回即可!

cpp 复制代码
iterator end()
{
	return iterator(_head);
}

这里其实还可以这样写:

cpp 复制代码
iterator begin()
{
	return _head->_next;
}

iterator end()
{
	return _head;
}

原因是我们前面在类和对象(下) 介绍过的一个小知识点:**单参数或支持接收第一个参数的构造支持隐式类型转换!**这里他就自动构造迭代器对象了!!

->

这个接口很多伙伴不太明白为什要搞这个操作符??OK,我先来举个例子:

cpp 复制代码
​
struct A
{
	int _a;
	int _b;

	A(int a = 0, int b = 0)
		:_a(a)
		,_b(b)
	{}
};

void test_list1()
{
	list<A> lt;
	lt.push_back({ 10, 20 });
	lt.push_back({ -1, 0 });
	lt.push_back({ 1, 2 });
}

​

此时要访问当前链表该如何访问呢???刚刚的迭代器一定不行了!肯定报错!!此时就是就得用->了!OK我们先见一见:

实现思路:我们返回当前迭代器对象的成员_node的数据域的地址即可!!

然后我们在根据指针的->去取数据访问即可!!!

cpp 复制代码
T* operator->()
{
	return &_node->_data;
}

所以这里编译器应该是省略了一个:实际应该是这样的:

普通迭代器

cpp 复制代码
template<class T>
class _list_iterator
{
public:
	typedef _list_node<T> Node;
	typedef _list_iterator<T> Self;

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

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

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

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

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

	Node* _node;
};

OK,迭代器就搞定了!但是这里有个问题就是,现在的迭代器只是支持普通的list的访问,如果这个list是const修饰的呢?就不能访问了!如何解决呢?其实很简单:在单独搞一个const的迭代器类即可!只需要要将operator*和operator->的返回值修改一下即可!

const迭代器

cpp 复制代码
template<class T>
	class _list_cosnt_iterator
	{
	public:
		typedef _list_node<T> Node;
		typedef _list_cosnt_iterator<T> Self;

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

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

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

		Node* _node;
	};

这里我们只需要在list中实现const迭代器的begin和end即可!

cpp 复制代码
const_iterator begin() const 
{
	return const_iterator(_head->_next);
}

const_iterator end() const
{
	return const_iterator(_head);
}

OK,举个例子验证一下:

OK,没问题!这里虽然实现了但是是不是觉得很冗余呀!两个迭代器就只有*和->的返回值有点区别,除此之外其他都已一样!我们能不能想个办法给把他两个冗余代码减少一下呢???当然!我们前面的模板就可以做到!

既然只有T*和T&不同,那我们把他们搞成模板参数!等链表用的时候让它自己去根据给定是否是const的模板参数再去分别实例化,这样就不用我们写两份代码了!

在list类中我们只需要给的两种迭代器的模板参数即可!

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

此时我们只需要这样的一份代码即可!

迭代器最终源码

cpp 复制代码
template<class T , class Ref, class Ptr>
class _list_iterator
{
public:
	typedef _list_node<T> Node;
	typedef _list_iterator<T, Ref, Ptr> Self;

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

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

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

	Node* _node;
};

总结:list的迭代器已经和我们前面的string、vector的底层实现有很大的区别了!但是在我们用户层面上感觉好像一样!这就是C++等OOP语言封装的魅力吧!

常用接口的模拟实现

我们前面已经把默认构造和尾插实现了!下来我们接着实现其他的接口!

拷贝构造

实现思路:给新链表先搞一个哨兵位的头结点,然后遍历被拷贝的链表,用他的值(数据域)逐一尾插到新链表。

cpp 复制代码
list(const list<T>& lt)
{
	empty_init();
	for (auto& e : lt)
	{
		push_back(e);
	}
}

迭代器区间构造

实现思路:先给当前的链表初始化一个头结点!然后依次遍历迭代器区间取出对应的数据尾插到当前链表的尾部即可!

cpp 复制代码
template <class Iterator>
list(Iterator first, Iterator last)
{
	empty_init();
	while (first != last)
	{
		push_back(*first);;
		++first;
	}
}

初始化序列构造

实现思路:还是先搞一个头结点,然后依次取出initializer_list<T> 类型的对象il的每个值一次尾插即可!

cpp 复制代码
list(initializer_list<T> il)
{
	empty_init();
	for (auto& e : il)
	{
		push_back(e);
	}
}

赋值拷贝的传统写法

我们的实现思路有两种。

第一(传统写法):把当前链表给清空,然后拿着要拷贝的每个节点的值逐一尾插,最后返回当前链表对象的引用即可!(这里用到了clear不了解的建议点击目录查看clear)

第二(现代写法):判断是否是自我赋值,如果不是,拷贝要赋值的那个链表,然后与当前链表交换!最后返回当前链表对象的引用即可!

cpp 复制代码
list<T>& operator=(list<T>& lt)
{
	if (&lt != this)
	{
		clear();
		for (auto e : lt)
		{
			push_back(e);
		}
	}

	return *this;
}

OK,这里要实现现代写法我们还得先来把swap给实现一下!

swap

实现思路:利用算法库提供的swap交换两个链表的成员属性即可!

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

赋值拷贝的现代写法

cpp 复制代码
list<T>& operator=(list<T>& lt)
{
	if (&lt != this)
	{
		list<T> tmp(lt);
		swap(tmp);
	}

	return *this;
}

size

我们提前在成员属性那里进行了记录!所以这里直接返回_size即可!

cpp 复制代码
size_t size() const
{
	return _size;
}

empty

判断是否_size 为0即可

cpp 复制代码
bool empty() const
{
	return _size == 0;
}

insert

实现思路:先把pos位置的那个节点的指针(记作cur)拿到,然后记录它的前驱(记作prev),在用val开一个节点(newnode),然后把newnode连接到cur和prev的中间,++一下_size即可!最后记得返回新插入的节点的迭代器对象即可!

cpp 复制代码
iterator insert(iterator pos, const T& val)
{
	Node* cur = pos._node;
	Node* newnode = new Node(val);
	Node* prev = cur->_prev;

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

	_size++;
	return newnode;
}

erase

实现思路:先判断是否当前的链表为空,为空的话就别删了!否则记录当前节点、当前节点的前驱和后继,然后把当前节点释放掉,让他的前驱和后继链接,减减一下_size即可!最后记得返回删除后第一个节点的迭代器对象即可!

cpp 复制代码
iterator erase(iterator pos)
{
	assert(!empty());//空了就不要删了
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	delete cur;
	prev->_next = next;
	next->_prev = prev;
	--_size;

	return next;
}

OK,有了insert和erase后我们就不用在和前面一样去一步一步的去写头插和头插、尾删了!直接复用insert和erase即可!

push_back

cpp 复制代码
void push_back(const T& val)
{
	insert(end(), val);
}

push_front

cpp 复制代码
void push_front(const T& val)
{
	insert(begin(), val);
}

pop_back

注意:这里一定是--end因为end是最后一节点的下一个节点!!!

cpp 复制代码
void pop_back()
{
	erase(--end());
}

pop_front

cpp 复制代码
void pop_front()
{
	erase(begin());
}

clear

删除并清理除了头结点以外的其他所有的节点!

cpp 复制代码
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}

析构

利用clear释放掉出头结点的所有节点,最后释放掉头结点!

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

front

返回头结点的_next的数据域的引用即可!

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

back

返回头结点的_prev的数据域的引用即可!

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

全部源码

cpp 复制代码
#pragma
#include <assert.h>

namespace cp
{
	template<class T>
	struct _list_node
	{
		_list_node<T>* _next;
		_list_node<T>* _prev;
		T _data;

		_list_node(const T& val = T())//全缺省的构造函数
			:_next(nullptr)
			,_prev(nullptr)
			,_data(val)
		{}
	};

	template<class T , class Ref, class Ptr>
	class _list_iterator
	{
	public:
		typedef _list_node<T> Node;
		typedef _list_iterator<T, Ref, Ptr> Self;

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

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

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

		Node* _node;
	};

	template<class T>
	class list
	{
	public:
		typedef _list_node<T> Node;
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;

		const_iterator begin() const 
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

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

		iterator end()
		{
			return _head;
		}

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

		list()
		{
			empty_init();
		}

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

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);;
				++first;
			}
		}

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

		//list<T>& operator=(list<T>& lt)
		//{
		//	if (&lt != this)
		//	{
		//		clear();
		//		for (auto e : lt)
		//		{
		//			push_back(e);
		//		}
		//	}

		//	return *this;
		//}

		list<T>& operator=(list<T>& lt)
		{
			if (&lt != this)
			{
				list<T> tmp(lt);
				swap(tmp);
			}

			return *this;
		}

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

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

		//void push_back(const T& val)
		//{
		//	Node* prev = _head->_prev;
		//	Node* newnode = new Node(val);
		//
		//	prev->_next = newnode;
		//	newnode->_prev = prev;
		//	newnode->_next = _head;
		//	_head->_prev = newnode;
		//	_size++;
		//}

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

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


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

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

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

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

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

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

			_size++;
			return newnode;
		}

		iterator erase(iterator pos)
		{
			assert(!empty());//空了就不要删了
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			delete cur;
			prev->_next = next;
			next->_prev = prev;
			--_size;

			return next;
		}


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

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}

	private:
			Node* _head;
			size_t _size;
	};
	
	
	template<class T>
	void print(const list<T>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

}

OK,好兄弟我们本期分享就到这里!下期我们再见!

结束语:相信自己,未来可期!

相关推荐
丶Darling.43 分钟前
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树
开发语言·数据结构·c++·笔记·学习·算法
小飞猪Jay1 小时前
面试速通宝典——10
linux·服务器·c++·面试
程序猿阿伟2 小时前
《C++高效图形用户界面(GUI)开发:探索与实践》
开发语言·c++
阿客不是客2 小时前
深入计算机语言之C++:C到C++的过度
c++
LN-ZMOI2 小时前
c++学习笔记1
c++·笔记·学习
no_play_no_games2 小时前
「3.3」虫洞 Wormholes
数据结构·c++·算法·图论
￴ㅤ￴￴ㅤ9527超级帅2 小时前
LeetCode hot100---数组及矩阵专题(C++语言)
c++·leetcode·矩阵
五味香2 小时前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
PYSpring3 小时前
数据结构-LRU缓存(C语言实现)
c语言·数据结构·缓存
Mr Aokey3 小时前
双向无头非循环链表的简单实现及介绍
数据结构