I. list是什么


翻译成中文是这样的
List(列表)
列表是序列容器 ,支持在序列内任意位置进行**常数时间O(1)**的插入和删除操作,并且支持双向迭代。
list 容器的底层实现为双向链表;双向链表可以将其包含的每个元素存储在互不相关、分散的内存位置中。容器内部通过为每个元素维护「指向前驱元素的链接」和「指向后继元素的链接」,来保持元素的顺序。
它和 forward_list 非常相似:核心区别在于 forward_list 是单向链表,因此仅支持正向迭代,代价是占用空间更小、效率略高。
与其他基础标准序列容器(array、vector、deque)相比,list 在「容器内任意位置插入、提取、移动元素(迭代器已定位的位置)」的场景中,性能通常更优,因此也更适合频繁执行这类操作的算法(比如排序算法)。
list 和 forward_list 相比其他序列容器的主要缺点是:不支持通过位置直接访问元素;例如,要访问 list 中的第 6 个元素,必须从一个已知位置(如开头或末尾)迭代到该位置,耗时与两个位置的距离呈线性关系。此外,它们还需要额外内存来存储每个元素的链接信息(对于存储小元素的大型列表,这可能是一个重要的性能开销)。
容器属性 (Container properties)
1. Sequence(序列型)
序列容器中的元素按严格的线性顺序排列,可通过元素在序列中的位置访问单个元素。
2. Doubly-linked list(双向链表)
每个元素都存储了定位下一个和上一个元素的信息,支持在指定元素之前 / 之后(甚至整个范围)进行常数时间的插入和删除操作,但不支持直接随机访问。
3. Allocator-aware(分配器感知)
该容器使用分配器对象来动态管理其存储需求。
list存储结构
list容器底层为带头双向链表,带头双向循环链表每个节点包含两个指针,prev和next,前者指向前置结点,后者指向后置结点,并且包含一个哨兵结点(第一个元素节点,且不包含任何数据)

list容器定义
cpp
list <typename> name;
这里的typename 可以是任何基本类型 ,例如int、double、char、结构体 等,也可以是STL标准容器 ,例如string、set、queue、vector 等。
注意: 使用前必须加上头文件: #include <list>
cpp
#include <iostream>
#include <list>
using namespace std;
int main()
{
int a[3]; // 正常定义的-----静态数组
list<int> str_a; // list 定义的 int类型的链表
char b[3];
list<char> str_b;
return 0;
}
list容器常用接口的使用
list构造

我们单独看一下最后一个方式:使用迭代器拷贝构造
cpp
string s("hello world");
list<char> lt4(s.begin(),s.end()); //构造string对象某段迭代器区间的内容
在C++11中,可以使用花括号构造内容
cpp
list<int> lt{ 1,2,3,4,5 }; // 直接使用花括号进行构造---C++11允许
list的遍历及迭代器的操作
需要注意的是 :链表只能使用迭代器和范围for遍历
迭代器
cpp
#include <iostream>
#include <list>
using namespace std;
int main()
{
int array[] = { 1,2,3,4,5,6,7,8 };
// 用已有的数组 初始化 list 链表
list<int> lt(array, array + sizeof(array) / sizeof(array[0]));
// 正向迭代器遍历容器
list<int>::iterator it = lt.begin();
// 开始循环遍历
while (it != lt.end())
{
cout << *it << " "; // 1 2 3 4 5 6 7 8
it++;
}
cout << endl;
return 0;
}
范围for
cpp
#include <iostream>
#include <list>
#include <vector>
using namespace std;
int main()
{
vector<int> v{ 1,2,3,4,5 };
list<int> lt(v.begin(), v.end());
// C++ 范围for 的方式遍历
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
// 输出 :1 2 3 4 5
return 0;
}
list容器常见操作

这些与之前string vector的 操作基本类似,就不做代码展示
list容量的常见访问操作
① front()------访问list头元素
cpp
#include<iostream>
#include<list>
using std::cout;
using std::endl;
using std::list;
int main() {
list<int> mylist {1, 2, 3, 4, 5, 6, 7, 8};
cout<< "初始化后的mylist为:";
for (auto num : mylist) {
cout<< num<< " ";
}
int front = mylist.front(); // 1
cout<< "\nmylist的头元素为:"<< front;
}
② back()------访问list尾元素
cpp
#include<iostream>
#include<list>
using std::cout;
using std::endl;
using std::list;
int main() {
list<int> mylist {1, 2, 3, 4, 5, 6, 7, 8};
cout<< "初始化后的mylist为:";
for (auto num : mylist) {
cout<< num<< " ";
}
int front = mylist.front(); // 8
cout<< "\nmylist的头元素为:"<< front;
}
list容器的常见修改操作

这里我们重点来看一看Insert 和 erase,在vector容器的实现中,我们了解到了这两者的实现中
存在迭代器失效的情况,解决方法是将返回值设置为迭代器类型
insert()------添加元素(任意位置)
insert共有三种形式:
- insert(iterator, value);
- insert(iterator, num, value);
- insert(iterator, iterator1, iterator2);
方法1和方法2 我们早就领教过了,我们这里来看一下方法三
insert(iterator, iterator1, iterator2)
使用**insert(iterator, iterator1, iterator2);**方法,作用是向iterator迭代器指向元素的前边添加[iterator1,iterator2)之间的元素。

cpp
#include <list>
#include <iostream>
using std::cout;
using std::list;
using std::endl;
int main() {
list<int> mylist{1, 2, 3, 4};
cout << "初始化后的mylist为:";
for (auto num : mylist) {
cout << num << " ";
}
// it指向mylist的第二个元素
list<int>::iterator it = mylist.begin() + 1;
// 定义一个辅助list
list<int> list2{10, 20, 30};
// it1指向list2的第一个元素
list<int>::iterator it1 = list2.begin();
// it2指向list2的最后一个元素后一个位置
list<int>::iterator it2 = list2.end();
// 使用insert在2之前添加[it1,it2)之间的元素
mylist.insert(it, it1, it2);
cout << "\n使用insert插入元素后:";
for (auto num : mylist) {
cout << num << " ";
}
}
erase()------删除元素(任意位置)
erase 共有 两 种形式:
-
list.erase(iterator) -
list.erase(iterator1,iterator2)
方法1就是删除指定位置的元素
**方法2就是删除指定位置之间的元素,**包括iterator1指向的元素但不包括iterator2指向的元素,即擦除[iterator1,iterator2),左闭右开,在迭代器区间这是一个颠簸不破的原则
cpp
#include <list>
#include <iostream>
using std::cout;
using std::list;
using std::endl;
int main() {
list<int> mylist{1, 2, 3, 4};
cout << "初始化后的mylist为:";
for (auto num : mylist) {
cout << num << " ";
}
// it1指向mylist的第二个元素
list<int>::iterator it1 = mylist.begin() + 1;
// it2指向mylist的最后一个元素后一个位置
list<int>::iterator it2 = mylist.end();
// 删除[it1,it2)之间的元素,即删除2,3,4
mylist.erase(it1, it2);
cout << "\n使用erase删除元素后:";
for (auto num : mylist) {
cout << num << " ";
}
}
list 容器的常见操作函数

接下来这些是list容器区别于之前STL容器的操作函数
① splice()------从另一个list中移动元素
splice共有四种形式,分别为:
splice(iterator_pos, otherList) : 将otherList中的所有元素移动到iterator_pos指向元素之前
splice(iterator_pos, otherList, iter1): 从 otherList转移 iter1 指向的元素到当前list。元素被插入到 iterator_pos指向的元素之前。
splice(iterator_pos, otherList, iter_start, iter_end) : 从 otherList转移范围 [iter_start, iter_end) 中的元素到 当前列表。元素被插入到 iterator_pos指向的元素之前。
注意:
1. splice 操作不会进行元素的复制或移动,只是修改指针连接,因此效率较高。
2. 在 splice 操作后,被移动的元素将不再属于原始列表。
3. splice 操作后,原始列表和插入列表的大小会相应改变。
4. 插入操作的时间复杂度为 O(1)
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> list1 = {1, 2, 3, 4, 5};
std::list<int> list2 = {10, 20, 30};
std::list<int> list3 = {1, 2, 3, 4, 5};
std::list<int> list4 = {10, 20, 30};
std::list<int> list5 = {1, 2, 3, 4, 5};
std::list<int> list6 = {10, 20, 30};
/*
用法一 : splice(iterator_pos, otherList)
*/
// 将list2中所有元素插入到list1的第一个位置
list1.splice(list1.begin(), list2);
// 输出结果
std::cout<< "转移list2元素到list1之后的list1: ";
for (int i : list1) {
std::cout << i << " ";
}
std::cout << std::endl;
std::cout<< "转移list2元素到list1之后的list1: ";
for (int i : list2) {
std::cout << i << " ";
}
std::cout << std::endl;
/*
用法二:splice(iterator_pos, otherList, iter1)
*/
auto it = list3.begin();
// 将迭代器向后移动两个位置,指向第三个元素
std::advance(it, 2);
// 将list4的第一个元素(10)插入到list3的第三个位置
list3.splice(it, list4, list4.begin());
// 输出结果
std::cout<< "转移list4元素到list3之后的list3: ";
for (int i : list3) {
std::cout << i << " ";
}
std::cout << std::endl;
std::cout<< "转移list4元素到list3之后的list4: ";
for (int i : list4) {
std::cout << i << " ";
}
std::cout << std::endl;
/*
用法三:splice(iterator_pos, otherList, iter_start, iter_end)
*/
// 将list5中第二个到第四个元素移动到list6的末尾
auto first = list5.begin();
std::advance(first, 1);
auto last = list5.begin();
std::advance(last, 4);
list6.splice(list6.end(), list5, first, last);
// 输出结果
std::cout<< "转移list5元素到list6之后的list5: ";
for (int i : list5) {
std::cout << i << " ";
}
std::cout << std::endl;
std::cout<< "转移list5元素到list6之后的list6: ";
for (int i : list6) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
② remove()------移除特定值的元素
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> mylist {1, 2, 3, 4, 5};
// 移除列表中所有值为2的元素
mylist.remove(2);
std::cout << "移除之后的mylist: ";
for (const auto& element : mylist) {
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
③ remove_if()------移除满足特定标准的元素
cpp
使用方式:remove_if(func)
传入一个函数,满足这个函数就删除
④ unique()------删除连续的重复元素
unique()方法用于移除链表中相邻且重复的元素,仅保留一个副本
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 2, 3, 3, 4, 5, 5, 5};
std::cout << "初始化后的list为: ";
for (auto i : myList) {
std::cout << i << " ";
}
std::cout << std::endl;
myList.unique();
std::cout << "执行unique后的list为: ";
for (auto i : myList) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
⑤ sort()------对元素进行排序
sort操作我们在vector中有提到,需要传入的是随即迭代器,该迭代器类型满足之前迭代器的所有特性,简直万能啊
sort()方法用于对链表中的元素进行排序。默认情况下,它按升序对元素进行排序,使用元素类型的 < 运算符进行比较。示例如下:
cpp
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {5, 3, 2, 4, 1};
std::cout << "初始化后的list为 ";
for (auto i : myList) {
std::cout << i << " ";
}
std::cout << std::endl;
myList.sort();
std::cout << "排序后的list为: ";
for (auto i : myList) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
⑥ merge()------合并list
注意:merge()方法只能用于已排序的list。如果list未排序,则合并的结果将是不正确的。
cpp
#include <iostream>
#include <list>
using namespace std;
int main() {
std::list<int> list1 = {1, 3, 5};
std::list<int> list2 = {2, 4, 6};
cout << "初始化后的list1为:";
for (auto num : list1) {
cout << num << " ";
}
std::cout << std::endl;
cout << "初始化后的list2为:";
for (auto num : list2) {
cout << num << " ";
}
std::cout << std::endl;
// 将 list2 合并到 list1 中
list1.merge(list2);
// 输出合并后的列表
cout << "合并后的list1为:";
for (const auto& element : list1) {
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
⑦ reverse()------将该链表的所有元素的顺序反转 【C++11】
不做展示
⑧ assign()------将值赋给容器
assign()方法用于将链表中的元素替换为新的元素序列。它可以接受不同形式的参数,提供了两种重载形式。
第一种形式接受迭代器范围作为参数,用于将另一个容器或数组中的元素复制到链表中。它会将链表清空,并将指定范围内的元素复制到链表中。void assign(InputIterator first, InputIterator last);
第二种形式接受一个元素数量和一个值作为参数,用于将指定数量的相同值的元素分配给链表。它会将链表清空,并将指定数量的元素赋值为指定的值。void assign(size_type count, const T& value);
cpp
#include <iostream>
#include <vector>
#include <list>
int main() {
std::list<int> myList;
// 使用迭代器范围进行分配
std::vector<int> numbers = {1, 2, 3, 4, 5};
myList.assign(numbers.begin(), numbers.end());
std::cout << "第一次执行assign之后的list为: ";
for (auto i : myList) {
std::cout << i << " ";
}
std::cout << std::endl;
// 使用元素数量和值进行分配
myList.assign(3, 100);
std::cout << "第二次执行assign之后的list为: ";
for (auto i : myList) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
vector 和 list 对比
| 对比维度 | std::vector |
std::list |
|---|---|---|
| 底层实现 | 动态数组,连续内存空间 | 双向链表,非连续内存节点 |
| 元素访问 | 支持随机访问 (operator[]/at()),时间复杂度 O(1) |
仅支持顺序遍历访问 ,不支持随机访问,访问任意元素时间复杂度 O(n) |
| 插入 / 删除操作 | - 尾部插入 / 删除:平均 O(1) (扩容时为 O (n))- 中间 / 头部插入 / 删除:O(n)(需移动后续所有元素) | - 任意已知迭代器位置插入 / 删除:O(1)(仅修改指针,无需移动元素)- 头部 / 尾部操作均为 O (1) |
| 内存开销 | 仅存储元素本身,额外开销极低;预分配空间可能存在内存浪费 | 每个元素需额外存储两个指针(前驱、后继节点地址),内存开销大 |
| 迭代器稳定性 | 插入 / 删除操作可能导致迭代器失效(扩容、中间操作会使后续迭代器失效) | 插入 / 删除操作仅使被删除元素的迭代器失效,其他迭代器完全稳定 |
| 缓存友好性 | 连续内存,CPU 缓存命中率极高,遍历速度极快 | 非连续内存,缓存命中率低,遍历速度远慢于 vector |
| 适用场景 | - 频繁随机访问元素- 主要在尾部插入 / 删除- 对内存占用敏感 | - 频繁在任意位置插入 / 删除元素- 不依赖随机访问- 需保证迭代器稳定 |
| 排序性能 | 极快(随机访问 + 缓存友好,std::sort 最优适配) |
极慢(无随机访问,std::sort 无法高效使用,需用成员函数sort()) |
| 空间扩容 | 自动预分配(通常 2 倍扩容),避免频繁内存申请 | 无预分配,按需分配节点,无内存浪费 |