C++ | STL :list的介绍 | vector和list的区别和使用场景

12 篇文章 1 订阅

前言

紧跟上篇文章的vector,本文介绍list,感冒了状态不好,很多细节根本没有写上来,实际使用时还会与遇到很多问题。

还可通过这篇文章来了解list的简单概念。
STL【list的仿写】

list 的介绍

  1. list是序列容器,允许在序列中的任何位置执行固定O(1)时间的插入和删除操作,并在两个方向上进行迭代。
  2. list容器使用双链表实现;双链表将每个元素存储在不同的存储(内存)位置。每个节点通过next,prev指针链接成顺序表。
  3. list 与其他基本标准序列容器(array、vector和deque)相比,list 通常在容器内的任何位置插入、提取和移动元素(已经获得迭代器的情况下时间渐进复杂度O(1)) 。
  4. list 与其他序列容器(vector,array, deque)相比,list和forward _list(单链表实现)的主要缺点是它们不能通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置〈如开始或结束)迭代到该位置,需要线性时间开销。
  5. 存储密度低,list要使用一些额外的内存空间(next,prev)来保持与每个元素相关联(前后序的线性)的链接信息,从而导致存储小元素类型(int, short, char)的列表的存储密度低。

图示

空的list,只有一个头结点。
在这里插入图片描述
非空list:
在这里插入图片描述

list的使用

1.构造函数

int main(void)
{
	// 默认构造
	list<int> ilist1;
	// 构造10个值为20的结点
	list<int> ilist2(10, 20);
	// 构造十个默认结点
	list<int> ilist3(10);
	// 利用别的list的已知范围来初始化
	list<int> ilist4(ilist2.begin(), ilist2.end());
	// 利用一个list来初始化另一个
	list<int> ilist5(ilist4);

	// 初始化列表方案
	list<int> ilist6({ 12,23,34 });
	list<int> ilist7 = { 12,23,34 };
	list<int> ilist8{ 12,23,34 };
	// 利用已存在的list来直接赋值
	list<int> ilist9 = ilist6;

	return 0;
}

2.元素访问

通过迭代器访问,或通过基于范围的for循环;
不能使用下标访问和at()访问。

int main(void) {
	list<int> ilist1{ 12,23,34,45,56 };

	list<int>::iterator it = ilist1.begin();
	cout << *it << endl;

	for (auto& x : ilist1)
	{
		cout << x << endl;
	}

	return 0;
}

迭代器失效问题

在list中,迭代器失效和vector中并不同,vector由于插入时可能出现重新申请空间的问题,所以会导致迭代器指向的原始空间被析构,导致迭代器失效。

而list在插入元素时不会出现迭代器失效,原因:
list插入时是按照结点申请的,只需要把新节点链入list中,而原始数据并未出现重新申请空间的情况。

示例,迭代器it指向list的开始位置,头插后依然指向

int main(void) {
	list<int> ilist1{ 12,23,34,45,56 };

	list<int>::iterator it = ilist1.begin();
	cout << *it << endl;

	ilist1.push_front(10);
	cout << *it << endl;

	return 0;
}

在这里插入图片描述

但删除时会出现失效问题,这是显而易见的:

int main(void) {
	list<int> ilist1{ 12,23,34,45,56 };

	list<int>::iterator it = ilist1.begin();
	cout << *it << endl;

	ilist1.pop_front();
	cout << *it << endl;

	return 0;
}

在这里插入图片描述
在这里插入图片描述

删除元素时如何防止迭代器失效呢?
可以使用erase()函数,将迭代器传入进去,删除当前对象,饼返回迭代器的后继位置。但如果一直删除,删完所有结点之后也会失效。

int main(void) {
	list<int> ilist1{ 12,23,34,45,56 };

	list<int>::iterator it = ilist1.begin();
	cout << *it << endl;

	it = ilist1.erase(it);
	cout << *it << endl;

	return 0;
}

如图:初始状态-> 删除后
在这里插入图片描述
在这里插入图片描述

vector和list的区别

  1. 底层实现不同
    vector:连续存储的容器,是一个动态数组,在堆区上分配空间。
    list:动态双向链表,靠各个结点地址连接起来,在堆区分配空间。

  2. 空间利用率
    vector:连续空间,不易造成内存碎片。空间利用率高
    list:结点不连续,易造成内存碎片,小元素使结点密度低。空间利用率低。

  3. 查询元素
    vector:由于是连续空间,所以可以通过 iterator, operator[],find()来查询,时间复杂度为O(n),还可通过二分查询binary_serach(),时间复杂度O(log2n)。
    list:空间不连续,只能通过iterator, find()来查询,时间复杂度O(n)。

  4. 插入和删除
    vector:
    在末尾插入:1. 容量足够,push_back()时间复杂度为O(1).
    2.容量不足时,push_back()时间复杂度为O(n); 因为需要重新申请空间,再把原有数据拷贝过去。
    在中间插入:1.容量够时,需要插入后需要将后面数据后移。2.容量不够时,重新申请空间,再拷贝之前数据。
    insert(it, val):O(n);
    删除:尾删(pop_back()),O(1);其他地方O(n);
    erase(it): O(n);
    list:
    插入:需要申请内存,时间复杂度O(1),push_back(),push_front(),insert(it, val);
    删除:需要释放内存,时间复杂度O(1),pop_back(), pop_front()
    erase(it):O(n);

  5. 迭代器
    vector:
    随即迭代器,迭代器检查越界。支持 ++, – , +, += , > , < , == , !=
    list:
    双向迭代器,迭代器检查越界。支持 ++, --, ==, !=。

  6. 迭代器失效

vector:
插入和删除都会导致迭代器失效
list:
插入元素不会导致迭代器失效;删除元素使当前迭代器失效,不影响其他迭代器。

总结

在平时使用时,不能谈论某个容器的优缺点,因为每种容器都有它适合的场景,只能说哪种容器在哪种场合更实用。

  1. vector底层实现是数组; list是双向链表。
  2. vector支持随机访问,list不支持。
  3. vector是顺序内存,list不是。
  4. vector在中间节点进行插入删除会导致内存拷贝,list不会。
  5. vector一次性分配好内存,不够时才进行2倍扩容(或1.5倍);list每次插入新节点都会进行内存申请。
  6. vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。

什么时候该用vector? 什么时候该用list?

  1. 如果需要高效的随机存取,而不在乎插入和删除的效率(很少使用插入和删除操作)。选用vector。
  2. 如果需要大量的插入和删除的操作,随机存取很少使用。选用list。

end

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_索伦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值