list【2】模拟实现(含迭代器实现超详解哦)

12 篇文章 0 订阅

引言(实现概述)

在前面,我们介绍了list的使用:
戳我看list的介绍与使用详解哦

在本篇文章中将重点介绍list的接口实现,通过模拟实现可以更深入的理解与使用list
在这里插入图片描述
我们模拟实现的 list 底层是一个带头双向循环链表

在实现list时,我们首先需要一个结构体以表示链表中结点的结构list_node,大致包括数据与指向前后结点的指针

template<class T>
struct list_node  //结点类
{
	list_node<T>* _prev;
	list_node<T>* _next;
	T _date;

	list_node(const T& date = T()) //匿名对象
		: _prev(nullptr)
		, _next(nullptr)
		, _date(date)
	{}
};

在有了结点之后,还需要一个 list类,包括双向链表的头结点指针_pHead,链表中的元素个数_size

template<class T>
class list  //带头双向循环链表
{
	typedef list_node<T> Node;
private:
	Node* _pHead;
	size_t _size;
};

大致结构如下:
在这里插入图片描述
关于带头双向循环链表的实现可以参考之前的C语言版本实现,在本篇文章中结构将不是最重点的内容:戳我看C语言实现带头双向循环链表详解哦

与vector相同,list是一个类模板,其声明与定义不能分离。我们将模拟实现的list放在我们创建的命名空间内,以防止与库发生命名冲突。
在list的模拟实现中,我们只实现一些主要的接口,包括默认成员函数、迭代器、容量、元素访问与数据修改

list迭代器实现

与vector不同,list的迭代器是指针的封装,通过运算符重载来实现原生指针的功能
我们可以通过封装结点的指针,即list_node<T>*,来实现迭代器:

默认成员函数

对于默认成员函数,其实我们只需要实现构造函数即可,这个__list_iterator类的属性只有一个结构体指针,并不存在类中申请的资源,所以编译器生成的析构函数与赋值运算符重载就够用了,不需要再实现了。

  1. 默认构造
    对于默认构造函数,我们可以使用缺省参数,即参数类型为结构体指针,缺省值为nullptr
    直接在初始化列表中用参数初始化类属性即可:
	__list_iterator(Node* pNode = nullptr)
		:_pNode(pNode)
	{}
  1. 拷贝构造
    对于拷贝构造,参数必须是类类型的引用,可以加上const修饰(我们可以在类中将类类型__list_iterator<T, Ref, Ptr>重命名为self,以方便书写)
    在函数中直接赋值即可:
	__list_iterator(const self& it)
	{
		_pNode = it._pNode;
	}

operator* 与 operator->

迭代器使用时应该是与指针基本类似的,所以也需要重载*->

  1. 重载 *
    指针当然可以进行解引用的操作,指向容器中元素的指针。对这个指针进行解引用操作结果就应该是该指针指向的元素。对于list的迭代器而言,解引用该迭代器的结果就应该是结点中的_data元素的引用,类型为&T
    (我们可以为模板参数加上一个参数,即Ref,它表示T&
	Ref operator*()
	{
		return _pNode->_date;
	}
  1. 重载->
    T是自定义类型时,其指针还可以通过->直接访问到T类型对象_data中的元素,起指针作用的迭代器自然也需要实现这个功能
    但是对于这个运算符重载而言,它并不知道要返回T类型对象中的什么元素,所以这个operator->函数可以直接返回该迭代器对应的结点中的_data元素的指针,然后在调用的时候再通过->来访问其中元素:it->->a;(通过迭代器it访问T类型对象中的a元素)。
    但是,这样的形式访问又与指针的用法不一致,所以这里有一个特殊的规定,即规定需要使用it->a;的方式通过迭代器访问T类型对象中的元素,以获得与原生指针相同的使用方式
    (我们可以为模板参数加上一个参数,即Ptr,它表示T*
	Ptr operator->()
	{
		return &(_pNode->_date);
	}

operator++ 与 operator–

  1. 重载 ++
    ++操作即实现迭代器的指向向后移动一个元素,list的迭代器底层是一个结构体指针,所以只需要将当前_pNode->_next 赋值给_pNode即可
    ++分为前置++与后置++,在区别这两个的实现时,前面类和对象时已经详细介绍过了,即给后置++增加一个参数int,但是不传参,只用于区分前置与后置。
    另外后置++需要创建临时对象,在*this++后必须要返回临时对象而非引用:
	self& operator++()
	{
		_pNode = _pNode->_next;
		return *this;
	}
	self operator++(int)
	{
		self temp(*this);
		_pNode = _pNode->_next;
		return temp;
	}
  1. 重载 --
    重载--与前面的++类似,即_pNode->_prev 赋值给_pNode即可
	self& operator--()
	{
		_pNode = _pNode->_prev;
		return *this;
	}
	self operator--(int)
	{
		self temp(*this);
		_pNode = _pNode->_prev;
		return temp;
	}

operator== 与 operator!=

对于==!= 重载,即判断两个迭代器对象的属性是否相等即可

	bool operator==(const self& it)
	{
		return _pNode == it._pNode;
	}
	bool operator!=(const self& it)
	{
		return _pNode != it._pNode;
	}

迭代器实现概览

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		Node* _pNode;

		__list_iterator(Node* pNode = nullptr)
			:_pNode(pNode)
		{}
		__list_iterator(const self& it)
		{
			_pNode = it._pNode;
		}


		Ref operator*()
		{
			return _pNode->_date;
		}
		Ptr operator->()
		{
			return &(_pNode->_date);
		}


		self& operator++()
		{
			_pNode = _pNode->_next;
			return *this;
		}
		self operator++(int)
		{
			self temp(*this);
			_pNode = _pNode->_next;
			return temp;
		}
		self& operator--()
		{
			_pNode = _pNode->_prev;
			return *this;
		}
		self operator--(int)
		{
			self temp(*this);
			_pNode = _pNode->_prev;
			return temp;
		}


		bool operator==(const self& it)
		{
			return _pNode == it._pNode;
		}
		bool operator!=(const self& it)
		{
			return _pNode != it._pNode;
		}
	};

list主要接口实现

在实现list之前,我们可以对一些较为麻烦的类型进行重命名以方便书写:

typedef list_node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

默认成员函数

构造函数

实现构造函数时,我们需要实现无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造

无参构造
由于我们模拟实现的list底层为带头双向循环链表,所以在无参构造时虽然没有在list中放入元素,但是还使需要先放入一个空结点作为头节点
创建头节点的操作在任何构造函数中都要先进行,所以我们将其封装为一个init_empty_list函数。这个初始化空list的函数需要new一个结点,然后使节点中的_prev_next都指向它自身,最后将_size赋值为0:

	void init_empty_list()
	{			
		_pHead = new Node();
		_pHead->_prev = _pHead;
		_pHead->_next = _pHead;
		_size = 0;
	}
	
	list()
	{
		init_empty_list();
	}

n个指定元素构造:
使用n个指定元素构造有两个参数,第一个就是int,第二个是const T&,缺省值为T()
函数中,首先调用init_empty_list构建一个头结点;
再循环,使用push_bake尾插n个指定元素value即可(push_back后面会实现):

	list(int n, const T& value = T())
	{
		init_empty_list();
		while (n--)
		{
			push_back(value);
		}
	}

迭代器区间构造
是用迭代器区间构造函数是一个函数模板,可以使用任何容器的迭代器区间来构造list
函数中,首先调用init_empty_list构建一个头结点;
然后再循环,使用push_bake尾插first迭代器的解引用出的元素,当firstlast相等时出循环:

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

拷贝构造:
拷贝构造函数的参数是一个const list<T>&
实现时,首先调用init_empty_list构建一个头结点;
然后范围for,使用push_bake将l中的元素逐一尾插到list中:

	list(const list<T>& l)
	{
		init_empty_list();
		for (auto el : l)
		{
			push_back(el);
		}
	}

析构函数

析构函数需要实现释放list中的资源。
首先复用clear,清空list中的元素。clear中会实现释放结点中的资源,后面部分会实现;
delete _pHead;,释放头节点中的资源,它会调用结点结构体的析构函数

	~list()
	{
		clear();
		delete _pHead;
	}

赋值重载

对于赋值运算符重载,我们直接使用新写法,即先使用参数l创建一个临时对象;
然后使用swap将临时对象与*this交换(后面会实现swap函数);
最后返回*this即可,创建的临时对象就会在函数栈帧销毁时自动释放

	list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可
	{
		list<T> temp(l);
		swap(temp);
		return *this;
	}

迭代器

在前面已经实现了list的迭代器,它是结点指针的封装

这里暂时只实现beginend,关于反向迭代器的实现在后面会详细介绍。
begin返回首元素的地址,即头结点的下一个结点的地址
end返回尾元素下一个位置的地址,即头节点的地址,他们分别重载有const版本:

	iterator begin()
	{
		return iterator(_pHead->_next);
	}
	iterator end()
	{
		return iterator(_pHead);
	}
	
	const_iterator begin() const
	{
		return const_iterator(_pHead->_next);
	}
	const_iterator end() const
	{
		return const_iterator(_pHead);
	}

容量

在容器部分,由于list并没有容量的概念,所以我们只需要实现sizeempty即可;
在这里插入图片描述
我们在list的属性中,我们设置了_size,在插入元素时_size++,删除元素时_size--,所以这里只需要返回_size的值即可;
_size == 0时,list为空,empty返回true,否者返回false

	size_t size() const
	{
		return _size;
	}
	bool empty() const
	{
		if (_size == 0)
			return true;
		else
			return false;
	}

元素访问

由于list在任意位置访问元素的成本较高,就没有提供operator[]的接口,所以我们只需要实现frontback即可。分别返回首尾的元素,有普通对象与const对象两个重载版本:
在这里插入图片描述
在实现时,我们可以借助list的属性_pHead,即头结点的指针来访问首尾元素
我们模拟实现list的底层是带头双向循环链表,所以list中的第一个元素就是_pHead->_next指向的结点中的元素;list中的_pHead->_prev指向的结点中的元素

front只需要返回 _pHead->_next->_date 即可;
back返回_pHead->_prev->_date即可,返回值类型为T&,const版本就返回const T&即可:

	T& front()
	{
		return _pHead->_next->_date;
	}
	const T& front()const
	{
		return _pHead->_next->_date;
	}
	
	T& back()
	{
		return _pHead->_prev->_date;
	}
	const T& back()const
	{
		return _pHead->_prev->_date;
	}

数据修改

在这里插入图片描述

insert

list 的结构使在任意位置插入数据的效率是较高的,只需要创建结点,再链接到pos位置前即可

在实现insert时,首先new一个结点,类型为Node,并用val初始化(这个Node类型是前面重命名后的类型);
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prev,以方便后续链接;
然后将pos结点的前一个结点与新结点链接,即newnodeprev链接;
再将pos结点与新结点链接,即newnodecur链接;

最后,更新_size,并返回新结点的迭代器:

	// 在pos位置前插入值为val的节点
	iterator insert(iterator pos, const T& val)
	{
		Node* newnode = new Node(val);

		Node* cur = pos._pNode;
		Node* prev = cur->_prev;

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

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

		++_size;

		return iterator(newnode);
	}

erase

erase实现时,只需要释放pos位置的结点,并链接剩余的结点即可:

首先assert判断list是否为空;
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prevpos的后一个结点指针为next,以方便后续链接;
然后直接链接pos位置的前一个结点与后一个结点,即链接prevnext即可;

最后,释放cur指向的结点,更新_size,并返回next

	// 删除pos位置的节点,返回该节点的下一个位置
	iterator erase(iterator pos)
	{
		assert(!empty());
		Node* cur = pos._pNode;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

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

		delete cur;
		--_size;
		return iterator(next);
	}

push_back 与 push_front

对于头插与尾插的实现,复用insert即可:

push_front,即在首结点的前面一个位置插入一个元素,即begin()迭代器位置插入
push_back,即在尾结点的后一个位置插入一个元素,即end()位置插入

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

pop_back 与 pop_front

对于头删尾删的实现,复用erase即可

pop_front,即删除头节点,即 erase删除begin()位置的结点 即可;
pop_back,删除尾结点,即 erase删除end()前面一个结点即可,但是由于list迭代器不支持-操作,所以这里传参为--end()

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

clear

clear用于清理list中的所有元素,可以直接复用erase来实现清理

我们可以通过遍历迭代器的方式逐一释放结点:
it的初始值为begin(),循环逐一erase删除,当it等于end()的时候终止循环,就可以实现删除所有结点并保留头节点;
另外,由于erase删除某节点后,会返回删除节点的下一个位置,所以只要把返回值载赋值给it就实现了迭代器的向后移动

	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);//erase返回删除的结点的下一个位置的迭代器
		}
	}

swap

实现list的交换函数时,只需要使用库swap交换list的属性即可,即交换_pHead_size

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

源码概览

(关于反向迭代器的实现在后面会详细介绍,现在可以暂时忽略)

#include<iostream>
#include<cassert>
#include"my_reverse_iterator.h"

namespace qqq
{
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _date;

		list_node(const T& date = T()) //匿名对象
			: _prev(nullptr)
			, _next(nullptr)
			, _date(date)
		{}
	};

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		Node* _pNode;

		__list_iterator(Node* pNode = nullptr)
			:_pNode(pNode)
		{}
		__list_iterator(const self& it)
		{
			_pNode = it._pNode;
		}


		Ref operator*()
		{
			return _pNode->_date;
		}
		Ptr operator->()
		{
			return &(_pNode->_date);
		}


		self& operator++()
		{
			_pNode = _pNode->_next;
			return *this;
		}
		self operator++(int)
		{
			self temp(*this);
			_pNode = _pNode->_next;
			return temp;
		}
		self& operator--()
		{
			_pNode = _pNode->_prev;
			return *this;
		}
		self operator--(int)
		{
			self temp(*this);
			_pNode = _pNode->_prev;
			return temp;
		}


		bool operator==(const self& it)
		{
			return _pNode == it._pNode;
		}
		bool operator!=(const self& it)
		{
			return _pNode != it._pNode;
		}
	};

	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;
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;

	public:
		/ constructor and destructor 
		void init_empty_list()
		{			
			_pHead = new Node();
			_pHead->_prev = _pHead;
			_pHead->_next = _pHead;

			_size = 0;
		}
		list()
		{
			init_empty_list();
		}
		list(int n, const T& value = T())
		{
			init_empty_list();

			while (n--)
			{
				push_back(value);
			}
		}
		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			init_empty_list();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list(const list<T>& l)
		{
			init_empty_list();
			for (auto el : l)
			{
				push_back(el);
			}
		}
		list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可
		{
			list<T> temp(l);
			swap(temp);
			return *this;
		}
		~list()
		{
			clear();
			delete _pHead;
		}


		// List Iterator///
	
		iterator begin()
		{
			return iterator(_pHead->_next);
		}
		iterator end()
		{
			return iterator(_pHead);
		}
		const_iterator begin() const
		{
			return const_iterator(_pHead->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_pHead);
		}
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}


		/List Capacity//
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			if (_size == 0)
				return true;
			else
				return false;
		}

		///List Access///
		T& front()
		{
			return _pHead->_next->_date;
		}
		const T& front()const
		{
			return _pHead->_next->_date;
		}
		T& back()
		{
			return _pHead->_prev->_date;
		}
		const T& back()const
		{
			return _pHead->_prev->_date;
		}

		List Modify//
		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());
		}
		// 在pos位置前插入值为val的节点
		iterator insert(iterator pos, const T& val)
		{
			Node* newnode = new Node(val);

			Node* cur = pos._pNode;
			Node* prev = cur->_prev;

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

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

			++_size;

			return iterator(newnode);
		}
		// 删除pos位置的节点,返回该节点的下一个位置
		iterator erase(iterator pos)
		{
			assert(!empty());
			Node* cur = pos._pNode;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

			delete cur;
			--_size;
			return iterator(next);
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//erase返回删除的结点的下一个位置的迭代器
			}
		}
		void swap(list<T>& l)
		{
			std::swap(_pHead, l._pHead);
			std::swap(_size, l._size);
		}

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

总结

到此,关于list的模拟实现就到此结束了
模拟实现容器并不是为了造一个更好的轮子,而是为了更好的理解与使用容器

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿qiu不熬夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值