手撕C++STL的list实现

1. STL list 容器概述

list 是 C++ 标准模板库(STL)中的一个双向链表 容器。与 vectordeque 不同,list 在任意位置的插入和删除操作效率更高,但随机访问效率较低。

核心特性:

  • 双向链表结构(每个节点包含前驱和后继指针)
  • 动态内存分配,节点独立存储
  • 插入/删除时间复杂度:O(1)(已知位置)
  • 随机访问时间复杂度:O(n)

2. list 的基本使用

常用操作示例
cpp 复制代码
#include <iostream>
#include <list>

int main() {
    // 初始化
    std::list<int> lst = {1, 2, 3};

    // 插入元素
    lst.push_back(4);       // 尾部插入
    lst.push_front(0);      // 头部插入
    auto it = lst.begin();
    std::advance(it, 2);    // 移动迭代器
    lst.insert(it, 10);     // 在位置2插入10

    // 删除元素
    lst.pop_back();         // 删除尾部
    lst.pop_front();        // 删除头部
    lst.erase(it);          // 删除迭代器指向元素

    // 遍历
    for (int num : lst) {
        std::cout << num << " ";
    }
    // 输出:2 10 3
    return 0;
}

3. 手撕模拟实现 list

核心结构设计
cpp 复制代码
template <typename T>
class List {
private:
    struct ListNode {
        T data;
        ListNode* prev;
        ListNode* next;
        ListNode(const T& val, ListNode* p = nullptr, ListNode* n = nullptr)
            : data(val), prev(p), next(n) {}
    };

    ListNode* head; // 哨兵头节点
    ListNode* tail; // 哨兵尾节点
    size_t size;

public:
    // 迭代器类
    class Iterator {
    private:
        ListNode* current;
    public:
        Iterator(ListNode* node) : current(node) {}
        T& operator*() { return current->data; }
        Iterator& operator++() {
            current = current->next;
            return *this;
        }
        bool operator!=(const Iterator& other) const {
            return current != other.current;
        }
    };

    // 构造函数
    List() : size(0) {
        head = new ListNode(T());
        tail = new ListNode(T());
        head->next = tail;
        tail->prev = head;
    }

    // 析构函数
    ~List() {
        clear();
        delete head;
        delete tail;
    }

    void push_back(const T& val) {
        ListNode* newNode = new ListNode(val, tail->prev, tail);
        tail->prev->next = newNode;
        tail->prev = newNode;
        size++;
    }

    void pop_back() {
        if (size == 0) return;
        ListNode* last = tail->prev;
        last->prev->next = tail;
        tail->prev = last->prev;
        delete last;
        size--;
    }

    Iterator begin() { return Iterator(head->next); }
    Iterator end() { return Iterator(tail); }

    void clear() {
        while (head->next != tail) {
            pop_back();
        }
    }
};

4. 关键机制解析

  1. 哨兵节点

    头尾哨兵节点(head/tail)不存储数据,仅用于简化边界操作,如 begin() 指向 head->nextend() 指向 tail

  2. 迭代器设计

    封装节点指针,通过重载 operator++operator* 实现链表遍历。

  3. 插入/删除逻辑

    • 插入 :修改相邻节点的指针,例如 push_back 中:

      cpp 复制代码
      newNode->prev = tail->prev; // 新节点前驱指向原尾节点前驱
      newNode->next = tail;       // 新节点后继指向尾哨兵
      tail->prev->next = newNode; // 原尾节点前驱的后继指向新节点
      tail->prev = newNode;       // 尾哨兵前驱指向新节点
    • 删除 :调整相邻节点指针并释放内存(见 pop_back)。


5. 应用场景

  • 频繁在任意位置插入/删除(如 LRU 缓存算法)
  • 不需要随机访问的场景
  • 实现队列(std::queue 默认基于 deque,但可适配 list

总结

list 通过双向链表结构优化了插入删除效率,但牺牲了随机访问性能。理解其底层实现有助于在特定场景下合理选择容器,并加深对 STL 设计思想的理解。

相关推荐
顺心而行...1 小时前
安装 ubuntu 24.04 LTS 单系统教程
开发语言
I_belong_to_jesus1 小时前
LLVM后端入门8:Subtarget支持
c++·llvm
yaoxin5211231 小时前
295. Java Stream API - 选择适用于并行计算的 BinaryOperator
java·开发语言
CHHC18801 小时前
golang 项目依赖备份
开发语言·后端·golang
冬至喵喵1 小时前
RoaringBitmap与传统Bitmap
java·开发语言
Front思2 小时前
Vue3仿美团实现骑手路线规划
开发语言·前端·javascript
Ulyanov2 小时前
PyVista与Tkinter桌面级3D可视化应用实战
开发语言·前端·python·3d·信息可视化·tkinter·gui开发
和你一起去月球2 小时前
动手学Agent应用开发(TS/JS 最简实践指南)
开发语言·javascript·ecmascript·agent·mcp
qq_589568102 小时前
centos6.8版本虚拟机使用过程中的问题解决
数据结构·centos·list·esc键盘