一、list基础介绍与使用
list是STL中的序列式容器,底层为带头结点的双向 循环链表,其接口丰富,核心需掌握基础使用方法,再深入底层原理以实现扩展。
1.1 list的构造
提供四种核心构造函数,满足不同初始化需求:
|-----------------------------------------------------------|-----------------------------|
| 构造函数 | 接口说明 |
| list (size_type n, const value_type& val = value_type()) | 构造包含n个值为val的元素的list |
| list() | 构造空的list |
| list (const list& x) | 拷贝构造函数,用已有list初始化新list |
| list (InputIterator first, InputIterator last) | 用[first, last)区间内的元素构造list |
1.2 list迭代器的使用
迭代器可暂理解为指向list节点的指针,分正向和反向两类,核心是掌握其移动规则:
|---------------|----------------------------------------|
| 函数声明 | 接口说明 |
| begin + end | begin返回首元素迭代器,end返回尾元素下一个位置迭代器(正向) |
| rbegin + rend | rbegin对应end位置,rend对应begin位置(反向) |
关键注意:
-
正向迭代器(begin/end):++操作,迭代器向后移动;
-
反向迭代器(rbegin/rend):++操作,迭代器向前移动。
1.3 list容量操作
仅提供两个核心容量查询接口,简单易用:
|-------|----------------------------|
| 函数声明 | 接口说明 |
| empty | 检测list是否为空,空返回true,否则false |
| size | 返回list中有效节点的个数 |
1.4 list元素访问
仅支持首尾元素的直接访问,不支持随机访问(底层链表特性):
|-------|---------------|
| 函数声明 | 接口说明 |
| front | 返回首节点值的引用 |
| back | 返回尾节点值的引用 |
1.5 list修改操作(modifiers)
提供增删、插入、交换、清空等核心修改接口,是list使用的重点:
|------------|-----------------------|
| 函数声明 | 接口说明 |
| push_front | 首元素前插入值为val的元素 |
| pop_front | 删除第一个元素 |
| push_back | 尾部插入值为val的元素 |
| pop_back | 删除最后一个元素 |
| insert | 在position位置插入值为val的元素 |
| erase | 删除position位置的元素 |
| swap | 交换两个list中的所有元素 |
| clear | 清空list中的所有有效元素 |
1.6 list迭代器失效
核心结论 :插入操作不会导致迭代器失效,仅删除操作 会让指向被删除节点的迭代器失效,其他迭代器不受影响(链表节点独立,删除仅影响当前节点)。
错误与正确示例
-
错误:erase后直接++失效的迭代器,迭代器指向已释放节点
cppauto it = l.begin(); while (it != l.end()) { l.erase(it); ++it; // 错误:it已失效 } -
正确:利用后置++,先传参再移动迭代器;或接收erase返回值
cppauto it = l.begin(); while (it != l.end()) { l.erase(it++); // 推荐 // 等价:it = l.erase(it); }
二、list的模拟实现
模拟实现的核心是掌握底层双向循环链表结构,以及接口的逻辑实现,重点为反向 迭代器 的实现。
2.1 基础模拟实现
需先熟悉list底层带头结点的双向循环链表结构,再按接口功能依次实现构造、迭代器、增删查改、容量查询等接口,核心是保证链表节点的双向指向和循环特性。
2.2 反向迭代器的模拟实现
反向迭代器的核心逻辑:反向迭代器的++ = 正向迭代器的--,反向迭代器的-- = 正向迭代器的++ ,因此可借助正向迭代器做包装实现,无需重新编写底层逻辑。
核心实现要点
-
反向迭代器类模板接收正向迭代器作为模板参数,内部封装一个正向迭代器成员;
-
使用
typename明确迭代器的关联类型(Ref/Ptr),避免编译器解析歧义; -
重载
*/->实现元素访问,重载++/--实现迭代器移动,重载==/!=实现迭代器比较; -
解引用
*时,需先将正向迭代器向前移动一位,再返回值(匹配反向迭代器的遍历逻辑)。
核心代码框架
cpp
template<class Iterator>
class ReverseListIterator {
public:
typedef typename Iterator::Ref Ref;
typedef typename Iterator::Ptr Ptr;
typedef ReverseListIterator<Iterator> Self;
ReverseListIterator(Iterator it): _it(it){}
// 元素访问
Ref operator*(){
Iterator temp(_it);
--temp;
return *temp;
}
Ptr operator->(){ return &(operator*());}
// 迭代器移动
Self& operator++(){--_it; return *this;}
Self operator++(int){Self temp(*this); --_it; return temp;}
Self& operator--(){++_it; return *this;}
Self operator--(int){Self temp(*this); ++_it; return temp;}
// 迭代器比较
bool operator!=(const Self& l)const {return _it != l._it;}
bool operator==(const Self& l)const {return _it == l._it;}
private:
Iterator _it; // 封装正向迭代器
};
三、list与vector的对比
vector和list是STL核心序列式容器,底层结构的差异导致二者特性、效率、应用场景截然不同,是学习的重点区分点。
|--------|----------------------------------------------|-----------------------------|
| 对比维度 | vector | list |
| 底层结构 | 动态顺序表,一段连续的内存空间 | 带头结点的双向循环链表 |
| 随机访问 | 支持,访问元素效率O(1) | 不支持,访问元素效率O(N) |
| 插入/删除 | 任意位置效率低,需搬移元素(O(N));插入可能增容(开辟新空间+拷贝+释旧),效率更低 | 任意位置效率高,无需搬移元素(O(1)) |
| 空间利用率 | 连续空间,无内存碎片,空间/缓存利用率高 | 节点动态开辟,小节点易造成内存碎片,空间/缓存利用率低 |
| 迭代器本质 | 原生态指针 | 对节点指针的封装 |
| 迭代器失效 | 插入:所有迭代器失效(可能增容);删除:当前迭代器失效,需重新赋值 | 插入:无迭代器失效;删除:仅指向被删节点的迭代器失效 |
| 核心应用场景 | 需高效存储、支持随机访问,对插入/删除效率要求低 | 存在大量插入/删除操作,对随机访问无要求 |
四、核心学习总结
-
list的底层特性决定其核心优势是任意位置的插入/删除效率高 ,劣势是不支持 随机访问,需与vector做好场景区分;
-
迭代器是list使用的关键,需牢记正向/反向迭代器的移动规则 和迭代器失效的场景及解决方法;
-
模拟实现的重点是反向迭代器的包装思想,借助正向迭代器实现反向迭代器,体现代码的复用性;
-
实际开发中,根据需求选择容器:需随机访问用vector,需大量增删用list。