作为C++标准模板库(STL)中最经典的序列式容器之一,list本质上是一个双向链表结构。与vector这种连续存储的数组结构不同,list通过指针将离散的内存块串联起来。这种底层设计决定了它的核心特性:
实际工程中,当我们需要频繁在序列中部进行插入删除操作时,list的性能优势就会凸显出来。比如实现LRU缓存淘汰算法、维护有序但频繁修改的数据集合等场景。
创建list对象有多种方式:
cpp复制#include <list>
using namespace std;
// 默认构造
list<int> lst1;
// 指定初始大小和值
list<string> lst2(5, "hello");
// 通过迭代器范围初始化
int arr[] = {1,3,5};
list<int> lst3(arr, arr+3);
// C++11统一初始化
list<char> lst4 = {'a','b','c'};
元素插入主要三个方法:
cpp复制list<int> nums;
// 尾部插入
nums.push_back(10);
// 头部插入
nums.push_front(5);
// 指定位置插入
auto it = nums.begin();
advance(it, 2);
nums.insert(it, 7); // 在第三个位置插入7
注意:insert操作不会使迭代器失效,这与vector有本质区别
由于不支持随机访问,list必须通过迭代器遍历:
cpp复制// 正向迭代器
for(auto it = nums.begin(); it != nums.end(); ++it) {
cout << *it << " ";
}
// C++11范围for
for(int num : nums) {
cout << num << " ";
}
// 反向迭代器
for(auto rit = nums.rbegin(); rit != nums.rend(); ++rit) {
cout << *rit << " ";
}
获取首尾元素有专用方法:
cpp复制if(!nums.empty()) {
cout << "头元素:" << nums.front() << endl;
cout << "尾元素:" << nums.back() << endl;
}
删除操作同样有多种形式:
cpp复制list<int> nums = {1,2,3,4,5};
nums.pop_front(); // 删除头部 → [2,3,4,5]
nums.pop_back(); // 删除尾部 → [2,3,4]
auto it = nums.begin();
advance(it, 1);
nums.erase(it); // 删除第二个元素 → [2,4]
nums.remove(2); // 删除所有等于2的元素 → [4]
nums.clear(); // 清空容器
关键区别:erase按位置删除,remove按值删除
虽然list不需要预分配内存,但仍有相关方法:
cpp复制list<int> nums;
cout << nums.size() << endl; // 当前元素个数
cout << nums.max_size() << endl; // 系统限制的最大容量
cout << boolalpha << nums.empty() << endl; // 判空
nums.resize(10); // 调整大小为10,新增元素默认初始化
nums.resize(5); // 缩小会删除多余元素
list内置sort方法比通用算法更高效:
cpp复制list<int> nums = {3,1,4,2,5};
// 升序排序
nums.sort(); // → [1,2,3,4,5]
// 自定义排序规则
nums.sort([](int a, int b) {
return a > b; // 降序
});
// 去重(需先排序)
nums.unique();
list特有的高效拼接操作:
cpp复制list<int> lst1 = {1,3,5};
list<int> lst2 = {2,4,6};
// 将lst2拼接到lst1末尾
lst1.splice(lst1.end(), lst2);
// 合并两个有序链表
lst1.sort();
lst2 = {0,3,7};
lst2.sort();
lst1.merge(lst2); // lst2将被清空
list非常适合存储复杂对象:
cpp复制struct Student {
string name;
int score;
bool operator<(const Student& rhs) const {
return score < rhs.score;
}
};
list<Student> classList;
classList.push_back({"Alice", 85});
classList.push_back({"Bob", 76});
// 按成绩排序
classList.sort();
// 查找特定学生
auto it = find_if(classList.begin(), classList.end(),
[](const Student& s) { return s.name == "Bob"; });
虽然list的插入删除不会使迭代器失效,但需要特别注意:
cpp复制list<int> nums = {1,2,3,4,5};
auto it = nums.begin();
advance(it, 2); // 指向3
nums.erase(it); // it现在指向被删除元素的后继(4)
// 错误示范:继续使用已删除的迭代器
// cout << *it << endl; // 未定义行为
当面临容器选择时,考虑以下决策矩阵:
| 操作需求 | 推荐容器 | 原因 |
|---|---|---|
| 频繁中间插入删除 | list | O(1)时间复杂度 |
| 随机访问频繁 | deque | 支持下标访问 |
| 内存占用敏感 | vector | 连续存储无额外指针开销 |
| 需要稳定排序 | list | 内置sort保证稳定性 |
避免频繁调用size():
cpp复制// 低效写法
for(size_t i=0; i < nums.size(); ++i) { /*...*/ }
// 正确做法
for(auto it = nums.begin(); it != nums.end(); ++it) { /*...*/ }
使用前置递增运算符:
cpp复制// 后置递增会产生临时对象
for(auto it = nums.begin(); it != nums.end(); it++) { /*...*/ }
// 更高效的写法
for(auto it = nums.begin(); it != nums.end(); ++it) { /*...*/ }
利用list的快速插入删除特性:
cpp复制class LRUCache {
private:
list<pair<int,int>> cache;
unordered_map<int, list<pair<int,int>>::iterator> map;
int capacity;
public:
LRUCache(int cap) : capacity(cap) {}
int get(int key) {
if(map.find(key) == map.end()) return -1;
// 移动到链表头部
cache.splice(cache.begin(), cache, map[key]);
return map[key]->second;
}
void put(int key, int value) {
if(map.find(key) != map.end()) {
map[key]->second = value;
cache.splice(cache.begin(), cache, map[key]);
return;
}
if(cache.size() == capacity) {
map.erase(cache.back().first);
cache.pop_back();
}
cache.emplace_front(key, value);
map[key] = cache.begin();
}
};
list本身不是线程安全的,需要额外保护:
cpp复制#include <mutex>
list<int> sharedList;
mutex mtx;
void safePush(int val) {
lock_guard<mutex> guard(mtx);
sharedList.push_back(val);
}
void safePop() {
lock_guard<mutex> guard(mtx);
if(!sharedList.empty()) {
sharedList.pop_front();
}
}
虽然list有专用算法,但部分STL算法仍适用:
cpp复制list<int> nums = {1,2,3,4,5};
// 查找元素
auto it = find(nums.begin(), nums.end(), 3);
// 条件计数
int cnt = count_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
// 遍历执行操作
for_each(nums.begin(), nums.end(),
[](int& x) { x *= 2; });
list作为C++中最经典的链表实现,其设计哲学体现了STL的核心思想:将数据结构与算法分离,通过迭代器作为粘合剂。在实际使用时,需要充分理解其底层实现特点,才能发挥最大性能优势。