在 C++ 标准模板库(STL)中,list是一种基于带头双向循环链表 实现的容器,它与vector、deque等顺序容器相比,在插入、删除操作上具有独特的优势。本文将从list的核心特性、常用接口、迭代器特性及实战场景等维度,全面解析list容器的使用方式与设计思想。
一、list 容器的核心特性
- 底层结构:带头双向循环链表,每个节点包含数据域、前驱指针和后继指针,头节点不存储有效数据,仅用于维护链表结构。
- 迭代器类型 :双向迭代器,仅支持
++、--操作,不支持+、-算术运算(区别于vector的随机迭代器)。 - 操作效率:任意位置的插入 / 删除操作时间复杂度为 O (1)(仅需修改指针),但随机访问效率低(需遍历链表)。
- 不支持 [] 运算符:由于不具备随机访问能力,无法通过下标直接访问元素。
二、list 容器的常用接口与实战
1. 基础遍历与初始化
list的遍历可通过迭代器或范围 for 循环实现,需注意其迭代器不支持算术运算(如it + 1)。
cpp
#include <iostream>
#include <list>
using namespace std;
void test_list_basic() {
list<int> lt;
// 尾插元素
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
// 迭代器遍历
list<int>::iterator it = lt.begin();
while (it != lt.end()) {
cout << *it << " ";
++it; // 仅支持++操作
}
cout << endl; // 输出:1 2 3 4
// 范围for遍历
for (auto e : lt) {
cout << e << " ";
}
cout << endl; // 输出:1 2 3 4
}
2. 高效插入:emplace_back vs push_back
push_back和emplace_back均用于尾插元素,但emplace_back更高效 ------ 它直接在list的内存空间中构造对象,避免了临时对象的拷贝 / 移动。
基础类型场景(无明显差异)
cpp
list<int> lt;
lt.push_back(1);
lt.emplace_back(2); // 功能等价于push_back,效率略优
lt.emplace_back(3);
自定义类型场景(优势显著)
cpp
struct A {
A(int a1 = 1, int a2 = 1) : _a1(a1), _a2(a2) {}
int _a1;
int _a2;
};
void test_emplace_back() {
list<A> lt;
A aa1(1, 1);
lt.push_back(aa1); // 拷贝构造
lt.push_back(A(2, 2)); // 临时对象构造+拷贝/移动
// lt.push_back(3, 3); // 错误:push_back仅支持单个参数
lt.emplace_back(aa1); // 拷贝构造
lt.emplace_back(A(2, 2)); // 临时对象构造+拷贝/移动
lt.emplace_back(3, 3); // 直接在链表节点中构造A对象,无临时对象
}
3. 插入与删除操作
(1)指定位置插入(insert)
insert可在迭代器指向的位置前插入元素,由于双向迭代器仅支持++/-- ,需通过循环移动迭代器定位。
cpp
void test_insert() {
list<int> lt{1,2,3,4,5,6};
auto it = lt.begin();
int k = 3;
while (k--) { // 移动3次,指向元素3
it++;
}
lt.insert(it, 30); // 在3前插入30
for (auto e : lt) {
cout << e << " "; // 输出:1 2 3 30 4 5 6
}
}
(2)查找与删除(erase/remove/remove_if)
erase:删除迭代器指向的元素,返回下一个元素的迭代器(避免迭代器失效);remove:直接删除指定值的所有元素;remove_if:按自定义条件删除元素(需配合仿函数)。
cpp
#include <algorithm> // find函数头文件
void test_erase() {
list<int> lt{1,2,3,30,4,5,6};
// 查找值为2的元素
auto it = find(lt.begin(), lt.end(), 2);
if (it != lt.end()) {
lt.erase(it); // 删除元素2
}
for (auto e : lt) {
cout << e << " "; // 输出:1 3 30 4 5 6
}
// 直接删除值为30的元素
lt.remove(30);
// 删除所有大于5的元素
lt.remove_if([](int x){ return x > 5; });
}
4. 排序、逆置与合并
(1)排序(sort)
算法库的sort要求随机迭代器,因此list提供了专属的sort成员函数,支持默认升序、自定义降序。
cpp
void test_sort() {
list<int> lt{1,20,3,4,5,6};
lt.sort(); // 默认升序:1 3 4 5 6 20
// 降序排序(仿函数)
greater<int> gt;
lt.sort(gt); // 20 6 5 4 3 1
}
(2)逆置(reverse)
list重载了reverse成员函数,比算法库的reverse更高效(无需随机迭代器)。
cpp
lt.reverse(); // 直接逆置链表,时间复杂度O(n)
(3)合并(merge)
将两个已排序的list合并为一个有序链表,合并后被合并的链表为空。
cpp
void test_merge() {
list<double> first{3.1,2.2,2.9};
list<double> second{3.7,7.1,1.4};
first.sort(); // 1.4 2.2 2.9(错误,实际排序后:2.2 2.9 3.1)
second.sort(); // 1.4 3.7 7.1
first.merge(second); // 合并后first:1.4 2.2 2.9 3.1 3.7 7.1,second为空
for (auto e : first) {
cout << e << " ";
}
}
5. 去重(unique)
unique用于删除连续的重复元素,使用前必须先排序(否则仅能删除连续重复项)。
cpp
void test_unique() {
list<int> lt{1,20,3,3,3,4,5,5,6,5};
lt.sort(); // 先排序:1 3 3 3 4 5 5 5 6 20
lt.unique(); // 去重后:1 3 4 5 6 20
for (auto e : lt) {
cout << e << " ";
}
}
6. 节点转移(splice)
splice可将一个list的节点转移到另一个list的指定位置前,操作仅修改指针,效率极高。
cpp
void test_splice() {
list<int> mylist1{1,2,3,4};
list<int> mylist2{10,20,30};
auto it = mylist1.begin();
++it; // 指向2
// 将mylist2所有节点转移到mylist1的it位置前
mylist1.splice(it, mylist2); // mylist1:1 10 20 30 2 3 4,mylist2为空
// 实战:将指定元素移到链表头部
list<int> lt{1,2,3,4,5,6};
int x = 5;
it = find(lt.begin(), lt.end(), x);
if (it != lt.end()) {
// 把it到末尾的所有节点移到头部
lt.splice(lt.begin(), lt, it, lt.end()); // lt:5 6 1 2 3 4
}
}
7. 内容替换(assign)
assign用于替换list的全部内容,先清空原有元素,再构造新元素,比直接拷贝更高效。
cpp
void test_assign() {
list<int> lt1, lt2{10,20,30,20,10};
// 优化排序:先拷贝到vector(支持随机迭代器)
vector<int> v(lt2.begin(), lt2.end());
sort(v.begin(), v.end()); // 算法库sort效率更高
lt2.assign(v.begin(), v.end()); // 替换lt2内容为排序后的结果
}
三、list 迭代器的深度解析
1. 迭代器类型划分
STL 迭代器按功能可分为三类:
- 单向迭代器(如
forward_list):仅支持++; - 双向迭代器(如
list/map/set):支持++/--; - 随机迭代器(如
vector/string/deque):支持++/--/+/-。
2. 迭代器的实现(自定义 list 迭代器)
list的迭代器本质是对链表节点指针的封装,核心重载*、->、++、--等运算符:
cpp
template<class T, class Ref, class Ptr>
struct list_iterator {
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;
list_iterator(Node* node) : _node(node) {}
Ref operator*() { return _node->_data; } // 支持const/非const引用
Ptr operator->() { return &_node->_data; } // 访问自定义类型成员
Self& operator++() { _node = _node->_next; return *this; }
Self& operator--() { _node = _node->_prev; return *this; }
bool operator!=(const Self& s) const { return _node != s._node; }
};
3. 按需实例化特性
模板的实例化遵循 "按需实例化" 原则:未调用的成员函数不会被编译,因此即使代码中存在语法问题,只要未调用就不会报错。
cpp
list<int>::const_iterator it = lt.begin();
while (it != lt.end()) {
// *it += 10; // 编译不报错(未实例化时),运行报错(const迭代器不可修改)
cout << *it << " ";
++it;
}
四、list 容器的适用场景
适用场景:
- 频繁在任意位置插入 / 删除元素(如链表结构的业务场景);
- 无需随机访问,仅需顺序遍历;
- 需要高效的节点转移(splice)操作。
不适用场景:
- 需要随机访问元素(优先选
vector/deque); - 频繁排序(优先选
vector,配合算法库sort)。