当前位置:   article > 正文

list的模拟实现_list operator ^

list operator ^


在上一篇博客中讲解了list的用法,了解了list的基本使用。这篇博客将会详细介绍list的底层实现原理,更加深入的了解list。

list的节点类型

list的底层是一个双向链表,因此list的节点中包括一前一后两个指针,以及节点的值val。指针prev指向该节点的前一个节点,指针next指向该节点的后一个节点。

//定义节点,通过模板来定义
template <class T>
//用struct定义成公有的,再list类中可以直接访问
struct List_Node
{
	T _val;
	List_Node<T>* _prev;
	List_Node<T>* _next;

	//这里的节点也相当于一个类,因此需要写一个构造函数,针对那些开辟空间时调用的初始化
	List_Node(const T val = T())//构造匿名对象进行赋值
		: _prev(nullptr)
		, _next(nullptr)
		, _val(val)
	{}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

为了能够使节点类型能够满足各种类型,因此节点的定义采用模板实现。并且将其定义为struct,方便list类在类外直接访问节点。

list的迭代器

前几篇博客中讨论的容器,比如string、vector等等的迭代器都是使用的原生指针,这是因为string、vector等容器底层空间是连续的,原生指针就具有迭代器的性质。
但是list每一个节点在底层空间中不连续,因此原生指针作为迭代器就不合适。因此list的迭代器实现就必须对原生指针进行封装,使得迭代器的使用形式与指针完全相同。

解引用

指针是可以解引用来获取指针对应的值,在list的迭代器中必须重载operator*。
在list重载operator也就是获取每个节点对应的值val,因此在list的迭代器中重载operator就是通过每一个节点的指针取到节点对应的值。

//重载*运算符
Ref operator*()
{
	return _node->_val;
}
  • 1
  • 2
  • 3
  • 4
  • 5

通过“->”访问成员

指针可以通过“->”访问其成员,这也是指针的特性之一。在list中想要完成“->”的重载,可以通过对operator*()进行取地址得到,使得重载后的“->”拿到节点的地址对节点成员进行访问。

//这里重载->是为了给自定义类型去使用,使得自定义类型能够通过指针来访问空间变量
Ptr operator->()
{
	return &(operator*());
}
  • 1
  • 2
  • 3
  • 4
  • 5

指针的前后移动

由于list的节点在底层空间中是不连续的,因此普通的++和–无法完成list中的前进和后退,必须在迭代器中对++和–进行重载。
++的操作通过节点中的next指针来完成;–的操作通过节点中的prev来完成。

//前置++和后置++的区别:
//1、前置++的参数中只有this指针,后置++的参数除了this指针,还有int进行占位
//2、前置++先修改再使用,所以返回值加引用;后置++先使用再修改,返回值没有引用。

//重载后置++运算符
self operator++(int)
{
	_node = _node->_next;
	return *this;
}

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

//重载后置--运算符
self operator--(int)
{
	_node = _node->_prev;
	return *this;
}

//重载前置--
self& operator--()
{
	_node = _node->_prev;
	return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

相等的比较

由于迭代器还需要进行是否相等的比较,因此迭代器还必须对operator==和operator!=进行重载。
两个迭代器之间的比较就是比较两个节点的地址是否相同,那么在底层可以直接通过比较节点是否相等来完成。

//重载!=
bool operator!=(self& l)//比较的时候是同一类的对象,参数应该也是对象,不应该是节点
{
	return _node != l._node;
}

//重载==
bool operator==(self& l)
{
	return _node == l._node;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

迭代器部分的完整代码如下:

	//如果想实现const迭代器,如果重新写一个相似的类就会出现代码冗余,因此可以在模板参数这里做调整
	//给定三个模板参数:T、Ref、Ptr
	//当参数不同时,对象的类型也就不同
	template <class T, class Ref, class Ptr>
	struct List_Iterator
	{
		typedef List_Node<T> Node;
		typedef Node* pNode;
		typedef List_Iterator<T, Ref, Ptr> self;
	public:
		List_Iterator(pNode node = nullptr)//迭代器的构造函数就是根据某个节点创建的
			: _node(node)
		{}

		//重载*运算符
		Ref operator*()
		{
			return _node->_val;
		}

		//这里重载->是为了给自定义类型去使用,使得自定义类型能够通过指针来访问空间变量
		Ptr operator->()
		{
			return &(operator*());
		}

		//针对const对象,这里不能在重载*这里加上const
		//因为对象是const,所以返回的迭代器也应该是const迭代器
		//const T& operator*() const
		//{
		//	return _node->_val;
		//}

		//前置++和后置++的区别:
		//1、前置++的参数中只有this指针,后置++的参数除了this指针,还有int进行占位
		//2、前置++先修改再使用,所以返回值加引用;后置++先使用再修改,返回值没有引用。

		//重载后置++运算符
		self operator++(int)
		{
			_node = _node->_next;
			return *this;
		}

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

		//重载后置--运算符
		self operator--(int)
		{
			_node = _node->_prev;
			return *this;
		}

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

		//重载!=
		bool operator!=(self& l)//比较的时候是同一类的对象,参数应该也是对象,不应该是节点
		{
			return _node != l._node;
		}

		//重载==
		bool operator==(self& l)
		{
			return _node == l._node;
		}

		pNode _node;//迭代器中节点是基本的成员变量
	};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

这其中有一点需要注意:
迭代器一般分为非const迭代器和const迭代器,如果是原生指针的画,直接加const就可以完成。但是如果是封装的迭代器,要完成const迭代器,一种方法是写一份相似的迭代器封装,在某些位置加上const,但是这样做会出现代码冗余的情况;第二种方法就是在模板参数上做文章,在模板参数传三个参数,分别对应list的值、引用和指针,这样子我们如果想是用const类型的迭代器,直接传const类型的模板参数即可。

list的构造函数

头节点构造

在实现list的构造函数之前,首先需要一个头节点构造的函数,用于对头节点进行创建。

//头节点创建
void CreateHead()
{
	_head = new Node;
	_head->_prev = _head;
	_head->_next = _head;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

基本构造函数

list中,基本的构造函数就是只创建一个头节点。

//基本构造
list()
{
	CreateHead();
}
  • 1
  • 2
  • 3
  • 4
  • 5

构造n个节点

n个节点的构造函数,在创建头节点之后,通过循环将节点尾插在头节点之后。

list(size_t n, const T& val = T())
{
	CreateHead();
	while (n--)
		push_back(val);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里val的缺省通过一个匿名对象来给定。

通过迭代器构造

由于迭代器根据容器的底层实现是不同的,因此通过迭代器构造list必须以模板来实现,这样才能实现对于各种迭代器的构造。这里的构造就是从前到后遍历迭代器,对每一个节点进行尾插。

//利用迭代器构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
	CreateHead();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

拷贝构造

对于拷贝构造,这里有两种方法:
第一种是定义一个头节点,将被拷贝对象中的每一个元素在新的对象中进行尾插。
第二种是通过构造一个临时对象,与新对象进行交换即可。

//拷贝构造函数
list(const list<T>& x)
{
	//CreateHead();
	//第一种方法:自己定义一个头节点,将x中的每个元素尾插
	//const_iterator it = x.begin();
	//while (it != x.end())
	//{
	//	push_back(*it);
	//	it++;
	//}

	//第二种方法:定义一个临时对象,进行交换
	list<T> tmp(x.cbegin(), x.cend());
	swap(tmp);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

比较建议写第二种,简单易懂。

重载operator=()

重载赋值运算符与拷贝构造一样,也有两种方法:
第一种是将赋值对象的每一个元素进行尾插;
第二种是在参数中赋值对象不加引用,由于此时传过来的是拷贝构造的临时对象,与当前对象进行交换即可。

//重载=运算符
//赋值的话,没有定义过的是调拷贝构造,定义过的是赋值重载
list<T>& operator=(const list<T> x)
{
	//第一种方法:就是将x的每个元素尾插
	//第二种方法:定义一个临时对象,将头节点交换
	swap(tmp);
	return *this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:赋值时,如果被赋值的对象是未定义的,此时调用的是拷贝构造;定义过的调用的是赋值重载。

list的增删查改

尾插

//push_back函数
void push_back(const T& val)
{
	pNode newnode = new Node(val);//这里可以认为是根据节点的类调用构造函数
	pNode tail = _head->_prev;
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

头插

//push_front函数
void push_front(const T& val)
{
	pNode newnode = new Node(val);
	pNode first = _head->_next;
	// _head     newnode    first
	newnode->_prev = _head;
	_head->_next = newnode;
	newnode->_next = first;
	first->_prev = newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

insert函数

//1、普通插入,在pos位置之前
void insert(iterator pos, const T& val)
{
	assert(pos._node);//断言以下空指针
	pNode newnode = new Node(val);
	pNode cur = pos._node;
	pNode prev = cur->_prev;
	newnode->_prev = prev;
	prev->_next = newnode;
	newnode->_next = cur;
	cur->_prev = newnode;
}

//2、填充n个值
void insert(pNode pos, size_t n, const T& val)
{
	assert(pos);
	while (n--)
	{
		insert(pos, val);//复用前面的insert
		pos = pos->_prev;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

尾删

//pop_back函数
void pop_back()
{
	assert(_head != _head->_next);
	pNode tail = _head->_prev;
	pNode last = tail->_prev;
	delete tail;
	last->_next = _head;
	_head->_prev = last;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

头删

//pop_front函数
void pop_front()
{
	assert(_head != _head->_next);
	pNode first = _head->_next;
	pNode newfirst = first->_next;
	delete first;
	_head->_next = newfirst;
	newfirst->_prev = _head;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

erase函数

//erase函数
pNode erase(pNode pos)
{
	assert(_head != _head->_next);
	pNode prev = pos->_prev;
	pNode next = pos->_next;
	delete pos;
	prev->_next = next;
	next->_prev = prev;
	return next;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

clear函数

//clear函数
void clear()
{
	//如果只有一个头节点,不需要动
	assert(_head->_next == _head);
	pNode cur = _head->_next;
	while (cur != _head)
	{
		pNode next = cur->_next;
		pop_back();//复用尾删
		cur = next;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

析构函数

//析构函数
~list()
{
	pNode cur = _head->_next;
	while (cur != _head)
	{
		pNode next = cur->_next;
		delete cur;
		cur = next;
	}
	delete _head;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号