迭代器失效和支持随机访问的容器总结

 创作活动

 迭代器失效:

  1. 顺序容器(如vectordequelist
    • vector
      • 插入操作
        • 当在vector中间或头部插入元素时,所有位于插入点之后的迭代器都会失效。这是因为vector的元素在内存中是连续存储的,插入元素可能会导致内存重新分配和元素的移动。例如,有一个vector<int> v = {1,2,3,4,5};,如果执行v.insert(v.begin() + 2, 6);(在索引为 2 的位置插入 6),那么从v.begin()+2之后的迭代器(包括指向 3、4、5 的迭代器)都将失效。
        • 如果插入操作没有导致内存重新分配(例如在vector的尾部插入元素,且当前容量足够),那么只有指向插入位置的迭代器会失效。
      • 删除操作
        • 当删除vector中的元素时,所有指向被删除元素及其之后元素的迭代器都会失效。例如,对于vector<int> v = {1,2,3,4,5};,如果执行v.erase(v.begin() + 2);(删除索引为 2 的元素),那么指向 3、4、5 的迭代器都将失效。
    • deque
      • 插入操作
        • deque中间插入元素时,指向插入位置及其之后元素的迭代器会失效。deque是双端队列,它的内部实现是分段存储的,插入操作可能会导致部分元素的重新排列。例如,在一个deque中间插入元素后,该位置之后的元素可能会被移动到新的段或者在当前段内重新排列。
      • 删除操作
        • 删除deque中的元素时,指向被删除元素及其之后元素的迭代器会失效。和插入操作类似,因为元素的删除可能会导致剩余元素的重新排列。
    • list
      • 插入和删除操作
        • 对于list,插入和删除操作只会使指向被插入或删除元素的迭代器失效。这是因为list是链表结构,元素在内存中不是连续存储的。插入或删除一个节点只影响该节点及其相邻节点的链接关系。例如,有一个list<int> l = {1,2,3,4,5};,如果执行l.insert(l.begin() + 2, 6);(在第二个位置插入 6),只有指向原来第二个位置元素(2)的迭代器可能会失效(如果之前有保存该迭代器的话),其他迭代器仍然有效。同样,对于l.erase(l.begin() + 2);(删除第二个位置的元素),只有指向被删除元素的迭代器失效。
  2. 关联容器(如mapsetmultimapmultiset
    • 插入操作
      • 在关联容器中插入元素不会使已有的迭代器失效。这是因为关联容器(如红黑树实现的mapset)在插入新元素时,通过调整树的节点链接来保持平衡,而不会改变已有节点的地址。例如,对于一个map<int, int> m;,插入新的键值对后,之前获取的指向其他键值对的迭代器仍然有效。
    • 删除操作
      • 当删除关联容器中的元素时,只有指向被删除元素的迭代器会失效。因为删除一个节点只会影响该节点本身,而不会改变其他节点的地址。例如,在一个set<int> s = {1,2,3,4,5};中,执行s.erase(s.find(3));(删除元素 3),只有指向 3 的迭代器失效,指向 1、2、4、5 的迭代器仍然有效。
  3. 无序关联容器(如unordered_mapunordered_set等)
    • 插入操作
      • 一般情况下,插入元素不会使已有的迭代器失效。但是如果插入操作导致容器的哈希表进行了重新哈希(例如,当容器的负载因子超过一定阈值时),所有的迭代器都会失效。这种情况相对较少,但在使用时需要注意。
    • 删除操作
      • 删除元素时,只有指向被删除元素的迭代器会失效。和关联容器类似,因为删除一个桶中的元素通常不会影响其他桶中的元素和迭代器。例如,在一个unordered_set<int> us = {1,2,3,4,5};中,执行us.erase(3);,只有指向 3 的迭代器失效。

 支持随机访问的容器:

  1. 支持随机访问的容器
    • vector(动态数组)
      • 存储结构
        • vector在内存中是连续存储的元素序列,就像一个普通的数组。例如,vector<int> v = {1,2,3,4,5};在内存中是依次排列的,每个元素紧挨着前一个元素。这种连续存储方式使得它能够通过计算偏移量来快速访问元素。
      • 访问方式
        • 可以使用[]运算符或者at()函数进行随机访问。例如,要访问v中的第三个元素,可以使用v[2]或者v.at(2)(注意at()函数会进行边界检查,[]运算符不会)。这种访问方式的时间复杂度是,因为可以直接通过计算元素的内存地址来获取元素。比如,对于一个vector对象v,第n个元素的地址可以通过&v[0]+n来计算(在符合语法规则的情况下)。
      • 迭代器支持
        • vector的迭代器是随机访问迭代器,支持算术运算。例如,可以通过it + n或者it - n来移动迭代器n个位置,其中itvector的迭代器。这使得在遍历vector或者在其中进行元素查找等操作时更加灵活。比如,可以用vector<int>::iterator it = v.begin(); it += 3;来快速定位到v中的第四个元素(索引为 3)。
    • array(固定大小数组)
      • 存储结构
        • vector类似,array也是连续存储元素的容器。不过,array的大小在编译时就确定了,不能动态改变。例如,array<int, 5> a = {1,2,3,4,5};的大小始终是 5 个元素,并且在内存中是连续排列的。
      • 访问方式
        • 同样可以使用[]运算符或者at()函数进行随机访问,访问时间复杂度也是。例如,a[2]可以获取a中的第三个元素。因为其存储结构的连续性,计算元素地址的方式和vector类似。
      • 迭代器支持
        • array的迭代器也是随机访问迭代器,支持算术运算。例如,可以像操作vector的迭代器一样,通过it + n或者it - n来移动array的迭代器n个位置,方便对array中的元素进行遍历和操作。
  2. 不支持随机访问的容器
    • list(双向链表)
      • 存储结构
        • list是由一系列节点组成的双向链表。每个节点包含数据元素以及指向前一个节点和后一个节点的指针。例如,对于list<int> l = {1,2,3,4,5};,元素 1 的节点包含指向元素 2 节点的后向指针和nullptr(如果是头节点)的前向指针,元素 2 的节点包含指向元素 1 节点的前向指针和指向元素 3 节点的后向指针,以此类推。
      • 访问方式
        • 不能像vectorarray那样直接通过索引来访问元素。要访问list中的元素,通常需要从链表的头部或者某个已知位置开始,逐个节点地遍历。例如,要找到l中的第三个元素,可能需要从l.begin()开始,使用迭代器++操作符移动两次才能找到。这种访问方式的时间复杂度是,其中n是要访问的元素位置与起始位置的距离。
      • 迭代器支持
        • list的迭代器是双向迭代器,不支持像随机访问迭代器那样的算术运算。只能通过++--操作符来向前或者向后移动迭代器。例如,给定一个list<int>的迭代器it,不能使用it + 3这样的操作来移动迭代器,而只能通过it++; it++; it++;这样的方式来移动 3 个位置。
    • forward_list(单向链表)
      • 存储结构
        • forward_list是单向链表,每个节点只包含一个指向下一个节点的指针。例如,对于forward_list<int> fl = {1,2,3,4,5};,元素 1 的节点包含指向元素 2 节点的指针,元素 2 的节点包含指向元素 3 节点的指针,以此类推。
      • 访问方式
        • list类似,不能通过索引直接访问元素,需要从链表头部开始逐个节点地遍历。由于是单向链表,只能向前遍历。访问特定元素的时间复杂度也是,其中n是要访问的元素位置与起始位置的距离。
      • 迭代器支持
        • forward_list的迭代器是前向迭代器,只能使用++操作符向前移动迭代器。不能进行--操作,也不支持算术运算。例如,要遍历forward_list,只能通过for (auto it = fl.begin(); it!= fl.end(); ++it)这样的方式来进行。
    • mapset及其关联容器(基于树结构)
      • 存储结构
        • map为例,通常是基于红黑树实现的。map中的元素是以键值对的形式存储的,键是唯一的,并且按照一定的顺序(默认是升序)排列在树中。例如,对于map<int, int> m = { {1,10},{2,20},{3,30}};,这些键值对在红黑树中按照键的大小进行排序。
      • 访问方式
        • 不能通过索引进行随机访问。一般通过键来查找元素,时间复杂度是,其中n是容器中的元素数量。例如,要查找m中键为 2 的元素,可以使用m.find(2),它会在红黑树中进行对数时间复杂度的查找。
      • 迭代器支持
        • 迭代器是双向迭代器(对于mapset),可以使用++--操作符来遍历容器中的元素。但是不支持算术运算,因为元素在树中的存储位置不是像数组那样连续的,不能通过简单的偏移量来计算元素位置。例如,在遍历map时,迭代器会按照键的顺序依次访问元素。

猜你喜欢

转载自blog.csdn.net/2401_83603768/article/details/143222599