STL源码解析之list(1)

与vector相比,list 的每个元素是一个独立的节点,包含前驱和后继指针。节点可在内存中任意位置,通过指针链接。

一、list与vector对比

操作 vector list
随机访问([i]) O(1) -- 极快 O(n) -- 需要遍历
头部插入/删除 O(n) -- 移动所有后续元素 O(1) -- 只改几个指针
尾部插入/删除 O(1) -- 摊销常数(可能触发扩容) O(1) -- 双向链表尾节点
中间插入/删除 O(n) -- 移动插入点后的元素 O(1) -- 前提是已找到位置(迭代器)
查找给定值 O(n) -- 线性搜索 O(n) -- 线性搜索
排序 可用 std::sort(O(n log n)) 需用成员 sort()(O(n log n))
内存开销 很低(仅存储元素,可能少量预留空间) 高(每个节点额外存储两个指针)
缓存友好性 极好(连续内存,预取) 差(节点分散,缓存缺失多)
  • vector 插入元素可能引起重新分配,使所有迭代器、引用、指针失效。删除元素后,被删元素之后的迭代器失效。

  • list 插入或删除节点,仅影响指向被操作节点的迭代器,其他迭代器始终有效。这是链表的重要优势。

二、源码解析

1)list节点定义

cpp 复制代码
template <class T>
struct __list_node {
  typedef void* void_pointer;
  void_pointer next; 
  void_pointer prev;
  T data;

STL list是一个双向链表,next指向下一个节点,prev指向前一个节点

2)前向遍历与后向遍历

cpp 复制代码
  self& operator++() { 
    node = (link_type)((*node).next);  	
    return *this;
  }
  self operator++(int) { 
    self tmp = *this;
    ++*this;
    return tmp;
  }

  self& operator--() { 
    node = (link_type)((*node).prev);
    return *this;
  }
  self operator--(int) { 
    self tmp = *this;
    --*this;
    return tmp;
  }

cpp 复制代码
std::list<int> lst = {1, 2, 3, 4, 5};
for (auto it = lst.begin(); it != lst.end(); ++it) {
    std::cout << *it << " ";
}
// 输出: 1 2 3 4 5


for (auto it = lst.rbegin(); it != lst.rend(); ++it) {
    std::cout << *it << " ";
}
// 输出: 5 4 3 2 1

3)插入操作

在postion处插入元素

cpp 复制代码
  iterator insert(iterator position, const T& x) {
    link_type tmp = create_node(x);
    // 調整雙向指標,使 tmp 安插進去。
    tmp->next = position.node;
    tmp->prev = position.node->prev;
    (link_type(position.node->prev))->next = tmp;
    position.node->prev = tmp;
    return tmp;
  }

在链表头部插入一个元素

cpp 复制代码
void push_front(const T& x) { insert(begin(), x); }

在链表尾部插入一个元素

cpp 复制代码
void push_back(const T& x) { insert(end(), x); }

4)删除操作

删除position处node

erase 返回:指向被删元素之后的下一个有效元素的迭代器(若已无后续,返回 end())

cpp 复制代码
  iterator erase(iterator position) {
    link_type next_node = link_type(position.node->next);
    link_type prev_node = link_type(position.node->prev);
    prev_node->next = next_node;
    next_node->prev = prev_node;
    destroy_node(position.node);
    return iterator(next_node);
  }

删除链表首部

cpp 复制代码
 void pop_front() { erase(begin()); }

删除链表尾部

cpp 复制代码
  void pop_back() { 
    iterator tmp = end();
    erase(--tmp);
  }

list插入/删除只修改指针,从不拷贝或移动元素本身。对比 vector:当元素类型拷贝/移动成本高时(如包含大量数据的结构),vector 的重新分配或插入/删除操作会付出高昂的元素移动代价。

list本身也有局限性,对比 vector 的主要代价

  • 内存开销大:每个元素多存两个指针(prev/next),对于小对象内存浪费严重。

  • 缓存不友好:节点在堆中分散,遍历时缓存命中率低,速度远慢于 vector。

  • 不支持随机访问:获取第 N 个元素需要 O(n) 遍历。

经验法则 :默认使用 vector,除非你明确需要 list 的上述优点,并且 vector 的缺点(中间插入/删除 O(n)、迭代器失效、大块连续内存)成为实际瓶颈。

相关推荐
蹊黎8 小时前
C++中vector类的部分简单实现
开发语言·c++·visual studio
程序猿追14 小时前
kotlin-retry 接入鸿蒙后,怎样确认它真的会停止重试
开发语言·kotlin·harmonyos
我是人✓14 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
智能直播14 小时前
网络RTMP拉流不卡顿、声音不同步,一文讲透缓冲区、时间戳
开发语言·ffmpeg·php·音视频·视频编解码
墨云安全15 小时前
内网资产无法暴露到公网,怎么做安全检测?2026 年内网渗透测试的落地路径
开发语言·安全·网络安全·渗透测试·php
摇滚侠15 小时前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记
Cc.Y15 小时前
Java零基础入门:深入Java核心:文件IO与序列化全解
java·开发语言·python
xier_ran15 小时前
【infra之路】AI 编译器学习笔记(六):重拾 C++——lambda 与 MLIR 中的回调
c++·笔记·学习
jimy117 小时前
c++的 Type Traits (一)
开发语言·c++
别动我齐刘海17 小时前
Linux 系统调试学习2
linux·运维·c++·人工智能·学习·机器学习·机器人