C++ STL list 原理到模拟实现

本文将从list的基础介绍、常用接口使用、迭代器失效问题、模拟实现带你彻底吃透list容器。


一、list 容器基础介绍

1.1 底层结构

list的底层是带头结点的双向循环链表,这是它所有特性的根源:

  • 每个节点包含数据域 + 前驱指针 + 后继指针
  • 链表首尾相连,形成环状结构
  • 不占用连续内存空间,节点动态开辟与释放

1.2 核心迭代器边界

  • begin():指向第一个有效数据节点
  • end():指向头结点(最后一个元素的下一个位置)
  • 支持正向迭代器反向迭代器,遍历更灵活

二、list 常用接口使用详解

2.1 构造函数

list提供 4 种常用构造方式,覆盖空对象、拷贝、区间、指定值初始化:

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

2.2 迭代器使用

迭代器可暂时理解为指向节点的指针,是访问 list 的核心工具:

  1. 正向迭代器
    • begin():首元素迭代器
    • end():尾元素下一个位置迭代器
    • ++:向后移动迭代器
  2. 反向迭代器
    • rbegin():等价于end(),反向起始
    • rend():等价于begin(),反向结束
    • ++:向前移动迭代器

2.3 容量操作

  • empty():判断 list 是否为空,空返回 true
  • size():返回有效节点个数

2.4 元素访问

list不支持随机访问 (无[]at),只能访问首尾元素:

  • front():返回第一个元素的引用
  • back():返回最后一个元素的引用

2.5 增删改查

这是list最高效的操作,时间复杂度 O (1):

函数 功能
push_front(val) 头插
pop_front() 头删
push_back(val) 尾插
pop_back() 尾删
insert(pos, val) pos 位置插入 val
erase(pos) 删除 pos 位置元素
swap(list) 交换两个 list
clear() 清空有效元素

三、list 迭代器失效问题

迭代器失效本质:迭代器指向的节点被销毁,变成野指针list迭代器失效规则:

  1. 插入操作:不会导致任何迭代器失效
  2. 删除操作仅失效被删除节点的迭代器,其他迭代器不受影响

错误示例

cpp 复制代码
void TestListIterator1() {
    int array[] = {1,2,3,4,5,6,7,8,9,0};
    list<int> l(array, array+sizeof(array)/sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end()) {
        l.erase(it); // 删除后it失效
        ++it;        // 对失效迭代器++,未定义行为
    }
}

正确写法

cpp 复制代码
void TestListIterator() {
    int array[] = {1,2,3,4,5,6,7,8,9,0};
    list<int> l(array, array+sizeof(array)/sizeof(array[0]));
    auto it = l.begin();
    while (it != l.end()) {
        // 后置++:先传旧迭代器删除,再让it指向下一节点
        l.erase(it++);
        // 或 it = l.erase(it);
    }
}

四、list 模拟实现

1. 节点结构

cpp 复制代码
template<class T>
struct list_node {
    list_node* _next;
    list_node* _prev;
    T _data;

    list_node(const T& val = T())
        :_next(nullptr)
        ,_prev(nullptr)
        ,_data(val)
    {}
};

2. 迭代器(重点:三个模板参数)

cpp 复制代码
template<class T, class Ref, class Ptr>
struct list_iterator {
    typedef list_node<T> Node;
    typedef list_iterator self;

    Node* _node;

    list_iterator(Node* node) :_node(node) {}

    self& operator++() {
        _node = _node->_next;
        return *this;
    }

    self& operator--() {
        _node = _node->_prev;
        return *this;
    }

    Ref operator*() {
        return _node->_data;
    }

    Ptr operator->() {
        return &_node->_data;
    }

    bool operator==(const self& s) const {
        return _node == s._node;
    }

    bool operator!=(const self& s) const {
        return _node != s._node;
    }
};

3. list 本体

cpp 复制代码
template<class T>
class list
{
public:
    typedef list_node<T> Node;

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


    list()
    {
        empty_init();
    }

    ~list()
    {
        clear();
        delete _head;
        _head = nullptr;
    }


    iterator begin()
    {
        return iterator(_head->_next);
    }

    iterator end()
    {
        return iterator(_head);
    }

    const_iterator begin() const
    {
        return const_iterator(_head->_next);
    }

    const_iterator end() const
    {
        return const_iterator(_head);
    }

    const_iterator cbegin() const
    {
        return const_iterator(_head->_next);
    }

    const_iterator cend() const
    {
        return const_iterator(_head);
    }


    size_t size() const
    {
        return _size;
    }

    bool empty() const
    {
        return _size == 0;
    }

 
    T& front()
    {
        return *begin();
    }

    const T& front() const
    {
        return *cbegin();
    }

    T& back()
    {
        return *(--end());
    }

    const T& back() const
    {
        return *(--cend());
    }


    void push_back(const T& x)
    {
        Node* newnode = new Node(x);
        Node* tail = _head->_prev;

        tail->_next = newnode;
        newnode->_prev = tail;
        newnode->_next = _head;
        _head->_prev = newnode;

        _size++;
    }

    void pop_back()
    {
        erase(--end());
    }


    void push_front(const T& x)
    {
        insert(begin(), x);
    }

    void pop_front()
    {
        erase(begin());
    }


    iterator insert(iterator pos, const T& x)
    {
        Node* cur = pos._node;
        Node* prev = cur->_prev;
        Node* newnode = new Node(x);

        prev->_next = newnode;
        newnode->_prev = prev;
        newnode->_next = cur;
        cur->_prev = newnode;

        _size++;
        return iterator(newnode);
    }


    iterator erase(iterator pos)
    {
        Node* cur = pos._node;
        Node* prev = cur->_prev;
        Node* next = cur->_next;

        prev->_next = next;
        next->_prev = prev;

        delete cur;
        _size--;

        return iterator(next);
    }


    void clear()
    {
        iterator it = begin();
        while (it != end())
        {
            it = erase(it);
        }
    }

private:
    // 空链表初始化
    void empty_init()
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
        _size = 0;
    }

private:
    Node* _head;
    size_t _size;
};

五、list 与 vector 核心对比

两者同为序列容器,底层结构决定性能差异

维度 vector list
底层结构 动态顺序表(连续空间) 带头双向循环链表
随机访问 支持,O (1) 不支持,O (N)
插入删除 任意位置效率低,O (N),需搬移 + 增容 任意位置效率高,O (1),无需搬移
空间利用率 连续内存,碎片少,缓存命中率高 节点动态开辟,碎片多,缓存低
迭代器 原生态指针 封装节点指针
迭代器失效 插入可能全失效;删除当前失效 插入不失效;删除仅当前失效
适用场景 需随机访问、少插入删除 大量插入删除、不关心随机访问

六、总结

  1. list双向循环链表,插入删除效率极高,不支持随机访问
  2. 迭代器++分方向:正向向后,反向向前
  3. 迭代器仅在删除当前节点时失效,插入不会失效
  4. 频繁插入删除选list,需要随机访问选vector
相关推荐
Highcharts.js3 小时前
企业级可视化生态系统|关于Highcharts集成的前端框架、后端编程语言与生态
开发语言·javascript·python·前端框架·编辑器·编程语言·highcharts
君义_noip3 小时前
信息学奥赛一本通 4149:【GESP2509七级】连通图 | 洛谷 P14077 [GESP202509 七级] 连通图
c++·图论·gesp·信息学奥赛
小肝一下3 小时前
每日两道力扣,day7
数据结构·c++·算法·leetcode·双指针·hot100·接雨水,四数之和
学嵌入式的小杨同学3 小时前
STM32 进阶封神之路(四十)FreeRTOS 队列、信号量、互斥锁精讲|任务通信、同步、资源保护(超详细图文版)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构
A懿轩A3 小时前
【2026 最新】JDK 下载与安装:在 macOS 下使用 Homebrew 和 jenv 完美管理多版本 JDK
java·开发语言·jdk·mac
Wang ruoxi3 小时前
Pygame小游戏——扫雷
开发语言·python·pygame
2401_892070981 天前
【Linux C++ 日志系统实战】LogFile 日志文件管理核心:滚动策略、线程安全与方法全解析
linux·c++·日志系统·日志滚动
yuzhuanhei1 天前
Visual Studio 配置C++opencv
c++·学习·visual studio
Wenweno0o1 天前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang