news 2026/9/30 9:27:02

C++ STL:list 底层结构、模拟实现与 vector 对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ STL:list 底层结构、模拟实现与 vector 对比

1. list 的介绍

list是 STL 中非常重要的序列式容器之一,它可以在常数时间 O(1)内在任意位置进行插入和删除元素。

list 的底层结构是带头结点的双向循环链表:

  • 每个节点包含一个数据域data、一个前驱指针prev和一个后继指针next;
  • 头结点(哨兵节点)不保存有效数据,它的next指向第一个有效节点,prev指向最后一个有效节点;
  • 空表时,哨兵节点的next和prev都指向自己。

由于底层是链表,list 支持高效的任意位置插入/删除,但不支持随机访问,访问第 i 个元素的复杂度是 O(N)。


2. list 的使用

list 接口很多,学习时应该先掌握“如何正确使用”,再去研究背后的实现原理。下面是 list 中常见的重要接口。

2.1 list 的构造

接口说明
list (size_type n, const value_type& val = value_type())构造包含 n 个值为 val 的元素的 list
list()构造空的 list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造 list

使用示例:

#include<iostream>#include<list>usingnamespacestd;intmain(){list<int>l1;// 空 listlist<int>l2(4,100);// {100, 100, 100, 100}list<int>l3(l2);// 拷贝构造list<int>l4(l2.begin(),l2.end());// 迭代器区间构造list<int>l5{1,2,3,4,5};// C++11 initializer_list 构造return0;}

2.2 list 的迭代器

此处可以暂时把迭代器理解成一个指针,该指针指向 list 中的某个节点。

接口说明
begin + end返回第一个元素的迭代器 + 最后一个元素下一个位置的迭代器
rbegin + rend反向迭代器:rbegin即end位置,rend即begin位置

注意:

  1. begin与end是正向迭代器,对迭代器执行++,迭代器向后移动;
  2. rbegin(end)与rend(begin)是反向迭代器,对迭代器执行++,迭代器向前移动。

使用示例:

list<int>l{1,2,3,4,5};// 正向遍历for(autoit=l.begin();it!=l.end();++it)cout<<*it<<" ";// 反向遍历for(autoit=l.rbegin();it!=l.rend();++it)cout<<*it<<" ";// 范围 for(本质也是 begin()/end())for(auto&e:l)cout<<e<<" ";

2.3 list capacity

接口说明
empty检测 list 是否为空,是返回 true,否则返回 false
size返回 list 中有效节点的个数

2.4 list element access

接口说明
front返回 list 的第一个节点中值的引用
back返回 list 的最后一个节点中值的引用

2.5 list modifiers

接口说明
push_front在 list 首元素前插入值为 val 的元素
pop_front删除 list 中第一个元素
push_back在 list 尾部插入值为 val 的元素
pop_back删除 list 中最后一个元素
insert在 list 的 position 位置插入值为 val 的元素
erase删除 list 的 position 位置的元素
swap交换两个 list 中的元素
clear清空 list 中的有效元素

2.6 list 的迭代器失效

因为 list 的底层结构是带头结点的双向循环链表,所以:

  • 插入操作不会导致 list 的迭代器失效;
  • 删除操作只会使指向被删除节点的那个迭代器失效,其他迭代器不受影响。

经典错误示例:删除节点后还继续使用已经失效的迭代器。

voidTestListIterator1(){intarray[]={1,2,3,4,5,6,7,8,9,0};list<int>l(array,array+sizeof(array)/sizeof(array[0]));autoit=l.begin();while(it!=l.end()){// erase() 执行后,it 所指向的节点已被删除,因此 it 已经失效l.erase(it);++it;// 对失效迭代器 ++,未定义行为!}}

改正方式:利用后置++先保存旧迭代器、再前进、最后删除旧节点。

voidTestListIterator(){intarray[]={1,2,3,4,5,6,7,8,9,0};list<int>l(array,array+sizeof(array)/sizeof(array[0]));autoit=l.begin();while(it!=l.end()){l.erase(it++);// 等价于 it = l.erase(it);}}

3. list 的模拟实现

要模拟实现 list,必须熟悉它的底层结构以及每个接口的含义。

3.1 整体结构:哨兵节点 + 双向循环链表

下面是本次审查的手写实现的核心结构(略有删减,行号对应原始List.h):

#pragmaonce#include<iostream>#include<assert.h>usingnamespacestd;namespacetx_list{template<typenameT>classlist_Node{friendclasslist<T>;public:list_Node(constT&value=T()):data(value),next(nullptr),prev(nullptr){}private:T data;// 数据域list_Node<T>*next;// 后继指针list_Node<T>*prev;// 前驱指针};template<typenameT>classlist{typedeflist_Node<T>Node;public:list(){empty_init();}// 拷贝构造list(constlist<T>&l){empty_init();for(auto&e:l)push_back(e);}// initializer_list 构造list(initializer_list<T>il){empty_init();for(auto&e:il)push_back(e);}// 拷贝赋值(copy-and-swap 惯用法)list<T>&operator=(list<T>lt){swap(lt);return*this;}~list(){clear();delete_head;_head=nullptr;}voidempty_init(){_head=newNode(T());// 创建哨兵节点_head->next=_head;// 哨兵的 next 指向自己_head->prev=_head;// 哨兵的 prev 指向自己_size=0;}private:Node*_head;// 哨兵节点size_t _size;};}

设计要点:

  1. 哨兵节点(头结点):不存有效数据,让所有插入/删除操作都不需要特判“空表/首尾”情况;
  2. 循环链表:_head->next是第一个节点,_head->prev是最后一个节点;
  3. copy-and-swap 赋值:operator=(list<T> lt)按值传参先拷贝一份,再交换内部指针,天然保证异常安全和自赋值安全。

3.2 迭代器设计:list 的迭代器不是原生指针

vector底层是连续空间,迭代器可以用原生指针T*;但list的节点在内存中不连续,++/--必须“跳节点”,所以list 的迭代器是对节点指针的封装。

一个非常巧妙的做法是:用Ref和Ptr两个模板参数,让同一个模板同时生成iterator和const_iterator:

template<classT,classRef,classPtr>structlist_iterator{typedeflist_Node<T>Node;typedeflist_iterator<T,Ref,Ptr>Self;Node*_node;list_iterator(Node*node):_node(node){}Refoperator*(){return_node->_data;}Ptroperator->(){return&_node->_data;}Self&operator++(){_node=_node->_next;return*this;}Self&operator--(){_node=_node->_prev;return*this;}Selfoperator++(int){Selftmp(*this);_node=_node->_next;returntmp;}booloperator!=(constSelf&s)const{return_node!=s._node;}booloperator==(constSelf&s)const{return_node==s._node;}};

然后在list里 typedef:

typedeflist_iterator<T,T&,T*>iterator;// 普通迭代器typedeflist_iterator<T,constT&,constT*>const_iterator;// const 迭代器

3.3 关键接口实现

insert:在 pos 之前插入
voidinsert(iterator pos,constT&value){Node*newNode=newNode(value);Node*cur=pos._node;Node*prev=cur->prev;// 双向链表四步链接:prev <-> newNode <-> curprev->next=newNode;newNode->prev=prev;newNode->next=cur;cur->prev=newNode;_size++;}
erase:删除 pos 指向的节点
voiderase(iterator pos){assert(pos!=end());// 不能删除哨兵节点Node*prev=pos._node->prev;Node*next=pos._node->next;prev->next=next;next->prev=prev;deletepos._node;_size--;}
复用 insert/erase 实现 push/pop
voidpush_back(constT&value){insert(end(),value);}// 在哨兵前插入,即尾插voidpush_front(constT&x){insert(begin(),x);}// 在首节点前插入voidpop_back(){erase(--end());}// end() 前一个即尾节点

4. list 与 vector 的对比

vector 与 list 都是 STL 中非常重要的序列式容器,由于两者底层结构不同,导致其特性及应用场景也不同。

维度vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素 O(1)不支持随机访问,访问某个元素 O(N)
插入和删除任意位置插入/删除效率低,需搬移元素 O(N);插入时可能增容(开新空间、拷贝元素、释放旧空间)任意位置插入/删除效率高,不需搬移元素,O(1)
空间利用率底层连续空间,不易造成内存碎片,空间利用率高,缓存利用率高节点动态开辟,小节点易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生指针对原生指针(节点指针)进行封装
迭代器失效插入可能因扩容使所有迭代器失效;删除时当前迭代器需重新赋值插入不导致迭代器失效;删除只使当前迭代器失效,其他不受影响
使用场景需要高效存储、支持随机访问、不关心插入删除效率大量插入和删除操作、不关心随机访问

5. 总结

  1. list 的底层结构是带头结点的双向循环链表,因此任意位置插入/删除是 O(1),但不支持随机访问(O(N))。
  2. list 的迭代器是对节点指针的封装,++/--实际是沿next/prev指针移动;用Ref/Ptr模板参数可以让一套代码同时生成iterator和const_iterator。
  3. 反向迭代器可以包装正向迭代器实现:反向++= 正向--。
  4. list 的迭代器失效规则:插入不失效;删除只使“被删节点”对应的迭代器失效。删除遍历时要写l.erase(it++);。
  5. 手写 list 的三个高频坑(本次审查发现的阻塞级问题):
    • erase返回void却写了it = erase(it),无法编译 ——erase应返回后继迭代器;
    • 迭代器访问节点私有成员但忘了声明友元;
    • 后置--误写为返回Self&,返回局部对象引用(悬垂引用)。
  6. vector vs list:随机访问、连续存储选vector;频繁任意位置插入删除选list。

参考资料

  • cplusplus.com - list
  • cppreference.com - std::list
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 9:26:46

TFServing性能调优:从单实例瓶颈到十万QPS微服务架构实践

简介&#xff1a;这份 PDF 围绕 TFServing 吞吐量性能瓶颈&#xff0c;提出微服务架构层面的系统性调优方案&#xff0c;面向具备一定编程基础、关注机器学习模型部署与高并发服务的研发人员和技术管理人员。文档正文从 TFServing 的工作原理与架构组成切入&#xff0c;针对 10…

作者头像 李华
网站建设 2026/9/30 9:26:10

SiamRPN单目标追踪实战:从原理到复现的完整指南

1. 为什么现在还要回头啃 SiamRPN 这篇“老论文”如果你这两年才入坑单目标追踪&#xff08;Visual Object Tracking&#xff09;&#xff0c;大概率一上来接触的就是 Transformer 系或者各种端到端的新框架&#xff0c;SiamRPN 这个名字可能只在综述的引用列表里扫到过。但我自…

作者头像 李华
网站建设 2026/9/30 9:25:43

AI进课堂不只是讲题:备课、互动、批改与反馈的课堂协作者实践

1. 从“讲题工具”到“课堂协作者”的认知转变1.1 一个被窄化了的普遍印象“AI进课堂”这件事&#xff0c;过去两年我接触过不少一线教师和教研员&#xff0c;发现一个特别有意思的现象&#xff1a;绝大多数人第一次听到这个说法&#xff0c;脑子里蹦出来的画面几乎都一样——学…

作者头像 李华
网站建设 2026/9/30 9:24:22

AI数据中心电源 OCP Open Rack V3 48V 5.5kW PSU 设计规范

Open Rack V3 48V 5.5kW 整流器规范由Meta贡献到社区,用于定义 Open Rack V3 高功率机架(High Power Rack, HPR)中 48V 整流器(rectifier, PSU / Power Supply Unit)的技术要求。规范对象为插入 48V 电源架(power shelf)的单相整流模块,单机额定输出功率 5.5kW,电源架…

作者头像 李华
网站建设 2026/9/30 9:23:31

深入理解C++ vector:底层实现、扩容机制与迭代器失效陷阱

1. 先聊清楚 vector 的“性格”——底层实现与内存模型上一篇文章我们把 STL 的容器体系整体过了一遍&#xff0c;这一篇专门把 vector 拎出来聊透。之所以把 vector 放在第二篇单独讲&#xff0c;原因很简单&#xff1a;它是 STL 里使用频率最高、同时也是最容易产生隐性性能问…

作者头像 李华