C++ 标准库(STL)系列教程, 序列容器 list 与 forward_list 部分。
CPP 序列容器快速入门(4) list 与 forward_list
引言
序列容器是C++标准模板库(STL)中的重要组成部分,它们允许我们按顺序存储和访问元素。这些容器为开发者提供了灵活且高效的数据结构,用于处理各种复杂的编程任务。
其中,list 允许在序列的任何位置快速进行插入和删除操作。与 vector 和 deque 不同,list 并不是通过连续的内存块来存储元素,而是采用链式存储结构。这种结构使得 list 在插入和删除元素时无需移动其他元素,因此效率更高。然而,由于链式存储的特性,std::list 的随机访问性能相对较差,不如 vector 和 deque.
在本入门教程中,我们将详细探讨 list 容器的使用方法,帮助读者快速掌握这一基本而实用的工具。
list
list 是一个容器,它支持在容器的任何位置进行常数时间的元素插入和移除操作。这个容器并不支持快速的随机访问,通常实现为双向链表。与 forward_list 相比,list 提供了双向迭代能力,但在空间效率上稍逊一筹。
在 list 内部或在不同 list 对象之间添加、移除和移动元素时,并不会导致已存在的迭代器或引用失效。 迭代器只有在对应元素被显式删除时才会变得无效。
这种特性使得 list 在需要对元素进行频繁插入和删除操作的场景中非常有用,因为它能够保持操作的效率,同时保持对容器内其他元素的稳定引用。
list 上的常见操作复杂度(效率)如下:
- 随机访问——常数——与到 list 首尾的距离成线性 O(n)。
- 插入或移除元素——均摊常数 O(1)。
隐式定义的成员函数
构造函数
: 初始化容器析构函数
: 销毁容器的每个元素operator=
: 赋值运算符assign
: 将值赋给容器
1 |
|
元素访问
对于元素访问,list 和 deque vector array 的接口差距很大。
由于不支持随机访问,因此没有接口 at
operator[]
, 只有 front
和 back
。
那我们怎么访问中间元素呢?我们可以使用迭代器获取中间元素,并对迭代器解引用获取对应的值。
front
: 访问第一个元素返回到容器首元素的引用。 在空容器上对
front
的调用是未定义的。back
: 访问最后一个元素。返回到容器中最后一个元素的引用 在空容器上对
back
的调用是未定义的。
1 |
|
迭代器
对于迭代器接口,list 和 deque vector array 的接口是一致的。
begin
cbegin
: 返回指向起始的迭代器,c 前缀代表 const 不可修改(下同)返回指向 容器 首元素的迭代器。 如果 容器 为空,那么返回的迭代器等于 end()。
end
cend
: 返回指向末尾的迭代器返回指向 容器 末元素后一元素的迭代器。 此元素表现为占位符;试图访问它导致未定义行为。
rbegin
crbegin
: 返回指向起始的逆向迭代器返回指向逆向的 容器 的首元素的逆向迭代器。它对应非逆向 容器 的末元素。 如果 容器 为空,那么返回的迭代器等于 rend()。
rend
crend
: 返回指向末尾的逆向迭代器返回指向逆向的 容器 末元素后一元素的逆向迭代器。它对应非逆向 容器 首元素的前一元素。此元素表现为占位符,试图访问它导致未定义行为。
1 |
|
容量
list 的容量接口与 array , deque 是一致的:
empty
: 检查容器是否为空size
: 返回元素数max_size
: 返回可容纳的最大元素数
由于 list 每次添加都会新申请空间,没有预分配的空间浪费,因此没有 shrink_to_fit
。
1 |
|
修改器
list 的修改器接口与 deque 是一致的:
clear
: 清除内容从容器擦除所有元素。此调用后 size() 返回零。
使任何指代所含元素的引用、指针和迭代器失效。 任何尾后迭代器保持有效。
insert
: 插入元素插入元素到容器中的指定位置。
- 在 pos 前插入 value; 返回指向被插入 value 的迭代器。
- 在 pos 前插入 value 的 count 个副本; 返回指向首个被插入元素的迭代器, count == 0 时返回 pos。
- 在 pos 前插入来自迭代器范围 [first, last) 的元素; 返回指向首个被插入元素的迭代器, first == last 时返回 pos。
- 在 pos 前插入来自 initializer_list ilist 的元素
没有引用和迭代器会失效。
emplace
: 原位构造元素在紧接 pos 之前的位置向容器插入新元素;返回指向被安置的元素的迭代器。
迭代器失效原理与 insert 相同。
erase
: 擦除元素从容器擦除指定的元素。
- 移除位于 pos 的元素; 返回最后移除元素之后的迭代器,如果 pos 指代末元素,那么返回 end() 迭代器。
- 移除范围 [first, last) 中的元素; 返回最后移除元素之后的迭代器,如果在移除前 last == end(),那么返回更新的 end() 迭代器。如果范围 [first, last) 为空,那么返回 last。
擦除中间元素,指向被擦除元素的迭代器和引用会失效。其他引用和迭代器不受影响。
push_back
: 将元素添加到容器末尾追加给定元素 value 到容器尾。
- 对于常量引用,初始化新元素为 value 的副本。
- 对于右值引用,移动 value 进新元素。
没有引用和迭代器会失效。
emplace_back
: 在容器末尾原位构造元素添加新元素到容器尾。元素通过 std::allocator_traits::construct 构造,通常用放置式 new 于容器所提供的位置原位构造元素。 迭代器失效原理与 push_back 相同。
pop_back
移除末元素移除容器的末元素。 在空容器上调用 pop_back 是未定义行为。 指向被擦除元素的迭代器和引用会失效。
push_front
: 插入元素到容器起始前附给定元素 value 到容器起始。 没有引用和迭代器会失效。
emplace_front
: 在容器头部原位构造元素插入新元素到容器开头。通过 std::allocator_traits::construct 构造元素,通常用放置式 new 在容器所提供的位置原位构造元素。 没有引用和迭代器会失效。
pop_front
: 移除首元素移除容器首元素。若容器中无元素,则行为未定义。 指向被擦除元素的迭代器和引用会失效。
resize
: 改变存储元素的个数重设容器大小以容纳 count 个元素。 如果当前大小大于 count,那么减小容器到它的前 count 个元素。 如果当前大小小于 count,追加额外的默认插入的元素/ value 的副本。
swap
: 交换内容将内容以及容量与 other 的交换。不在单独的元素上调用任何移动、复制或交换操作。 所有迭代器和引用仍然有效。在操作后,未指明保有此容器中 end() 值的迭代器指代此容器还是另一容器。
注意与 array 的区别:array 会交换所有的元素; list 的实质是交换指针。
1 |
|
操作
merge
: 合并两个有序列表如果 other 与 *this 指代同一对象,那么什么也不做。 否则,将 other 合并到 *this。两个链表都应有序。用 operator< 或 comp 比较元素。 不复制元素,并且在操作后容器 other 会变为空。此操作是稳定的:对于两个链表中的等价元素,来自 *this 的元素始终在来自 other 的元素之前,并且不更改 *this 和 other 的等价元素顺序。 迭代器和引用不会失效。指向或指代从 other 移走的元素的指针、引用和迭代器将不再涉及 other,而是会指代 *this 中的相同元素。
splice
: 从另一个 list 中移动元素从一个 list 转移元素给另一个。 不复制或移动元素,仅对链表结点的内部指针进行重指向。 没有迭代器或引用会失效,指向被移动元素的迭代器保持有效,但现在指代到 *this 中,而不是到 other 中。
- 从 other 转移所有元素到 *this 中。元素被插入到 pos 指向的元素之前。操作后容器 other 变为空。
- 从 other 转移 it 指向的元素到 *this。元素被插入到 pos 指向的元素之前。
- 从 other 转移范围 [first, last) 中的元素到 *this。元素被插入到 pos 指向的元素之前。
remove
remove_if
:remove:移除所有等于 value 的元素。用 operator== 或 二元谓词 p 比较元素 remove_if:移除所有满足特定标准的元素。 只有指向被移除元素的迭代器和引用会失效。
reverse
: 反转元素的顺序逆转容器中的元素顺序。迭代器和引用不会失效。
unique
: 删除连续的重复元素从容器移除所有相继的的重复元素。用 operator== 或 二元谓词 p 比较元素,只留下相等元素组中的第一个元素。 只有到被移除元素的迭代器和引用会失效。 如果对应的的比较器没有建立等价关系,那么行为未定义。
sort
: 对元素进行排序排序元素,并保持等价元素的顺序。不会导致迭代器和引用失效。 用 operator< 或 comp 比较元素。因此默认升序
1 |
|
至此常见的 list 相关用法已经介绍完毕了,接下来会尝试代码实现这个容器(敬请期待)。
forward_list
forward_list 是 list 的简化版,通常实现是单链表。 它的接口和 list 基本一致,只是少了需要双指针才能实现的接口,在此只进行简要介绍,主要说明 forward_list 与 list 的区别,建议通过 list 来理解forward_list.
因为少了指向上一个节点的指针,容器其实保存的是头节点。
对于迭代器: 迭代器中所有和 rbegin 类似的反向迭代器接口都不支持。
对于函数: 函数中所有和 back 相关的接口都不支持。
对于基础的元素修改接口,insert
emplace
erase
分别对应 insert_after
emplace_after
erase_after
.
其他的和 list 理解上就没什么区别了。