1.内存存储方式的不同
vector是一段连续的地址存储数据,支持随机迭代器,可以使用下标加[]的操作来访问

list是多个小空间用节点指针进行连接,这种设计注定不支持随机迭代器和下标访问,因为地址不连续,但对于插入数据很高效

2.两者的迭代器原理(重点)
2.1迭代器的实现
vector迭代器
由于vector的地址是连续存储的,所以原生指针的逻辑即可满足迭代器的原理需求,比如说iterator++,iterator--,这些用指针就能实现,所以vector的迭代器可以大致分为两种实现方式
第一种:直接将原生指针使用typedef进行封装,封装为iterator
第二种:将迭代器实现为一个类,再在类里面重载iterator所需要的运算符,再封装为iterator
list迭代器
list是多个内存使用节点指针进行连接,多个数据之间地址不连续,所以无法支持下标访问,因为十分低效,使用一次就要遍历一次list,也就不支持+或-,不是随机迭代器
list的迭代器是指向每个listnode,但由于数据的内存不连续,所以传统的指针逻辑无法满足逻辑
因此list迭代器只能将他实现一个类,然后在类里面重载运算符来实现逻辑
例:
//简写iterator
template<class T>
struct ListIterator//因为迭代器在外部经常访问所以直接写成struct
{
typedef ListNode<T> node;//节点封装
//operator++()
iterator operator++()
{
_node = _node->next;
return *this;
}
node* _node;
}
2.2迭代器失效
我们先想一下什么时候迭代器会失效,可以大致分为两种情况:第一种是类似野指针,迭代器指向了一段未开辟的空间,第二种是迭代器指向了错误的位置,访问了错误的数据
所以迭代器失效可能会发生在insert,erase,扩容,缩容
总结一下,如果某个操作会改变容器的内容,那么就可能引起迭代器失效,或者是临时对象的迭代器
vector迭代器失效
根据上述分析,我们先看一下insert是否会引发迭代器失效,以尾插举例,如下图


对比图

容器内存改变,则引发迭代器失效
说完insert,那我们再看一下erase

大家发现如果不缩容,好像直接挪动数据,就直接将pos移到我的目标位置了,但是这样的代码是不可控的
给一个代码样例
void test01()
{
vector<int> v1{ 1,2,3,4,5 };
auto it1 = v1.begin();
while (it1 != v1.end())
{
if (*it1 % 2 == 0)
{
v1.erase(it1);//不更新迭代器
}
cout << *it1 << ' ';
++it1;
}
}
int main()
{
test01();
return 0;
}
如果是在vs下运行则会发生报错,vs进行了强制检查,必须更新迭代器

更新之后就可以运行

但是linux下并没有硬性规定,意思就是说,如果你不更新迭代器,上面的代码也是可以运行成功的

这种不可控的原因是由于代码并不是只由一个人编写,有的人就觉得规范一点好,有的人觉得只要能跑就好,而对于我们用户,代码的核心我们可能并不知道他到底是怎么写的,所以说,最好规范我们的代码,更新迭代器,这也是应该的,而且规范写代码可以增加代码的可读性
更新迭代器运行结果

再说一点,我们并不知道erase缩不缩容,因为C++没有规定,你缩容了也行,不缩容也行,只不过我们一般情况下不缩容,因为空间够,而且能满足逻辑
再看一下指向非法数据的情况
代码段:
void test02()
{
vector<int> v1{ 1,2,3,4,5 };
auto it1 = v1.begin();
auto it2 = v1.end();
--it2;
while (it1 != v1.end())
{
if (*it1 % 2 == 0)
{
it1 = v1.erase(it1);
}
cout << *it1 << ' ';
++it1;
}
cout << endl;
cout << "it2:" << *it2 << endl;
}
int main()
{
test02();
return 0;
}
vs下还是会强制检查,运行报错,但linux下不会,而且能访问到数据

linux下还是能跑,而且能访问数据

那扩容缩容也不用说了,上面的情况都包含到了
list迭代器失效
迭代器失效主要可能发生在容器扩容/缩容的时候,那我们先看list插入数据/删除数据会不会引起迭代器失效
1.insert

我们发现并不存在迭代器失效问题,但为了格式统一,还是建议更新迭代器,提升可读性
2.erase
如果是删除掉某个节点,而不进行迭代器更新,那显然会引起迭代器失效,让迭代器指向一块已经被释放的空间。

此时如果不更新迭代器,那么就会引发迭代器失效
3.排序问题
vector的内存存储是一块连续的空间,支持随机迭代器,支持[]访问,可以使用algorithm算法库排序,排序效率非常高
list的内存存储是多块空间通过节点指针连接,不支持随机迭代器,不能使用algorithm算法库排序,但list标准库提供了sort函数,不过十分鸡肋,效率十分低下,如果有两组相同的数据存在list中,第一组使用list标准库的sort,第二组先把list的数据拷贝到vector中,再使用algorithm算法库中的sort排序vector,再将vector的数据拷贝回list中,第二组的时间可以比第一组的时间少一半多
4.reserve/resize
由于vector的结构,所以vector是可以支持reserve的
但list不行,试想一下,如果list提供reserve,那他怎么分辨哨兵位?
但resize两个都可以支持
5.增删查改
这里只总结这两种容器的优缺点
vector的优点:
1.尾插、尾删十分高效
2.访问、修改十分高效
vector缺点:
1.只要不是尾插/尾删就十分低效,因为必须挪动数据
list优点:
1.无论什么位置插入都还可以,尾插虽然比不上vector,但除了尾插都比vector高效
2.无论什么位置删除都还可以,尾删比不上vector,但除了尾删都比vector高效
3、访问、修改十分低效,每次访问基本上都要遍历list
6.deque引入
就像马和驴,汽车和火车一样,马跑的快耐力没驴强,驴耐力好但跑的不快,汽车可以很快去近距离的地方,但跑长途得走走停停,火车可以去很远的地方,但如果路途不是特别远有点不合适
vector和list就是这样的关系,属于两个极端,于是乎,我们引入了一种新的容器deque,他继承了一些vector的优点,比如说访问效率比list高,也继承了一些list的优点,头插/头删效率比vector高
但也继承了vector和list的缺点,下面是deque的STL库的成员变量

deque的逻辑结构如下




1309

被折叠的 条评论
为什么被折叠?



