0
点赞
收藏
分享

微信扫一扫

C++初阶:list的使用和模拟实现

关于list可以先看一下这个文档:list文档

一.list的介绍和使用

1.1 list的介绍

list实际上就是链表,是带头双向循环链表。

1.2 list的使用

list的使用跟我们以前用C语言实现时的一样。push,pop,insert等等。

1.2.1list的构造

list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的 元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)

用[first, last)区间中的元素构造

list

list<int> l1;                         // 构造空的l1
list<int> l2(4, 100);                 // l2中放4个值为100的元素
list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3
list<int> l4(l3);                    // 用l3拷贝构造l4

上面只是C++98的版本,在C++11中也支持这样构造:

list<int> l5 = { 1,2,3,4,5 };

 1.2.2迭代器的使用

因为是带头循环双向链表,所以begin是第一个元素,end是最后一个元素的下一个节点(哨兵位节点)。而rbegin和rend就刚好是它们两个的相反位置。

//这里用数组的元素来初始化l
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
// 使用正向迭代器正向list中的元素
// list<int>::iterator it = l.begin();   // C++98中语法
auto it = l.begin();                     // C++11之后推荐写法
while (it != l.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;

// 使用反向迭代器逆向打印list中的元素
// list<int>::reverse_iterator rit = l.rbegin();
auto rit = l.rbegin();
while (rit != l.rend())
{
	cout << *rit << " ";
	++rit;
}
cout << endl;
return 0;

打印出来的实际上就是正向和逆向。

1.2.3 capacity

关于capacity的重要的就两个

empty判空,不为空返回true,为空返回false
size返回有效节点个数
	list<int> l;
	if (l.empty())
	{
		cout << "l为空" << endl;
	}
	l = { 1,2,3,4,5 };
	cout << l.size() << endl;

1.2.4 access

front返回list中第一个节点值的引用
back

返回list中最后一个节点值的引用

1.2.5modify

push_back

尾插

pop_back尾删
push_front头插
pop_back头删
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素
void PrintList(const list<int>& l)
{
	for (auto& it : l)
	{
		cout << it << " ";
	}
	cout << endl;
}
int main()
{

	int array[] = { 1, 2, 3 };
	list<int> L(array, array + sizeof(array) / sizeof(array[0]));

	// 在list的尾部插入4,头部插入0
	L.push_back(4);
	L.push_front(0);
	PrintList(L);//0 1 2 3 4

	// 删除list尾部节点和头部节点
	L.pop_back();
	L.pop_front();
	PrintList(L);//1 2 3

	// 获取链表中第二个节点
	auto pos = ++L.begin();
	cout << *pos << endl;//2

	// 在pos前插入值为4的元素
	L.insert(pos, 4);
	PrintList(L);// 1 4 2 3

	// 在pos前插入5个值为5的元素
	L.insert(pos, 5, 5);
	PrintList(L);//1 4 5 5 5 5 5 2 3

	// 在pos前插入[v.begin(), v.end)区间中的元素
	vector<int> v{ 7, 8, 9 };
	L.insert(pos, v.begin(), v.end());
	PrintList(L);//1 4 5 5 5 5 5 7 8 9 2 3

	// 删除pos位置上的元素
	L.erase(pos);
	PrintList(L);//1 4 5 5 5 5 5 7 8 9 3

	// 删除list中[begin, end)区间中的元素,即删除list中的所有元素
	L.erase(L.begin(), L.end());
	PrintList(L);//  没有元素不再打印


	list<int> l2 = { 1,2,3 };
	L.swap(l2);
	PrintList(L);//1 2 3
	PrintList(l2);//   没有元素不打印

	// 将l2中的元素清空
	l2.clear();
	cout << l2.size() << endl;//0
	return 0;
}

 1.2.6迭代器失效

迭代器失效即迭代器所指向的节点的无 效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入 时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭 代器,其他迭代器不会受到影响。

下面就是一个典型的迭代器失效问题:

int main()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
		l.erase(it);
		++it;
	}

	return 0;
}

要想解决上面的问题,只就要更新一下it就行了.

int main()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
		it = l.erase(it);
		//++it;
	}

	return 0;
}

二.list的模拟实现

关于list的模拟实现因为结构与前面的string和vector完全不同,所以在进行模拟实现的时候要注意它们之间的差异,最主要和最难的一点就是它的迭代器如何实现实现。因为我们要对它进行封装用一个范围for就可以实行这两种结构的遍历。

2.1迭代器的实现

它的结构在空间上不是连续的,所以我们不能直接以原生指针进行封装。

先把节点的结构写出来:

	template <class T>
	struct ListNode
	{
		ListNode(const T& val = T())
			: _prev(nullptr)
			, _next(nullptr)
			, _val(val)
		{}

		ListNode<T>* _prev;//指向前一个节点
		ListNode<T>* _next;//指向后一个节点
		T _val;
	};

因为节点的地址都不是连续的,所以我们需要重载关于节点的++,--等内容。比如++就要重载为跳到下一个节点。这一点与顺序表是不同的。

	template<class T,class Ref,class Ptr>
	class ListIterator
	{
	public:
		typedef ListNode<T> Node;
		typedef ListIterator<T,Ref,Ptr> Self;
	public:
		//这里的重定义是为了实现反向迭代器
		typedef Ref Ref;
		typedef Ptr Ptr;
		Node* _node;
		//构造函数
		ListIterator(Node* node=nullptr)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_val;//返回的是数值的引用
		}
		Ptr operator->()
		{
			return &_node->_val;//返回的是地址
		}
		Self& operator++()//前置++
		{
			_node = _node->_next;
			return *this;//this指针指向的_node移动
		}
		Self operator++(int)//后置++
		{
			Self tmp(*this);//构造函数
			_node = _node->_next;
			return tmp;
		}
		Self& operator--()//前置--
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator--(int)//后置--
		{
			Self temp(*this);
			_node = _node->_prev;
			return temp;
		}
		bool operator!=(const Self& l) const
		{
			return _node != l._node;
		}
		bool operator==(const Self& l) const
		{
			return _node == l._node;
		}
	};

注意: 

然后就是反向迭代器:

	template<class Iterator>
	class ReverseListIterator
	{
		// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量
		// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
		// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
	public:
		typedef typename Iterator::Ref Ref;
		typedef typename Iterator::Ptr Ptr;
		typedef ReverseListIterator<Iterator> Self;//类型是正向迭代器类型
		Iterator _it;//用正向迭代器创建

		ReverseListIterator(Iterator it)
			:_it(it)//初始化
		{}

		//
		
		Ref operator*()
		{
			Iterator temp(_it);//用_it初始化对象temp
			--temp;//调用正向迭代器的前置--,因为rbegin的位置在哨兵位,没有数据,但我们在外面使用时认为是有数据的
			return *temp;//调用正向迭代器的operator*()
		}

		Ptr operator->()
		{
			return &(operator*());//与上面同理,需要调整一下位置,最开始指向哨兵位的上一个节点
		}

		//
		// 迭代器支持移动
		Self& operator++()
		{
			--_it;//因为是反向的,所以这里是--
			return *this;//返回的是自身迭代器的引用
		}
		
		Self operator++(int)
		{
			Self temp(*this);
			--_it;
			return temp;//与上面同理
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}

		Self operator--(int)
		{
			Self temp(*this);
			++_it;
			return temp;
		}

		//
		// 迭代器支持比较
		bool operator!=(const Self& l)const
		{
			return _it != l._it;
		}

		bool operator==(const Self& l)const
		{
			return _it != l._it;
		}

	};

对于反向迭代器需要注意的一点就是反向迭代器是在正向迭代器的基础上实现的,里面用到的--和++等都是用的正向迭代器的。然后就是因为这是双向循环带头链表,关于最后一个位置的处理要注意一下。

最后关于迭代器的就是迭代器的正式实现:
 

//迭代器实现
iterator begin()
{
	iterator it(_head->_next);
	return it;
	//return iterator(_head->_next);//用的都是匿名对象
}
iterator end()
{
	return iterator(_head);
}

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

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

reverse_iterator rbegin()
{
	return reverse_iterator(end());
}

reverse_iterator rend()
{
	return reverse_iterator(begin());
}

const_reverse_iterator rbegin()const
{
	return const_reverse_iterator(end());
}

const_reverse_iterator rend()const
{
	return const_reverse_iterator(begin());
}

到这里才算是真正完成了迭代器。

 2.2模拟实现list的准备

为了更清晰更清晰的实现list,我们需要清晰的知道各个名字都代表什么。还有里面的成员变量。

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

	typedef ReverseListIterator<iterator> reverse_iterator;
	typedef ReverseListIterator<const_iterator> const_reverse_iterator;
private:
	void CreateHead()
	{
		_head = new Node;
		_head->_prev = _head;
		_head->_next = _head;
	}
	Node* _head;
};

2.3modify

这里主要是对数据的插入删除,insert  erase  等等。

// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
	Node* pNewNode = new Node(val);
	Node* pCur = pos._node;
	// 先将新节点插入
	pNewNode->_prev = pCur->_prev;
	pNewNode->_next = pCur;
	pNewNode->_prev->_next = pNewNode;
	pCur->_prev = pNewNode;
	return iterator(pNewNode);
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
	// 找到待删除的节点
	Node* pDel = pos._node;
	Node* pRet = pDel->_next;

	// 将该节点从链表中拆下来并删除
	pDel->_prev->_next = pDel->_next;
	pDel->_next->_prev = pDel->_prev;
	delete pDel;

	return iterator(pRet);
}
void clear()
{
	Node* cur = _head->_next;

	// 采用头删除删除
	while (cur != _head)
	{
		_head->_next = cur->_next;
		delete cur;
		cur = _head->_next;
	}

	_head->_next = _head->_prev = _head;
}
void resize(size_t newsize, const T& data = T())
{
	size_t oldsize = size();
	if (newsize <= oldsize)
	{
		// 有效元素个数减少到newsize
		while (newsize < oldsize)
		{
			pop_back();
			oldsize--;
		}
	}
	else
	{
		while (oldsize < newsize)
		{
			push_back(data);
			oldsize++;
		}
	}
}
void swap(list<T>& l)
{
	std::swap(_head, l._head);
}
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());
}	

2.4capacity

bool empty()const
{
	return _head->_next == _head;
}
// List的容量相关
size_t size()const
{
	Node* cur = _head->_next;
	size_t count = 0;
	while (cur != _head)
	{
		count++;
		cur = cur->_next;
	}

	return count;
}

2.5access

链表不支持[]。

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

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

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

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

2.6list的构造

因为这里要频繁用到前面的东西,所以就把它放在了最后。

// List的构造
list()
{
	CreateHead();
}

list(int n, const T& value = T())
{
	CreateHead();
	for (int i = 0; i < n; ++i)
		push_back(value);
}

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

list(const list<T>& l)
{
	CreateHead();

	// 用l中的元素构造临时的temp,然后与当前对象交换
	list<T> temp(l.begin(), l.end());
	this->swap(temp);
}

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

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

到这里关于list的就分享完了,如有错误还请多多指出。

举报

相关推荐

0 条评论