
🏝️专栏: 【C++修炼之路】
🌅主页: f狐o狸x
"追风赶月莫停留,平芜尽处是春山"
目录
[3.1. 空构造(默认构造)](#3.1. 空构造(默认构造))
[3.2. 填充构造](#3.2. 填充构造)
[3.3. 迭代器范围构造](#3.3. 迭代器范围构造)
[3.4. 拷贝构造](#3.4. 拷贝构造)
[4.1. 常用迭代器](#4.1. 常用迭代器)
[4.2. const迭代器](#4.2. const迭代器)
[4.3. 迭代器遍历示例](#4.3. 迭代器遍历示例)
[5.1. 元素插入(push_back、push_front、insert)](#5.1. 元素插入(push_back、push_front、insert))
[5.2. 元素删除(pop_back、pop_front、erase、clear)](#5.2. 元素删除(pop_back、pop_front、erase、clear))
[5.3. 元素访问与修改(front、back)](#5.3. 元素访问与修改(front、back))
[5.4. 容器大小与判断(size、empty、resize)](#5.4. 容器大小与判断(size、empty、resize))
[5.5. 其他常用函数](#5.5. 其他常用函数)
[6.1. 迭代器失效问题](#6.1. 迭代器失效问题)
[6.2. 不可使用STL算法sort](#6.2. 不可使用STL算法sort)
[6.3. 与vector的对比选择](#6.3. 与vector的对比选择)
在C++ STL(标准模板库)中,list是一种基于双向链表实现的序列式容器,它与vector、deque并称为常用的线性容器,但因底层结构不同,具备独特的优势与适用场景。本文将从list的核心特性出发,逐步讲解其构造、迭代器、常用成员函数及实战用法,适合C++初学者快速上手。
一、list的核心特性
list的底层是双向链表,每个节点包含数据域、前驱指针和后继指针,节点之间通过指针关联,不占用连续的内存空间。这种结构决定了它的核心特性:
-
插入/删除高效:在list的任意位置(头部、尾部、中间)插入或删除元素时,仅需修改节点的指针指向,时间复杂度为O(1)(前提是已找到目标位置),无需像vector那样移动大量元素。
-
随机访问低效:无法通过下标[]直接访问元素,必须通过迭代器逐步遍历,访问第n个元素的时间复杂度为O(n),因为需要从头部或尾部逐个移动指针。
-
内存灵活:节点动态分配内存,无需提前预留空间,内存利用率较高,避免了vector扩容时的内存浪费。
-
支持双向遍历:提供双向迭代器(bidirectional iterator),可正向、反向遍历容器。
适用场景:适合频繁进行插入、删除操作,且无需频繁随机访问元素的场景,例如任务队列、频繁修改的链表结构等。
二、list的基础使用前提
使用list容器前,必须包含对应的头文件,并引入std命名空间(或显式使用std::list):
cpp
#include <list> // 必须包含的头文件 using namespace std;
// 简化写法,否则需写std::list
三、list的构造函数(4种常用方式)
list提供多种构造方式,可根据需求灵活选择,以下是最常用的4种:
3.1. 空构造(默认构造)
创建一个空的list,无任何元素,初始大小为0。
cpp
list<int> lst; // 空list,存储int类型元素
3.2. 填充构造
创建一个包含n个相同值的list,值可指定(默认值为元素类型的默认构造值,如int默认0)。
cpp
// 方式1:创建包含5个元素,每个元素值为10的
list list<int> lst1(5, 10);
// 方式2:创建包含3个元素,每个元素为int默认值0
list<int> lst2(3);
3.3. 迭代器范围构造
通过其他容器的迭代器范围,构造一个新的list(复制该范围的所有元素)。
cpp
vector<int> vec = {1,2,3,4,5};
// 复制vec的所有元素(从begin到end),构造list
list<int> lst3(vec.begin(), vec.end());
// 复制vec的前3个元素(从begin到begin+3)
list<int> lst4(vec.begin(), vec.begin()+3);
3.4. 拷贝构造
通过另一个list,复制其所有元素构造新的list。
cpp
list<int> lst5(5, 10); list<int> lst6(lst5);
// 拷贝lst5的所有元素,lst6与lst5完全一致
四、list的迭代器使用
list的迭代器是双向迭代器,支持++(正向移动)、--(反向移动)操作,但不支持+、-(如it+2)操作(区别于vector的随机访问迭代器)。常用迭代器分为4类:
4.1. 常用迭代器
-
begin():返回指向容器第一个元素的迭代器(非const,可修改元素)。
-
end():返回指向容器末尾(最后一个元素的下一个位置)的迭代器,不指向具体元素。
-
rbegin():返回反向迭代器,指向容器最后一个元素(正向遍历的逆序起点)。
-
rend():返回反向迭代器,指向容器第一个元素的前一个位置。
4.2. const迭代器
用于只读场景,防止通过迭代器修改元素,对应const_begin()、const_end()、const_rbegin()、const_rend()。
4.3. 迭代器遍历示例
cpp
#include <list>
#include <iostream> using namespace std;
int main()
{
list<int> lst = {1,2,3,4,5};
// 1. 正向遍历(非const迭代器)
cout << "正向遍历:";
for (list<int>::iterator it = lst.begin(); it != lst.end(); it++)
{
cout << *it << " "; // *it 获取迭代器指向的元素
}
cout << endl;
// 2. 反向遍历(反向迭代器)
cout << "反向遍历:";
for (list<int>::reverse_iterator it = lst.rbegin(); it != lst.rend(); it++)
{ cout << *it << " "; }
cout << endl;
// 3. const迭代器(只读)
cout << "const迭代器遍历(只读):";
for (list<int>::const_iterator it = lst.cbegin(); it != lst.cend(); it++)
{
// *it = 10; // 报错,const迭代器无法修改元素
cout << *it << " ";
}
cout << endl; return 0;
}
输出结果:

五、list的常用成员函数(核心)
list的成员函数主要用于元素的增、删、改、查及容器的基础操作,以下是高频使用的函数,搭配实例说明。
5.1. 元素插入(push_back、push_front、insert)
push_back(elem):在容器尾部插入一个元素elem,时间复杂度O(1)。
push_front(elem):在容器头部插入一个元素elem,时间复杂度O(1)(vector无此函数)。
insert(pos, elem):在迭代器pos指向的位置插入elem,返回指向插入元素的迭代器,时间复杂度O(1)。
insert(pos, n, elem):在pos位置插入n个elem元素。
insert(pos, beg, end):在pos位置插入另一个容器[beg, end)范围的元素。
cpp
list<int> lst;
// 尾部插入
lst.push_back(10);
lst.push_back(20);
// 头部插入
lst.push_front(5);
// 迭代器指向第二个元素(值为10),插入15
list<int>::iterator it = ++lst.begin();
lst.insert(it, 15);
// 插入3个30,位置在开头
lst.insert(lst.begin(), 3, 30);
// 遍历结果:30 30 30 5 15 10 20
5.2. 元素删除(pop_back、pop_front、erase、clear)
pop_back():删除容器尾部元素,无返回值,时间复杂度O(1)。
pop_front():删除容器头部元素,无返回值,时间复杂度O(1)(vector无此函数)。
erase(pos):删除迭代器pos指向的元素,返回指向删除元素下一个位置的迭代器,时间复杂度O(1)。
erase(beg, end):删除[beg, end)范围的元素,返回指向删除范围下一个位置的迭代器。
clear():清空容器所有元素,容器大小变为0,时间复杂度O(n)。
⚠️ 注意:删除元素后,指向该元素的迭代器会失效,后续不可再使用,需通过erase的返回值更新迭代器。
cpp
list<int> lst = {5,15,10,20};
// 删除尾部元素(20)
lst.pop_back();
// 删除头部元素(5)
lst.pop_front();
// 删除迭代器指向的元素(15)
list<int>::iterator it = lst.begin();
it = lst.erase(it);
// it更新为指向10
// 删除所有元素
lst.clear();
// 此时lst为空,size()为0
5.3. 元素访问与修改(front、back)
list不支持下标访问,只能通过front()和back()访问头部、尾部元素:
front():返回容器第一个元素的引用,可修改(非const容器)。
back():返回容器最后一个元素的引用,可修改。
cpp
list<int> lst = {1,2,3};
cout << "头部元素:" << lst.front() << endl;
// 输出1
cout << "尾部元素:" << lst.back() << endl;
// 输出3
// 修改头部、尾部元素
lst.front() = 10; lst.back() = 30;
// 此时list为{10,2,30}
5.4. 容器大小与判断(size、empty、resize)
size():返回容器中元素的个数,时间复杂度O(1)(C++11后)。
empty():判断容器是否为空,为空返回true,否则返回false,时间复杂度O(1)。
resize(n):将容器大小调整为n,若n>原大小,新增元素为默认值;若n<原大小,删除多余元素。
resize(n, elem):调整大小为n,新增元素为elem。
cpp
list<int> lst = {1,2,3};
cout << "元素个数:" << lst.size() << endl;
// 输出3
cout << "是否为空:" << lst.empty() << endl;
// 输出0(false)
lst.resize(5, 10);
// 调整为5个元素,新增2个10,结果{1,2,3,10,10}
lst.resize(2);
// 调整为2个元素,删除多余元素,结果{1,2}
5.5. 其他常用函数
sort():对list元素进行排序,默认升序,时间复杂度O(nlogn)(注意:list自带sort成员函数,不可使用STL算法sort,因为STL sort要求随机访问迭代器)。
reverse():反转容器中所有元素的顺序,时间复杂度O(n)。
unique():删除容器中相邻的重复元素(需先排序,否则仅删除连续重复项),时间复杂度O(n)。
merge(lst):将另一个已排序的list lst合并到当前已排序的list中,合并后仍有序,时间复杂度O(n+m)。
cpp
list<int> lst = {3,1,4,2,2,5,5,5};
// 排序(升序)
lst.sort();
// 结果{1,2,2,3,4,5,5,5}
// 反转
lst.reverse();
// 结果{5,5,5,4,3,2,2,1}
// 去重(需先排序,这里重新排序后去重)
lst.sort();
lst.unique();
// 结果{1,2,3,4,5}
六、list的常见误区与注意事项
6.1. 迭代器失效问题
list插入元素时,仅修改节点指针,迭代器不会失效(指向插入位置前后的迭代器仍可用);但删除元素时,指向被删除元素的迭代器会失效,其他迭代器不受影响。因此删除元素后,需通过erase的返回值更新迭代器:
cpp
// 错误写法:迭代器失效后继续使用
for (auto it = lst.begin(); it != lst.end(); it++)
{
if (*it == 2)
{
lst.erase(it); // it失效,后续++操作报错
}
}
// 正确写法:用erase返回值更新迭代器
for (auto it = lst.begin(); it != lst.end(); )
{
if (*it == 2)
{
it = lst.erase(it); // 更新it为下一个有效迭代器
}
else
{ it++; }
}
6.2. 不可使用STL算法sort
STL中的sort算法(#include <algorithm>)要求迭代器支持随机访问,而list的迭代器是双向迭代器,不满足要求,因此必须使用list自带的sort成员函数。
6.3. 与vector的对比选择
很多初学者会混淆list和vector,两者核心区别及选择建议如下:
| 特性 | list | vector |
|---|---|---|
| 底层结构 | 带头双向循环链表 | 动态数组 |
| 随机访问 | 不支持(O(n)) | 支持(O(1)) |
| 插入/删除(中间) | 高效(O(1)) | 低效(O(n)) |
| 内存占用 | 节点额外存储指针,内存开销大 | 连续内存,开销小 |
| 适用场景 | 频繁插入/删除,无需随机访问 | 频繁随机访问,插入/删除多在尾部 |
七、实战案例:list实现简单任务队列
利用list的头部删除、尾部插入高效的特性,实现一个简单的先进先出(FIFO)任务队列:
cpp
// 任务队列类
class TaskQueue {
public:
list<string> taskList; // 存储任务的list public:
// 加入任务(尾部插入)
void pushTask(const string& task)
{ taskList.push_back(task); cout << "任务加入:" << task << endl; }
// 执行任务(头部删除)
void executeTask()
{ if (taskList.empty()) { cout << "无任务可执行!" << endl; return; }
string task = taskList.front();
taskList.pop_front();
cout << "执行任务:" << task << endl;
}
// 查看队列大小
int getTaskCount() const
{ return taskList.size(); }
};
int main()
{
TaskQueue q;
q.pushTask("完成C++ list博客");
q.pushTask("学习STM32新建工程");
q.pushTask("整理嵌入式笔记");
cout << "当前任务数:" << q.getTaskCount() << endl;
q.executeTask();
q.executeTask();
q.executeTask();
q.executeTask();
// 无任务 return 0;
}
输出结果:

除此之外,我们还可以用list实现栈、队列、双端队列等数据结构,感兴趣的话可以去看煮包的数据结构专栏~
总结
本文讲解了C++ list容器的核心用法,包括构造函数、迭代器、常用成员函数及实战案例,重点突出了list基于双向链表的特性与适用场景。对于初学者而言,需牢记list与vector的区别,根据实际需求选择合适的容器;同时注意迭代器失效问题,避免编程错误。
后续可深入学习list的进阶用法,如自定义排序规则、与其他STL容器的配合使用等,逐步夯实C++ STL基础。
都看到这里啦,留下你滴三连吧~
