C++--8.list类

我们在上一节中了解了vector这个容器,但是它有一些缺点

vector缺点:

1.头部和中部插入删除效率低,O(N)因为需要挪动数据

2.插入数据空间不够需要增容,增容需要开辟空间,拷贝数据,释放旧空间,代价很大

优点:支持下标的随机访问,间接就很好地支持排序二分堆算法等

所以我们出现了list容器,可以弥补vector的一些缺陷

list优点:

1.list头部,中间插入不再需要挪动数据,效率高。O(1)

2.list插入数据是新增节点,不需要增容

缺点:

不支持随机访问

因为他们两个形成了很好的互补,所以在使用的时候是相辅相成的,其实也就是我们之间学习过的顺序表与链表

所以我们这一章来介绍一下list

list 的介绍及使用
 list 的深度剖析及模拟实现
 list vector 的对比

list的介绍及使用

list 的文档介绍
1. list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list 的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
4. 与其他的序列式容器相比 (array vector deque) list 通常在任意位置进行插入、移除元素的执行效率更好。
5. 与其他序列式容器相比, list forward_list 最大的缺陷是不支持任意位置的随机访问,比如:要访问 list的第6 个元素,必须从已知的位置 ( 比如头部或者尾部 ) 迭代到该位置,在这段位置上迭代需要线性的时间开销;list 还需要一些额外的空间,以保存每个节点的相关联信息 ( 对于存储类型较小元素的大 list 来说这可能是一个重要的因素)

 其实list对于我们而言就是之前学习过的带头双向循环链表

list的构造

 我们同vector与string一样,对ist用我们两种方式进行基础的遍历(list底层为链表,所以不支持[]遍历)

#include<iostream>
#include<list>
#include<Windows.h.>
using namespace std;
void print_list(const list<int>& lt)//只读迭代器
{
	list<int>::const_iterator it1 = lt.begin();
	while (it1 != lt.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
}
void test_list1()
{
	list<int> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	list<int>::iterator it1 = lt1.begin();//正常迭代器
	while (it1 != lt1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	list<int>lt2(lt1);//拷贝构造
	print_list(lt2);
	list<int>lt3;
	lt3.push_back(10);
	lt3.push_back(20);
	lt3.push_back(30);
	lt3.push_back(40);
	lt1 = lt3;//赋值
	//只要一个容器支持迭代器,就可以支持范围for
	for (auto e : lt1)//范围for遍历
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>::reverse_iterator rit = lt1.rbegin();//反向迭代器
	while (rit != lt1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}
//迭代器方向划分:单项(forword_list),双向(list),随机(vector)
//使用属性:正向,反向+const
int main()
{

	test_list1();
	system("pause");
	return 0;
}

接下来我们对list的常用接口进行测试

正常尾插,头插,尾删头删

 插入:无法直接随机插入,因为底层是链表,所以需要借助迭代器找到位置随后插入,并且完成插入之后pos指针还会留在插入位置

 顺逆序打印

 注意:这里有与vector一样的迭代器失效的问题,只是list中的是当删除后it就失去了指向,会变为随机值,erase会返回删除后的下一个节点解决了这个问题

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

 list的深度剖析及模拟实现

#pragma once

namespace wxy
{
	template<class T>//创建模板
	struct __list_node//定义节点结构体
	{
		__list_node<T>*_next;
		__list_node<T>*_prev;
		T _data;

		__list_node(const T& x=T())//结点初始化列表
			: _data(x)
		    , _next(nullptr)
			, _prev(nullptr)
		{}
	};
	// __list_iterator<T, T&, T*>   -> iterator
	// __list_iterator<T, const T&, const T*>   -> const_iterator
	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)
		{}
		//*it
		Ref operator*()//重载*it,返回引用(节点中存的值)
		{
			return _node->_data;//节点数据
		}
		Ptr operator->()//重载->,对指针的解引用,返回节点
		{
			return &_node->_data;
		}
		//++it,前++
		Self& operator++()//迭代器中++重载,完成指向下一节点的操作
		{
			_node = _node->_next;
			return *this;//返回指向后的节点
		}

		//it++后++
		Self operator++(int)
		{
			Self tmp(*this);//将+之前的结点存在tmp中
			//_node = _node->_next;
			++(*this);//将指针指向下一节点

			return tmp;//返回+之前的结点
		}
		//it--
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		Self operator--(int)
		{
			__list_iterator<T> tmp(*this);
			//_node = _node->_prev;
			--(*this);
			
			return tmp;
		}
		//it!=end()
		bool operator!=(const Self& it)//重载!=
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
	};
	//以上都为迭代器与节点的定义,以及其相关运算符的重载
	template<class T>
	class list//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()//对begin的调用函数
		{
			return iterator(_head->_next);//返回的是头结点的下一节点,因为在双向循环链表当中第一个节点为头节点下一个
		}
		const_iterator begin()const//对只读迭代器中的begin调用
		{
			return const_iterator(_head->_next);
		}
		iterator end()//对end进行调用
		{
			return iterator(_head);//返回的是头节点,因为在双向循环链表中,头节点就是遍历的最后一个节点
		}
		const_iterator end()const//只读迭代器中end调用
		{
			return const_iterator(_head);
		}
		//带头双向循环链表
		list()//list构造函数
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list(const list<T>& lt)//list拷贝构造
		{
			_head = new Node;//指针赋初值
			_head->_next = _head;
			_head->_prev = _head;

			/*const_iterator it = lt.begin();//迭代器方式依次遍历赋值
			while (it != lt.end())
			{
				push_back(*it);
				++it;
			}*/
			for (auto e : lt)//范围for赋值,本质还是迭代器
			{
				push_back(e);
			}
		}
		//lt1=lt3
		/*list<T> operator=(const list<T>& lt)//重载=(拷贝构造并赋值)
		{
			if (this != &lt)//lt1=lt1
			{
				clear();//清理lt1
				for (auto e : lt)//范围for将lt3中的值依次赋给lt1
				{
					push_back(e);
				}
			}
			return *this;//返回lt1
		}*/
		//lt1=lt3
		list<T>& operator=(list<T> lt)//拷贝构造现代写法,传入lt3,会引起拷贝构造出一份lt3
		{
			swap(_head, lt._head);//直接交换本来的lt1(this)与拷贝构造出来的lt3,相当于将头指针指向交换
			return *this;//返回lt1(也就是拷贝出的lt3)
		}//原先的lt1(this),指向了拷贝出的lt3,而存有lt1内容的lt3则在出作用域时完成析构
		~list()//析构函数
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		void clear()//清理函数
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);//依次迭代销毁
			}
		}
		void push_back(const T& x)//尾插,同我们的双向带头链表
		{
			/*Node* tail = _head->_prev;
			Node* newnode = new Node(x);

			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;*/

			insert(end(), x);//在end(头结点前)插入
		}
		void pop_back()
		{
			//erase(iterator(_head->prev));//释放迭代器头结点的前一个
			erase(--end());
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void pop_front()
		{
			erase(begin());
		}
		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;
			newnode->_next = cur;
			cur->_prev = newnode;

		}
		void erase(iterator pos)//删除
		{
			assert(pos != end());//头节点所在的迭代器是end

			Node* cur = pos._node;
			Node*prev = cur->_prev;
			Node* next = cur->_next;
			delete cur;

			prev->_next = next;
			next->_prev = prev;
		}
	private:
		Node* _head;
	};

上面便是我们的list的关键接口的实现,构造了一个简易的架构,剖析了在stl中list是如何实现的,下面我们对上面的情况进行一些测试

 经典的插入输出

 插入删除并且调用只读迭代器进行输出

 拷贝构造以及=赋值

listvector的对比

 事实上,这两个容器的区别就是链表与顺序表的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值