C++ STL list容器详解与实现
一, list概念
C++ STL list 容器详解
基本定义list 是标准模板库(STL)提供的双向链表实现,属于序列式容器。它以泛型模板方式设计,支持存储任意类型(包括内置类型和用户自定义类)。例如:
list<int> intList; // 存储整数的链表
list<string> strList; // 存储字符串的链表
核心特性
- 高效增删元素:在任何位置插入或删除元素的时间复杂度为 O(1),仅需调整相邻节点的指针。
- 不支持随机访问:必须通过迭代器顺序访问元素,访问第 N 个元素的时间复杂度为 O(N)。
与其他容器对比
vector:连续内存,随机访问高效(O(1)),但中间插入/删除效率低(O(N))。list:非连续内存,随机访问效率低(O(N)),但插入/删除高效(O(1))。
列表成员函数分类表格
| 类别 | 函数名 | 描述 |
|---|---|---|
| 构造/析构 | constructor | 构造列表 |
| destructor | 列表析构函数 | |
| operator= | 赋值操作 | |
| 迭代器 | begin | 返回指向起始位置的迭代器 |
| end | 返回指向末尾位置的迭代器 | |
| rbegin | 返回指向反向起始位置的迭代器 | |
| rend | 返回指向反向末尾位置的迭代器 | |
| cbegin | 返回指向起始位置的常量迭代器 | |
| cend | 返回指向末尾位置的常量迭代器 | |
| crbegin | 返回指向反向起始位置的常量迭代器 | |
| crend | 返回指向反向末尾位置的常量迭代器 | |
| 容量 | empty | 检查容器是否为空 |
| size | 返回容器大小 | |
| max_size | 返回容器最大可能大小 | |
| 元素访问 | front | 访问第一个元素 |
| back | 访问最后一个元素 | |
| 修改器 | assign | 分配新内容到容器 |
| emplace_front | 在起始位置构造并插入元素 | |
| push_front | 在起始位置插入元素 | |
| pop_front | 删除第一个元素 | |
| emplace_back | 在末尾位置构造并插入元素 | |
| push_back | 在末尾位置插入元素 | |
| pop_back | 删除最后一个元素 | |
| emplace | 在指定位置构造并插入元素 | |
| insert | 插入元素 | |
| erase | 删除元素 | |
| swap | 交换内容 | |
| resize | 调整容器大小 | |
| clear | 清空容器内容 | |
| 操作 | splice | 将元素从一个列表转移到另一个列表 |
| remove | 删除具有特定值的元素 | |
| remove_if | 删除满足条件的元素 | |
| unique | 删除重复值 | |
| merge | 合并已排序的列表 | |
| sort | 对容器中的元素进行排序 | |
| reverse | 反转元素的顺序 |
常用操作
定义与初始化
list<int> l1; // 空链表
list<int> l2(10, 1); // 10 个值为 1 的元素
vector<int> v{1, 2, 3};
list<int> l3(v.begin(), v.end()); // 通过迭代器范围构造
list<int> l4(l3); // 拷贝构造
插入与删除
l1.push_front(0); // 头部插入元素
l1.push_back(2); // 尾部插入元素
l1.insert(++l1.begin(), 1); // 在第二个位置插入 1
l1.pop_front(); // 删除头部元素
l1.pop_back(); // 删除尾部元素
l1.erase(l1.begin()); // 删除指定位置元素
元素访问
int first = l1.front(); // 获取首元素
int last = l1.back(); // 获取尾元素
大小与容量
int size = l1.size(); // 元素数量
bool isEmpty = l1.empty(); // 判断是否为空
l1.resize(5); // 调整大小为 5
l1.clear(); // 清空所有元素
迭代器操作
for (auto it = l1.begin(); it != l1.end(); ++it) {
cout << *it << " "; // 顺序遍历
}
二,内容新增
实际list就是我们在数据结构学习的双链表的增删改查,但是在c++中实现有一个新知识:迭代器的实现,vector和string我们都学习了迭代器,但是vector和string是数组指针可以直接访问节点的数据,list链表指针访问不了,所以我们要新增加一个知识迭代器的实现。
其中我们可以先理解以下内容(不理解也没有什么关系,后面继承会讲):
这个图是继承关系比如(Bidirectional继承了Forward特性,好比一个儿子继承了父亲的东西,儿子可以使用父亲的东西,但是父亲不能使用访问儿子东西(这个是比喻不用骂我))

前向迭代器(Forward Iterator)
前向迭代器支持单向遍历,只能从容器头部向尾部移动,适用于单次线性访问的场景。常见应用包括链表(如 std::forward_list)和需要顺序读取的数据结构。例如,遍历未排序的日志文件时,只需单向读取每条记录。
双向迭代器(Bidirectional Iterator)
双向迭代器支持前向和后向移动(如 ++ 和 -- 操作),适用于需要双向遍历的容器。典型应用包括 std::list 和 std::set。在需要反向检查或修改数据的场景(如撤销操作的历史记录)中,双向迭代器尤为实用。
随机访问迭代器(Random Access Iterator)
随机访问迭代器支持直接跳转到任意位置(如 +n 或 [] 操作),时间复杂度为 O(1)。常见于连续内存容器(如 std::vector 和 std::deque)。适用于需要高效随机访问的场景,例如快速排序算法或数值计算中的矩阵元素访问。
输入迭代器(Input Iterator)
输入迭代器仅支持单向读取数据,且每个元素只能读取一次。适用于一次性输入流(如 std::istream_iterator)。典型场景包括从文件或网络流中读取数据,例如逐行解析 CSV 文件。
输出迭代器(Output Iterator)
输出迭代器仅支持单向写入数据,适用于单向输出流(如 std::ostream_iterator)。常见于数据导出或流式处理,例如将计算结果写入文件或传输到外部设备
三,list和veorct区别
| 特性 | list | vector |
|---|---|---|
| 底层结构 | 双向链表 | 动态数组 |
| 内存分配 | 非连续内存,节点分散存储 | 连续内存,支持随机访问 |
| 插入/删除效率 | O(1)(已知迭代器位置) | O(n)(需移动后续元素) |
| 随机访问效率 | O(n)(需遍历) | O(1)(直接通过下标访问) |
| 迭代器失效 | 插入/删除操作不影响其他迭代器 | 插入/删除可能导致所有迭代器失效 |
| 空间开销 | 每个元素额外存储前后指针,占用更多空间 | 仅存储元素,空间利用率高 |
| 扩容机制 | 无扩容概念,按需动态分配节点 | 容量不足时重新分配内存并拷贝元素 |
| 适用场景 | 频繁插入/删除,无需随机访问 | 需要快速随机访问,较少插入/删除 |
关键差异说明
内存连续性
vector的数据存储在连续内存块,适合CPU缓存预取;list的节点分散在内存中,访问局部性较差。
性能权衡
vector的随机访问速度快,但中间插入/删除成本高;list在任何位置插入/删除高效,但查找元素需线性遍历。
迭代器稳定性
vector在增删元素后,原有迭代器可能失效;list的迭代器除非指向被删除元素,否则保持有效。
容量管理
vector需预留容量(capacity)以减少扩容次数;list无预留空间概念,每次插入动态分配节点。
四,list实现
1,节点结构体
因为list是一个带头双向循环链表这个节点有:访问下一个的指针,访问上一个指针和存放数据
c++中struct和类没有区别一定要写构造,如果没有构造默认构造可能达不到预期,可能会报错。
template <class T>
struct ListNode
{
ListNode(const T& val = T())
:_val(val),
pre(nullptr),
next(nullptr)
{
}
struct ListNode* pre;
struct ListNode* next;
T _val;
};
2,迭代器
迭代器这里难度就上来了,你要理解iterator和const_iterator的哪里不同。
这里的const_iterator不是const iterator如果是这样子节点不能访问下一个节点,这里的Ref是引用(reference),Ptr是指针(pointer)
如果没有Ref修饰operator*(),是用T&和const T&这里会是虚假的const_iterator,这个_val是可以改变的。
template<class T,class Ref,class Ptr>
struct ListIterator
{
typedef struct ListNode<T> Node;
typedef struct ListIterator<T, Ref, Ptr> Self;
typedef Node* PNode;
ListIterator(PNode pNode = nullptr)
:_pNode(pNode)
{
}
Ref operator*()
{
return _pNode->_val;
}
Ptr operator->()
{
return &_pNode->_val;
}
Self& operator++()
{
_pNode = _pNode->next;
return *this;
}
Self& operator++(int)
{
_pNode = _pNode->next;
return *this;
}
Self& operator--()
{
_pNode = _pNode->pre;
return *this;
}
Self operator--(int)
{
_pNode = _pNode->pre;
return *this;
}
bool operator!=(const Self& l)
{
return l._pNode != _pNode;
}
bool operator==(const Self& l)
{
return l._pNode == _pNode;
}
PNode _pNode;
};
3,增删改查
这里相信大家数据结构学习过list,增删改查对大家都不是难事。
template <class T>
class list1
{
typedef struct ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
list1()
{
PHead = new Node;
PHead->pre = PHead->next = PHead;
}
list1(int n, const T& value = T())
{
PHead = new Node;
PHead->pre = PHead->next = PHead;
while (n--)
{
this->push_back(value);
}
}
template <class Iterator>
list1(Iterator first, Iterator last)
{
PHead = new Node;
PHead->pre = PHead->next = PHead;
while (first != last)
{
push_back(*first);
first++;
}
}
list1(const list1<T>& l)
{
PHead = new Node;
PHead->pre = PHead->next = PHead;
list1<T> i(l.begin(), l.end());
swap(i);
}
list1<T>& operator=(const list1<T> l)
{
if(!PHead)
clear();
list1<T> i(l.begin(), l.end());
swap(i);
return *this;
}
~list1()
{
if (this->PHead)
{
auto cur = begin();
while (end() != cur)
{
cur = erase(cur);
}
delete PHead;
PHead = NULL;
}
}
// List Iterator
iterator begin()
{
return PHead->next;
}
iterator end()
{
return PHead;
}
const_iterator begin()const
{
return PHead->next;
}
const_iterator end()const
{
return PHead;
}
///////////////////////////////////////////////////////////////
// List Capacity
size_t size()const
{
return _size;
}
bool empty()const
{
if (!size())
{
return true;
}
return false;
}
////////////////////////////////////////////////////////////
// List Access
T& front()
{
assert(!empty());
return PHead->next->_val;
}
const T& front()const
{
return PHead->next->_val;
}
T& back()
{
return PHead->pre->_val;
}
const T& back()const
{
return PHead->pre->_val;
}
// List Modify
void push_back(const T& val)
{
PNode Next = new Node;
Next->_val = val;
PNode New = PHead;
Next->next = New;
Next->pre = New->pre;
New->pre->next = Next;
New->pre=Next;
_size++;
}
void pop_back() { erase(--end()); }
void push_front(const T& val) { insert(begin(), val); }
void pop_front() {
erase(begin());
}
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
PNode New = new Node;
New->_val = val;
PNode per = pos._pNode->pre;
PNode cur = pos._pNode;
New->next = cur;
cur->pre = New;
New->pre = per;
per->next = New;
_size++;
return New;
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
assert(_size > 0);
if (pos._pNode == PHead) pos._pNode = pos._pNode->pre;
PNode per = pos._pNode->pre;
PNode next = pos._pNode->next;
per->next = next;
next->pre = per;
_size--;
delete pos._pNode;
pos._pNode = NULL;
return next;
}
void clear()
{
if (this->PHead)
{
auto cur = begin();
while (end() != cur)
{
cur = erase(cur);
}
delete PHead;
PHead = NULL;
}
}
void swap(list1<T>& l)
{
std::swap(l.PHead, this->PHead);
std::swap(l._size, this->_size);
}
private:
PNode PHead;
size_t _size = 0;
};
更多推荐

所有评论(0)