前言:在 C++ 中,list 是基于双向链表实现的容器,其迭代器失效情况相对简单,主要与删除操作相关,插入操作一般不会导致迭代器失效 。
目录
一、迭代器失效问题
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
1. 删除操作(erase)
当使用 list 的 erase 成员函数删除元素时,仅指向被删除节点的迭代器会失效,其他迭代器不受影响。
核心原理:list 的底层是双向链表,节点通过指针连接。
删除一个节点时,只会断开该节点与前后节点的链接,其他节点的位置和指针不受影响。
因此,只有指向被删除节点的迭代器会因为所指节点被销毁而失效,指向其他节点的迭代器仍能正常使用。
list<int> mylist = {1, 2, 3, 4};
auto it = mylist.begin(); // it 指向 1
mylist.erase(it); // 删除 1,it 失效
// 此时,指向 2、3、4 的迭代器仍有效
auto it2 = mylist.begin(); // it2 指向 2,可正常使用
2. 插入操作(insert、push_front、push_back 等)
由于 list 是链表结构,插入新节点时,只需调整相邻节点的指针,不会移动其他节点的位置
因此,插入操作不会导致任何迭代器失效(包括指向插入位置附近节点的迭代器)
list<int> mylist = {1, 3, 4};
auto it = mylist.begin();
++it; // it 指向 3
// 在 3 前插入 2,it 仍指向 3(节点 3 未被移动,指针未变)
mylist.insert(it, 2);
// 遍历结果:1 2 3 4,所有迭代器(包括原来指向 3 的 it)都有效
3. 清空操作 (clear)
clear 会删除 list 中所有元素,所有指向该 list 元素的迭代器都会失效(因为没有元素可指向了)调用 clear 后,若再使用之前的迭代器,会导致未定义行为
4. 赋值 / 交换操作 (assign、swap 等)
assign:会替换 list 的内容,原 list 所有元素被删除,原迭代器全部失效,需重新获取新 list 的迭代器。
swap:交换两个 list 的内容后,原 list 的迭代器会指向另一个 list 的元素(逻辑上 “转移” 了指向),若原 list 被销毁或内容改变,需注意迭代器的有效性。
#include <iostream>
#include <list>
using namespace std;
// 错误示例:迭代器失效问题
void TestListIterator01()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
auto it = lt.begin();
while (it != lt.end())
{
lt.erase(it);
++it; //未定义行为:对失效迭代器进行递增操作
//错误分析:
// erase会删除it指向的节点,并使it失效
// 失效的迭代器不能再进行++操作
}
}
// 正确示例:处理迭代器失效
void TestListIterator02()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
auto it = lt.begin();
while (it != lt.end())
{
/*-------方法1:利用后置++的特性-------*/
//lt.erase(it++);
//正确分析:
// it++ 返回旧值(当前有效迭代器)
// erase删除旧值指向的节点
// it 被递增为下一个有效节点
/*-------方法2:显式接收erase返回的迭代器-------*/
//it = lt.erase(it); // erase返回下一个有效迭代器
}
}
int main()
{
//TestListIterator01();
TestListIterator02();
return 0;
}
二、反向迭代器实现问题
通过前面的例子可知:
反向迭代器的 ++ 操作,对应正向迭代器的 -- 操作
反向迭代器的 -- 操作,对应正向迭代器的 ++ 操作
基于此,反向迭代器的实现可借助正向迭代器来完成,具体来说:反向迭代器内部可包含一个正向迭代器,通过对该正向迭代器的接口进行封装、调整逻辑,就能实现反向迭代器的功能 。
//反向迭代器适配器:将正向迭代器转换为反向迭代器
template<class Iterator>
class ReverseListIterator
{
private:
Iterator _it; //底层维护的正向迭代器
public:
/*--------------------------第一部分:定义类型别名--------------------------*/
//1.重命名“list正向迭代器中的引用类型”:Iterator::Ref ---> Ref
typedef typename Iterator::Ref Ref;
//2.重命名“list正向迭代器的指针类型”:Iterator::Ptr ---> Ptr
typedef typename Iterator::Ptr Ptr;
//3.重命名“list反向迭代器”的类型:ReverseListIterator ---> Self
typedef ReverseListIterator<Iterator> Self;
//注意:
//1.此处typename的作用是明确告诉编译器,Ref是“Iterator类中的类型”,而不是“静态成员变量”
//2.否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
//3.因为静态成员变量也是按照 “类名::静态成员变量名” 的方式访问的
/*--------------------------第二部分:定义成员变量--------------------------*/
//1.实现:“有参数构造函数”
ReverseListIterator(Iterator it)
: _it(it) //用正向迭代器初始化反向迭代器
{}
//2.实现:“*运算符的重载函数”
Ref operator*() //注意:返回当前位置前一个元素的引用(反向迭代器的特性)
{
//1.创建临时迭代器
Iterator temp(_it);
//2.前移一位(注意:这是正向迭代器的--)
--temp;
//3.返回临时迭代器的解引用
return *temp;
}
//3.实现:“->运算符的重载函数”
Ptr operator->() //注意:返回当前位置前一个元素的指针
{
return &(operator*()); //复用解引用运算符,取地址
}
//4.实现:“前置++运算符的重载函数”
Self& operator++() //(反向迭代器递增 = 正向迭代器递减)
{
--_it; //注意:调整底层正向迭代器向前移动
return *this;
}
//5.实现:“前置--运算符的重载函数”
Self& operator--() //(反向迭代器递减 = 正向迭代器递增)
{
++_it; //注意:调整底层正向迭代器向后移动
return *this;
}
//6.实现:“后置++运算符的重载函数”
Self operator++(int)
{
Self temp(*this);
--_it; //注意:(底层正向迭代器递减)
return temp;
}
//7.实现:“后置--运算符的重载函数”
Self operator--(int)
{
Self temp(*this);
++_it; //注意:(底层正向迭代器递增)
return temp;
}
//8.实现:“==运算符的重载函数”
bool operator==(const Self& lt) const
{
return _it == lt._it; //比较底层正向迭代器
}
//8.实现:“!=运算符的重载函数”
bool operator!=(const Self& lt) const
{
return _it != lt._it; //比较底层正向迭代器
}
};
三、vector和list的选择问题
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

拓展:list迭代器的模板参数为什么是三个?
template <class T, class Ref, class Ptr>
struct list_iterator
{
/*--------------------定义类型别名--------------------*/
//1.重命名“list节点”的类型:list_node<T> ---> Node
typedef list_node<T> Node;
//2.重命名“list迭代器”的类型:list_iterator<T,Ref,Ptr> ---> Self
typedef list_iterator<T, Ref, Ptr> Self;
/*--------------------定义成员变量--------------------*/
Node* _node; //迭代器内部存储的节点指针,指向当前位置
/*--------------------定义成员函数--------------------*/
//1.实现:“有参构造函数”
list_iterator(Node* node) :
_node(node)
{}
}
在 C++ 标准库中,list容器的迭代器模板参数设计为三个(通常是:T, Ref, Ptr),主要是为了支持 常量迭代器 和 非常量迭代器 的区分。
这种设计允许通过模板参数的不同组合,让同一套迭代器代码同时服务于普通迭代器和常量迭代器,避免代码冗余。
为了分离值类型、引用类型和指针类型
普通迭代器:需要返回T&和T*(可修改元素)
常量迭代器:需要返回const T&和const T*(不可修改元素)
通过三个模板参数,可以灵活指定引用和指针的常量性:Ref = T&,Ptr = T* 时,是普通迭代器。
Ref = const T&,Ptr = const T* 时,是常量迭代器
1599

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



