1、forward_list与list
forward_list 是在 C++11 中新增的一种容器类型,其底层实现为带有一个头节点的单向链表。使用该容器需包含头文件 #include<forward_list>,主要特性包括:
- 内存效率高,仅维护单向指针链接,占用空间较小。
- 在链表头部进行插入或删除操作时性能优异,时间复杂度为 O(1)。
以下是一个简单的 forward_list 使用示例:
#include <iostream>
using namespace std;
#include <forward_list>
int main()
{
forward_list<int> fl = {1, 2, 3};
// 在头部插入元素
fl.push_front(0);
// 遍历并输出
for (int num : fl)
{
cout << num << " ";
}
cout << endl;
return 0;
}
list 则是 C++ 标准库中基于带头结点的双向循环链表所实现的容器,使用前需要引入头文件 #include<list>,具备如下特点:
- 支持动态调整大小,可随元素增减自动扩展或收缩。
- 在任意位置插入和删除元素的效率都很高,接近 O(1) 时间复杂度。
- 提供双向迭代器,允许从前向后和从后向前两种遍历方式。
以下是 list 的基本用法演示:
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> myList = {10, 20, 30, 40, 50};
// 在头部插入元素
myList.push_front(5);
// 在尾部插入元素
myList.push_back(60);
// 遍历并输出
for (int num : myList) {
cout << num << " ";
}
cout << endl;
// 删除指定元素
myList.remove(30);
// 再次遍历输出
for (int num : myList) {
cout << num << " ";
}
cout << endl;
return 0;
}
虽然 forward_list 和 list 都用于存储线性序列数据,但二者存在显著差异:
- 结构差异:forward_list 采用单向链表结构,而 list 使用双向链表。
- 迭代器支持:forward_list 仅支持前向迭代器;list 支持双向迭代器,灵活性更高。
- 内存开销:通常情况下,forward_list 比 list 更节省内存空间。
实际开发中,若应用场景只需要单向遍历且对内存敏感,则推荐使用 forward_list;若需要频繁地双向访问或任意位置修改,list 是更优选择。
由于 forward_list 在日常编程中使用频率较低,本文不再深入展开,如有需要可查阅官方文档获取详细信息。接下来将重点介绍 list 容器的基本接口与使用方法。
2、list的接口
#
list 提供了丰富的成员函数接口,学习过程中建议结合标准文档参考 —— list的用法。
2.1 list的初始化与销毁
作为类类型,list 对象通过调用构造函数完成初始化。常见的构造方式如下所示:
具体代码示例如下:
void Test3()
{
// 默认构造函数
list<int> numbers1;
cout << "默认构造: ";
for (const auto& num : numbers1) {
cout << num << " ";
}
cout << endl;
// n个val构造
list<int> numbers2(5, 10);
cout << "n个val构造: ";
for (const auto& num : numbers2) {
cout << num << " ";
}
cout << endl;
int arr[] = {1, 2, 3};
// 通过vector的迭代器初始化
list<int> numbers3(arr, arr + sizeof(arr) / sizeof(arr[0]));
cout << "迭代器区间构造: ";
for (const auto& num : numbers3) {
cout << num << " ";
}
cout << endl;
list<int> numbers4 = {4, 5, 6};
// 拷贝构造
list<int> numbers5(numbers4);
cout << "拷贝构造: ";
for (const auto& num : numbers5) {
cout << num << " ";
}
cout << endl;
numbers1 = numbers2;
// 赋值重载
cout << "赋值重载: ";
for (const auto& num : numbers1) {
cout << num << " ";
}
cout << endl;
}
由于 list 具备自动资源管理机制,在对象生命周期结束时会自动调用析构函数释放资源,因此无需手动显式调用销毁函数。
2.2 list的迭代器
list 支持多种类型的迭代器,可用于正向和反向遍历。
begin() 与 end() 的功能说明如下:
函数声明:
iterator begin();
const_iterator begin() const;
作用:返回指向列表首个元素的迭代器。
返回值:非 const 对象返回 iterator 类型,const 对象则返回 const_iterator 类型。
函数声明:
iterator end();
const_iterator end() const;
作用:返回指向最后一个元素后一个位置(即头节点)的迭代器。
返回值:普通对象返回 iterator,const 对象返回 const_iterator。
示例代码:
void Test1()
{
list<int> numbers = { 1, 2, 3, 4, 5 };
list<int>::iterator it = numbers.begin();
cout << "First element: " << *it << endl;
while (it != numbers.end())
{
cout << *it <<" ";
++it;
}
// 注意:这里不能直接解引用it,因为此时它指向的是头节点
cout << endl;
}
rbegin() 与 rend() 用于反向遍历:
函数声明:
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
作用:返回指向最后一个元素的反向迭代器起始位置。
返回值:普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator。
函数声明:
reverse_iterator rend();
const_reverse_iterator rend() const;
作用:返回指向第一个元素前一位置(即头节点)的反向迭代器终点。
返回值:同上。
反向迭代器使用示例:
void Test2()
{
list<int> numbers = {1, 2, 3, 4, 5};
list<int>::reverse_iterator rit = numbers.rbegin();
cout << "Last element: " << *rit << endl;
while (rit!= numbers.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
2.3 list的容量操作
list 提供了一系列用于查询和控制容器容量的方法,常用接口如下表所示:
| 函数名称 | 功能描述 |
|---|---|
| size | 返回当前列表中元素的个数 |
| max_size | 获取容器理论上最多能容纳的元素数量 |
| empty | 判断列表是否为空,空则返回 true,否则返回 false |
| resize | 重新设定列表中元素的数量,若新大小大于原大小,则用默认值填充新增部分 |
| clear | 移除所有元素,使列表变为空 |
size 函数用于获取当前 list 中实际存储的元素总数。
max_size 返回系统限制下该 list 最大可容纳的元素数目。
void Test4()
{
list<int> numbers = {1, 2, 3, 4, 5};
cout << "Size of list: " << numbers.size() << endl;
cout << "Max size of list: " << numbers.max_size() << endl;
}
empty 可快速判断容器是否为空。
clear 能够彻底清空 list 中的所有内容。
void Test5()
{
list<int> numbers;
if (numbers.empty())
{
cout << "List is empty" << endl;
}
numbers = {1, 2, 3};
numbers.clear();
if (numbers.empty())
{
cout << "List is cleared and now empty" << endl;
}
}
resize 方法可以调整 list 的元素数量。
当调用 resize(n) 时,如果 n 大于当前元素数量,则会在末尾添加默认构造的元素以补足至 n 个。
当指定的大小 n 大于当前列表长度时,系统会在列表末尾补充相应数量的默认值元素,直至列表长度达到 n;若 n 小于当前列表长度,则会从列表尾部开始删除多余元素,使列表最终长度等于 n。
void Test6()
{
list<int> numbers = {1, 2, 3};
numbers.resize(5);
cout << "After resizing to 5: ";
for (auto& num : numbers)
{
cout << num << " ";
}
cout << endl;
numbers.resize(2);
cout << "After resizing to 2: ";
for (auto& num : numbers)
{
cout << num << " ";
}
cout << endl;
}
2.4 list 的访问操作
接下来我们介绍 list 中常用的访问函数:
| 函数名称 | 功能说明 |
|---|---|
| back | 获取并返回列表中最后一个元素 |
| front | 获取并返回列表中第一个元素 |
需要注意的是,list 不支持通过下标配合 [ ] 操作符进行元素访问。这是因为像 vector 这类容器底层采用连续内存空间,可以通过指针偏移快速定位元素;而 list 使用的是非连续的链式结构,访问第 n 个元素需要从头或尾逐个遍历,时间复杂度为 O(n)。尽管语法上可以实现 operator[ ],但由于性能代价过高,标准库并未提供该操作。
void Test7()
{
list<int> myList = {10, 20, 30}; // 创建一个包含元素的列表
// 输出列表的第一个元素
cout << "The front element is: " << myList.front() << endl;
// 输出列表的最后一个元素
cout << "The back element is: " << myList.back() << endl;
}
2.5 list 的修改操作
list 提供了多种用于修改内容的接口,种类较多。此处仅重点讲解常用方法,其余可查阅官方文档获取详细信息。以下是常见的 list 修改相关函数:
| 函数名称 | 功能描述 |
|---|---|
| push_back | 在列表尾部插入一个新元素 |
| emplace_back | 在列表尾部就地构造一个新元素 |
| push_front | 在列表头部插入一个新元素 |
| emplace_front | 在列表头部就地构造一个新元素 |
| pop_back | 移除列表最后一个元素 |
| pop_front | 移除列表第一个元素 |
| insert | 在指定位置前插入元素或元素序列 |
| erase | 删除指定位置或区间内的元素 |
| swap | 交换两个 list 容器的内容 |
push_back() 和 pop_back() 分别实现元素的尾部添加与删除操作。
void Test8()
{
list<int> myList; // 创建一个空列表
myList.push_back(10); // 在列表尾部添加元素 10
myList.push_back(20); // 在列表尾部添加元素 20
cout << "列表元素: ";
for (auto& num : myList) {
cout << num << " ";
}
cout << endl;
myList.pop_back(); // 删除列表尾部的元素
cout << "删除尾部元素后列表元素: ";
for (auto& num : myList) {
cout << num << " ";
}
cout << endl;
}
push_front() 和 pop_front() 则对应在列表头部进行插入和删除。
void Test9()
{
list<int> myList; // 创建一个空列表
myList.push_front(5); // 在列表头部添加元素 5
myList.push_front(3); // 在列表头部添加元素 3
cout << "列表元素: ";
for (auto& num : myList) {
cout << num << " ";
}
cout << endl;
myList.pop_front(); // 删除列表头部的元素
cout << "删除头部元素后列表元素: ";
for (auto& num : myList)
{
cout << num << " ";
}
cout << endl;
}
C++11 引入的 emplace 系列函数,其核心优势在于“就地构造”机制,避免了临时对象的生成以及后续的拷贝或移动开销,从而显著提升性能。
此外,emplace 系列还支持传递构造参数,适用于自定义类型的对象构建。
insert 与 erase 操作注意事项
在使用 insert() 向列表中插入元素时,若操作发生在某个迭代器所指向的位置之前或之后,原迭代器将失效。因此建议接收 insert() 的返回值,该返回值为指向新插入元素的迭代器,可用于后续操作。
void Test10()
{
list<int> myList = { 1, 2, 3 };
list<int>::iterator it = myList.begin();
it = myList.insert(it, 4); // 这里迭代器 it 失效
it = myList.insert(it, 5); // 这里迭代器 it 失效
for (auto& num : myList)
{
cout << num << " ";
}
cout << endl;
}
同理,在调用 erase() 删除元素后,指向被删元素的迭代器也会失效。此时应使用其返回值——即指向被删除元素下一位置的迭代器,以保证遍历等操作的连续性。
void Test11()
{
list<int> myList = { 1, 2, 3, 4, 5 };
list<int>::iterator it = myList.begin();
it = myList.erase(it); // 迭代器 it 失效
it = myList.erase(it); // 迭代器 it 失效
for (auto& num : myList)
{
cout << num << " ";
}
cout << endl;
}
list 中的 swap() 函数与 vector、string 中的 swap 类似,主要通过交换内部指针完成两个容器内容的互换,执行效率非常高。
void Test12()
{
list<int> list1 = { 1, 2, 3 };
list<int> list2 = { 4, 5, 6 };
cout << "交换之前:" << endl;
for (auto& num : list1)
{
cout << num << " ";
}
cout << endl;
for (auto& num : list2)
{
cout << num << " ";
}
cout << endl;
list1.swap(list2);
cout << "交换之前:" << endl;
for (auto& num : list1)
{
cout << num << " ";
}
cout << endl;
for (auto& num : list2)
{
cout << num << " ";
}
cout << endl;
}
2.6 list 的其他常用操作
以下是一些 list 中常见的附加功能函数:
| 函数名称 | 功能描述 |
|---|---|
| splice | 将一个列表中的元素转移到另一个列表中 |
| remove | 删除所有等于特定值的元素 |
| remove_if | 根据条件函数删除满足条件的元素 |
| unique | 去除相邻的重复元素 |
| merge | 合并两个已排序的列表为一个有序列表 |
| sort | 对列表内元素进行排序 |
| reverse | 反转列表中元素的顺序 |
splice() 函数允许将一个 list 中的部分或全部元素直接迁移到另一个 list 中,支持指定转移范围和插入位置,是一种高效且灵活的元素重组方式。
void Test13()
{
list<int> list1 = {1, 2, 3};
list<int> list2 = {4, 5};
// 将 list2 的元素转移到 list1 中
list1.splice(list1.end(), list2);
for (auto num : list1) {
cout << num << " ";
}
cout << endl;
}
remove 函数的作用类似于遍历整个列表,并对每一个匹配目标值的元素调用 erase 进行删除。
void Test14()
{
list<int> myList = {1, 2, 2, 3, 2};
// 移除值为 2 的元素
myList.remove(2);
for (auto num : myList) {
cout << num << " ";
}
cout << endl;
}
remove_if 是 remove 的扩展版本,支持传入函数指针或仿函数作为判断条件,实现更复杂的筛选逻辑。
bool isEven(int num) {
return num % 2 == 0;
}
void Test15() {
list<int> myList = {1, 2, 3, 4, 5, 6};
// 移除满足偶数条件的元素
myList.remove_if(isEven);
for (auto num : myList) {
cout << num << " ";
}
cout << endl;
}
unique() 函数用于清除相邻的重复元素,保留唯一的连续序列。需注意:它仅处理相邻重复项,并不会消除所有重复值(例如分散在不同位置的相同元素),如需完全去重,通常需先排序再调用 unique。
void Test16()
{
list<int> myList = {1, 2, 2, 3, 3, 3};
// 移除相邻的重复元素
myList.unique();
for (auto num : myList) {
cout << num << " ";
}
cout << endl;
}
merge() 函数用于将两个已排序的 list 合并为一个新的有序列表。合并过程会按照升序(或其他指定顺序)整合两个源列表中的元素。使用前提要求两个原始列表均已排好序,否则结果不可预期。
void Test17()
{
list<int> list1 = {1, 3, 5};
list<int> list2 = {2, 4, 6};
list1.sort();
list2.sort();
// 合并两个已排序的列表
list1.merge(list2);
for (auto num : list1) {
cout << num << " ";
}
cout << endl;
}
sort() 成员函数用于对 list 内部元素进行排序,默认按升序排列。值得注意的是,标准算法库中的 std::sort 要求随机访问迭代器,而 list 仅提供双向迭代器,因此无法使用 <algorithm> 中的全局 sort 函数,必须调用 list 自身提供的成员函数 sort()。
void Test18() {
list<int> myList = {3, 1, 4, 1, 5, 9, 2, 6, 5};
// 对列表进行排序
myList.sort();
for (auto num : myList) {
cout << num << " ";
}
cout << endl;
}
最后是 reverse() 函数,用于将列表中元素的顺序完全翻转。虽然算法库中也存在同名函数,但 list 自带的 reverse 成员函数更为高效,专为链表结构优化设计。
void Test19()
{
list<int> l2 = { 1,2,4,5 };
l2.reverse();//list中的reverse
reverse(l2.begin(), l2.end());//算法库中的reverse
for (auto& num : l2)
{
cout << num << " ";
}
}
3. 迭代器分类
根据功能强弱和操作特性,C++ 中的迭代器可分为以下几类:
迭代器的类型决定了其所能支持的算法操作。例如,标准算法库中的 sort 需要随机访问迭代器才能实现高效的排序逻辑,而 list 使用的是双向迭代器,不具备随机访问能力,因此不能直接使用该算法。
由于 sort 函数的底层实现基于快速排序,因此要求所使用的迭代器具备随机访问能力,并支持通过下标进行加减操作。正因如此,list 容器无法直接使用标准库中的 sort 算法,因为它所提供的迭代器不满足随机访问的条件。为此,list 类内部自行实现了一个专属的 sort 成员函数。
算法的设计前提是必须采用随机迭代器。

此外,在 C++ 标准库中,迭代器的分类采用了继承机制来表达不同类型之间的关系,即更具体的迭代器类型通过继承自较通用的基类来实现。这种设计体现了“子类是一种特殊的父类”的面向对象原则。



雷达卡


京公网安备 11010802022788号







