先看代码,常用的就是代码中有的那些
#include <bits/stdc++.h>
using namespace std;
int main() {
list<int> mylist;
for(int i=0;i<5;i++){
mylist.push_back(i);
//TODO
}
for(const auto&i:mylist)
cout<<i<<'\n';
//fanzhuan
reverse(mylist.begin(),mylist.end());
cout<<" //fanzhuan\n ";
for(const auto&i:mylist){
cout<<i<<'\n';
//TODO
}
mylist.insert(++mylist.begin(),9);
cout<<" //插入\n ";
for(const auto&i:mylist){
cout<<i<<'\n';
//TODO
}
mylist.erase(++ mylist.begin(),--mylist.end());
//删除了一个区间的数
cout<<"size"<<mylist.size()<<'\n';
for(const auto&i:mylist){
cout<<i<<'\n';
//TODO
}
return 0;
}
- List的核心操作:如push_back, push_front, pop_back, pop_front等。
- 迭代器使用:如何遍历list,不同迭代器的区别(正向、反向)。
- 容量与大小管理:size(), empty(), clear()等方法,以及与vector在内存管理上的对比。
- 元素访问:通过迭代器和下标访问的效率差异,at()方法的使用。
- 插入与删除操作:在特定位置插入或删除元素的效率,erase方法的不同用法。
- 自定义类型支持:如何存储对象,需要重载哪些运算符。
- 性能比较:list在插入删除时的优势,以及随机访问的劣势。
- 实际应用案例:比如实现栈、队列,或者作为链表结构的使用场景。
一、核心成员函数详解(修正拼写错误后)
函数名称 | 功能说明 | 示例代码 | 注意事项 |
---|---|---|---|
push_back() |
在链表尾部插入元素 | lst.push_back(10); |
时间 O(1) |
push_front() |
在链表头部插入元素 | lst.push_front(20); |
时间 O(1) |
pop_back() |
移除链表尾部元素 | lst.pop_back(); |
空容器调用会崩溃 |
pop_front() |
移除链表头部元素 | lst.pop_front(); |
同上 |
size() |
返回链表元素个数 | int n = lst.size(); |
O(1) |
empty() |
检查链表是否为空 | if (lst.empty()) {...} |
O(1) |
clear() |
清空所有元素 | lst.clear(); |
O(n) |
front() |
获取第一个元素的引用 | int x = lst.front(); |
空容器调用崩溃 |
back() |
获取最后一个元素的引用 | int y = lst.back(); |
同上 |
begin() |
返回首元素的迭代器 | auto it = lst.begin(); |
|
end() |
返回尾元素后继的迭代器 | auto rit = lst.rbegin(); |
|
insert(pos, val) |
在位置 pos 前插入元素 |
lst.insert(lst.begin()+1, 30); |
时间 O(n) |
erase(pos) |
删除位置 pos 的元素 |
lst.erase(lst.begin()+1); |
同上 |
二、常用扩展函数(图片未提及)
函数名称 | 功能说明 | 示例代码 |
---|---|---|
emplace_back(val) |
在尾部直接构造元素(比 push_back 更高效) |
lst.emplace_back(100); |
splice(pos, other) |
将另一个链表的元素插入到当前位置 | lst.splice(it, other_lst); |
merge(other) |
合并两个有序链表(保持有序性) | auto merged = merge(&a, &b); |
remove(val) |
删除所有等于 val 的元素 |
lst.remove(5); |
reverse() |
反转链表顺序 | lst.reverse(); |
assign(range) |
用区间内的元素覆盖当前链表 | lst.assign(arr.begin(), arr.end()); |
三、安全操作示例
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> lst;
// 安全插入
lst.push_back(1);
lst.push_front(2);
// 避免空容器操作
if (!lst.empty()) {
cout << "首元素: " << lst.front() << endl; // 输出 2
cout << "末元素: " << lst.back() << endl; // 输出 1
}
// 使用迭代器安全删除
if (lst.size() > 0) {
auto it = lst.begin();
lst.erase(it); // 删除第一个元素
}
return 0;
}
四、list vs vector 对比分析
特性 | list (双向链表) |
vector (动态数组) |
---|---|---|
内存分配 | 分散的内存块(指针开销大) | 连续内存块(紧凑存储) |
插入/删除效率 | 头部 O(1),中间 O(1)(已知位置) | 头部 O(n),中间 O(n) |
随机访问 | O(n)(需遍历) | O(1)(直接索引) |
内存占用 | 较高(每个节点存储指针) | 较低(仅存储数据) |
迭代器失效性 | 仅被删除元素的迭代器失效 | 所有迭代器可能失效(扩容时) |
适用场景 | 频繁插入/删除元素 | 频繁随机访问元素 |
1. 实现LRU缓存(基于list和unordered_map)
#include <list>
#include <unordered_map>
using namespace std;
template<typename K, typename V>
class LRUCache {
private:
list<pair<K, V>> cache; // 按访问顺序排列
unordered_map<K, typename list<pair<K, V>>::iterator> pos_map;
size_t capacity;
public:
LRUCache(size_t cap) : capacity(cap) {}
void get(K key) {
auto it = pos_map.find(key);
if (it == pos_map.end()) return;
// 将节点移到头部(最近使用)
cache.splice(cache.begin(), cache, it->second);
}
void put(K key, V value) {
auto it = pos_map.find(key);
if (it != pos_map.end()) {
// 更新值并移到头部
it->second->second = value;
cache.splice(cache.begin(), cache, it->second);
} else {
if (cache.size() >= capacity) {
// 删除末尾元素(最久未使用)
auto last = cache.end();
--last;
pos_map.erase(last->first);
cache.pop_back();
}
// 插入新节点到头部
auto newNode = cache.emplace_front(key, value);
pos_map[key] = newNode;
}
}
};
2. 双向链表反转
void reverse_list(list<int>& lst) {
auto it1 = lst.begin();
auto it2 = lst.end();
while (it1 != it2) {
swap(*it1, *it2);
++it1;
--it2;
}
}
二、核心内容架构
1. 双向链表节点的底层实现
• 节点结构体解析:
template<typename T>
struct Node {
T data; // 存储元素
Node* prev; // 前驱指针
Node* next; // 后继指针
Node(const T& val) : data(val), prev(nullptr), next(nullptr) {}
};
• 内存分配策略:
• 节点池技术:预分配内存块减少动态分配开销
• 分配器模式:与vector不同的allocator实现(如__gnu_pbds::node_allocator)
2. 关键成员函数源码剖析
• 构造函数:
list() : head(nullptr), tail(nullptr), size(0) {} // 空链表初始化
list(initializer_list<T> il) { ... } // 包含初始化列表的构造
• 动态扩容机制:
• 无需扩容:链表结构支持O(1)时间复杂度的头尾插入/删除
• 迭代器失效性:只有被删除元素的迭代器会失效,其他迭代器保持有效
3. 高效操作实现原理
• push_back():
void push_back(const T& val) {
Node* newNode = allocator.allocate(1);
allocator.construct(newNode, val);
if (empty()) {
head = tail = newNode;
} else {
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
}
++size;
}
• insert(pos, val):
• 定位节点:双向链表支持O(n)时间复杂度定位
• 指针调整:四步操作(新节点创建→前后指针重新链接)
4. 与vector的对比实验
操作 | list | vector |
---|---|---|
头部插入 | O(1) | O(n) |
中间插入 | O(1)(已知位置) | O(n) |
随机访问 | O(n) | O(1) |
内存占用 | 较高(指针开销) | 较低(紧凑存储) |
适用场景 | 频繁插入/删除 | 频繁随机访问 |
三、代码示例与调试
1. 实现链表反转(迭代器版)
void reverse_list(list<int>& lst) {
auto it1 = lst.begin();
auto it2 = lst.end();
while (it1 != it2) {
swap(*it1, *it2);
++it1;
--it2;
}
}
2. 模拟LRU缓存淘汰算法
#include <list>
#include <unordered_map>
template<typename K, typename V>
class LRUCache {
private:
list<pair<K, V>> cache; // 按访问顺序排列
unordered_map<K, typename list<pair<K, V>>::iterator> pos_map;
size_t capacity;
public:
LRUCache(size_t cap) : capacity(cap) {}
void get(K key) {
auto it = pos_map.find(key);
if (it == pos_map.end()) return;
// 将访问的节点移到链表头部
cache.splice(cache.begin(), cache, it->second);
}
void put(K key, V value) {
auto it = pos_map.find(key);
if (it != pos_map.end()) {
// 存在则更新值并移动到头部
it->second->second = value;
cache.splice(cache.begin(), cache, it->second);
} else {
if (cache.size() >= capacity) {
// 淘汰末尾元素
auto last = cache.end();
--last;
pos_map.erase(last->first);
cache.pop_back();
}
// 插入新节点到头部
auto newNode = cache.emplace_front(key, value);
pos_map[key] = newNode;
}
}
};
四、进阶知识点
1. 内存泄漏检测技巧
• 使用Valgrind工具检测链表节点泄漏:
valgrind --leak-check=yes ./a.out
2. 自定义内存池优化
template<typename T>
class FastList : public std::list<T> {
private:
struct Pool {
T* buffer;
size_t capacity;
Pool(size_t cap) : capacity(cap) {
buffer = static_cast<T*>(malloc(cap * sizeof(T)));
}
~Pool() { free(buffer); }
};
Pool pool(1024); // 预分配1024个节点
// 重载allocator
using allocator_type = typename std::list<T>::allocator_type;
allocator_type alloc;
public:
FastList() : std::list<T>(alloc), pool(1024) {
this->allocator = alloc; // 绑定自定义分配器
}
};
五、学习建议
配套书籍推荐:
• 《Effective STL》Item 10:选择合适的容器
• 《C++ Primer》第16章:链表容器详解实验环境配置:
g++ -std=c++17 -Wall -Wextra list_exercise.cpp -o list_exercise
常见错误总结:
• 误用operator[]
访问链表(仅vector支持随机访问)
• 忘记释放自定义分配的内存(内存泄漏)
• 在迭代器失效后继续操作(未理解链表结构特性)