C++ 8:list容器详解与实战指南

list文档介绍

https://cplusplus.com/reference/list/list/?kw=list

list的底层结构

cpp 复制代码
template<class T>
class list {//list本体的结构
	typedef listNode<T> Node;
	typedef list_Iterator<T> iterator;//迭代器
public:
    //函数实现
    ......
private:
    Node* head;//头节点
    size_t size;//链表的有效结点个数(不包含哨兵位)
};

list的使用

list构造及迭代器使用

此处我们可以将迭代器认为是指针,这个指针指向链表的某个节点

|------------------------------------------------------------------------------------|-------------------------------------------------------------|
| 构造函数(constructor) | 接口说明 |
| 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 |

|-------------|--------------------------------------------------------------------------------------------------------------------------------|
| 函数声明 | 接口说明 |
| begin+end | 返回第一个元素的迭代器 + 返回最后一个元素下一个位置的迭代器 |
| rbegin+rend | 返回第一个元素的 reverse_iterator, end 位置返回最后一个元素下一个位 置的 reverse_iterator, begin 位置 |

cpp 复制代码
list<int>it(5, 1);
for (auto e : it)
	cout << e << " ";
list<int>it1(it.begin(), it.end());
for (auto e : it1)
	cout << e << " ";
list<int>it2(2);
list<int>it3(it2);//拷贝构造
for (auto e : it2)
	cout << e << " ";

list的空间问题

|-----------|-----------------------------------------------------------|
| empty | 检测 list 是否为空,是返回 true ,否则返回 false |
| size | 返回 list 中有效节点的个数 |

cpp 复制代码
list<int> v(5,1);
list.empty();//判断是否为空
list.size();

list的元素获取

|-----------|----------------------------------|
| 函数声明 | 接口说明 |
| front | 返回 list 的第一个节点中值的引用 |
| back | 返回 list 的最后一个节点中值的引用 |

cpp 复制代码
list<int> v(5,1);
list.front();//获取第一个元素1
list.back();//获取最后一个元素1

list的修改

|------------|------------------------------------------------|
| 函数声明 | 接口说明 |
| push_front | list 首元素前插入值为 val 的元素 |
| pop_front | 删除 list 中第一个元素 |
| push_back | list 尾部插入值为 val 的元素 |
| pop_back | 删除 list 中最后一个元素 |
| insert | list position 位置中插入值为 val的元素 |
| erase | 删除 list position 位置的元素 |
| swap | 交换两个 list 中的元素 |
| clear | 清空 list 中的有效元素 |

cpp 复制代码
list<int>lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(5);
lt.push_back(5);
for (auto e : lt)
	cout << e << " ";
lt.emplace_back((2, 3));
for (auto e : lt)
	cout << e << " ";
reverse(lt.begin(), lt.end());
for (auto e : lt)
	cout << e << " ";
//去重  前提已经排好序
lt.unique();
//排序
lt.sort();
//删除数据
lt.erase(lt.begin());//只支持迭代器,如果要删除中间的数据,只能先遍历
//插入
lt.insert(lt.begin(), 4);
lt.insert(lt.begin(), 4,3);//插入4个3的数
list<int>it;
it.insert(it.begin(),lt.begin(), lt.end());//迭代器插入
lt.remove(5);//删除一个值,但不需要迭代器,有就删除,没有不影响
it.merge(lt);//合并两个链表,按升序

迭代器失效问题

与前面的vector与string一样,链表也会出现迭代器失效问题

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

cpp 复制代码
void TestListIterator1()
{
    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;
    }
}
    // 改正
void TestListIterator()
{
    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())
    {
        l.erase(it++); // it = l.erase(it);
    }
}

list的模拟实现

节点结构的基本模板

cpp 复制代码
template<typename T>
struct ListNode {
    T _data;
    ListNode<T>* _prev;
    ListNode<T>* _next;
    ListNode(const T& val = T())
//初始化列表
        : _data(val)
        , _prev(nullptr)
        , _next(nullptr)
    {}
};

list功能的基本结构

cpp 复制代码
template <class T>
class list {
	typedef list_node<T> Node;//
public:
	/*typedef list_iterator<T> iterator;
	typedef list_cnost_iterator<T> const_iterator;*/
	typedef list_iterator <T,T&,T*> iterator;
	typedef list_iterator <T,const T&,const T*> const_iterator;
	void clear()
	{
		auto node = begin();
		while (node != end())
		{
			node = erase(node); //就是这里,你没有接受下一个迭代器,导致使用失效的迭代器造成死循环了
		}
	}
	void emInit()
	{
		_head = new Node;
		_head->_next = _head->_prev = _head;
	}
	list(const list<T>& x)
	{
		emInit();
		for (auto& e : x)
			push_back(e);
	}	
	void swap( list<T> x)
	{
		std::swap(_head, x._head);
		std::swap(_size, x._size);
	}
	list<T>&operator=(const list<T> x)

	{

		swap(x);
		return *this;
	}

	~list()
	{
		clear(); //应该是这里边死循环了-所以析构不完,函数没返回
		delete _head;
		_head = nullptr;
	}
	iterator begin()
	{
		//还需要一个构造函数构造
		////有名对象
		//iterator it(_head->_next);
		//return it;
		////匿名对象
		//retutn iterator(_head->_next);
		//隐式转换
		return _head->_next;
	}
	const_iterator begin()const
	{
		return _head->_next;
	}
	const_iterator end()const
	{
		return _head;
	}
	iterator end()
	{
		//return _head->_prev;
		return _head;
	}
	iterator erase(iterator pos)
	{
		assert(pos != end());
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;
		prev->_next = next;
		next->_prev = prev;
		--_size;
		//清理完了,应该往下一个位置走
		return next;
	}
	void pop_front()
	{
		erase(begin());
	}
	void pop_back()
	{
		erase( end());
	}
	list(){
		_head = new Node(T());//在堆上开辟一个NOde类型的节点,并将该节点的地址赋给_head指针  有隐式this指针
		_head->_next = _head;
		_head->_prev = _head;
	}
	
	
	
	bool empty()
	{
		return _size == 0;
	}
	void push_back(const T& x)
	{
		/*Node* newnode = new Node(x);
		Node* tail = _head->_prev;
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_next = newnode;*/

		insert(end(), x);

	}
	
	void push_front(const T& x) {
		insert(begin(), x);
	}
	void insert(iterator pos, const T& x)
	{
		Node* cur = pos._node;
		Node* prev = cur->_prev;

		Node* newnode = new Node(x);

		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;
		prev->_next=newnode;
		++_size;
		
	}
	
	
private:
	Node* _head;
	size_t _size;
};

迭代器的模拟实现

cpp 复制代码
template <class T,class Ref, class Ptr>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T,Ref,Ptr> Self;//iterator 其实还是节点的指针,但直接使用不可,所以进行封装,运算符重载
	Node*_node;
	//开辟一个空间,来接收传的节点,进行下面begin end的构造
	//指针走默认构造 值拷贝不会有问题
	list_iterator(Node*node)
		:_node(node)
	{ }
	Ref operator*()
	{
		return _node->_data;
	}
	
	
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
	//返回当前数据的data地址
	
	
	Ptr operator->()
	{
		return &(_node->_data);
	}
	
	Self& operator++()
		{
		_node = _node->_next;
		return *this;
	}
	
	
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	Self operator++(int)
	{
		Self temp(*this);//提前保存原来的节点
		_node = _node->_next;
		return temp;
	}
	Self operator--(int)
	{
		Self temp(*this);//提前保存原来的节点
		_node = _node->_prev;
		return temp;
	}
	
};

list与vector的对比

|-------------------------|----------------------------|----------------------|
| | vector | list |
| | 动态顺序表,连续空间 | 带头循环链表,非连续空间 |
| 随机访问 | 支持,时间复杂度可为O(1) | 不支持,访问时间复杂度为O(N) |
| 插入与删除 | 效率低,需要搬运元素,且涉及插入时空间不足,需要扩容 | 不需要搬运元素,时间复杂度可为O (1) |
| 空间利用率 | 物理上为连续空间,缓存利用率高 | 非连续空间,利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针进行封装, 但还是节点的地址 |
| 迭代器失效 | 插入与删除都有可能 | 仅删除 |
| 使用场景 | 需要高效储存,大量连续访问时 | 需要大量插入数据, 非大量随机访问 |

vector插入失效原因:可能会扩容,导致旧的迭代器指向的空间被释放

删除失效原因:删除后,vector内部要搬运元素,会导致原指向被删元素或其后方元素的迭代器,会变成 "悬空" 状态

list删除失效原因:底层是双向链表(不用搬动数据),删除元素只会导致指向被删除元素的迭代器失效,其他元素的不会

性能优化

使用vector时,可提前使用reserve进行开辟空间,避免频繁开辟空间,影响效率

使用list时,要善用list的sort与merge等函数

相关推荐
UpgradeLink2 小时前
Electron项目使用electron-updater与UpgradeLink接入参考
开发语言·前端·javascript·笔记·electron·用户运营
做cv的小昊2 小时前
计算机图形学:【Games101】学习笔记04——着色(光照与基本着色模型,着色频率、图形管线、纹理映射)
笔记·学习·3d·图形渲染·光照贴图·计算机图形学
小尧嵌入式2 小时前
C++11线程库的使用(上)
c语言·开发语言·c++·qt·算法
石像鬼₧魂石2 小时前
Hydra 弱口令爆破的详细命令模板
linux·windows·学习·ubuntu
m0_616188492 小时前
JS文件批量下载并打包成ZIP的功能
开发语言·javascript·ecmascript
CodeAmaz2 小时前
mysql乐观锁和悲观锁
数据库·mysql·乐观锁·悲观锁
蓝色汪洋2 小时前
luogu填坑
开发语言·c++·算法
咖啡の猫2 小时前
Python列表推导式
开发语言·python
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于PHP的高校心理测评系统的设计与实现为例,包含答辩的问题和答案
开发语言·php