C++ std::list 详解与模拟实现

1. list 基本概念

std::list是 STL 提供的双向循环链表容器 ,底层内存不是连续的,由一个个独立的节点通过前驱、后继指针链接而成,并且 STL 的 list 采用带头哨兵结点的双向循环链表

vector 是连续的动态数组,list 和 vector 是互补的容器。 list 核心特性:

  1. 不支持随机访问,没有[]下标运算符,不能list[2]访问元素;访问中间元素必须迭代器一步步遍历。
  2. 在已经拿到迭代器的前提下,任意位置插入、删除是 O (1),只需要修改指针指向,不需要挪动数据。
  3. 内存碎片化,每个节点在堆上单独开辟,CPU 缓存命中率差,遍历速度比 vector 慢很多。
  4. 迭代器失效规则:插入元素时所有迭代器都不会失效 ;erase 删除某个节点时,只有被删除节点对应的迭代器失效,其余迭代器依旧有效。vector 插入删除会大片失效迭代器,这是 list 很大的优势。
  5. list 自带成员方法:sort()merge()reverse()unique(),不要使用 std 算法库的 sort,std::sort 要求随机访问迭代器,list 迭代器不满足,只能调用自身的 sort 成员函数。

vector vs list 对比

特性 vector list
内存 连续数组 离散双向链表节点
随机访问 支持 O (1) 不支持
头部 / 中间插入删除 O (n),数据搬移 O (1)(已有迭代器)
迭代器失效 插入 / 删除极易大量失效 插入全部有效;仅 erase 的节点迭代器失效
缓存效率 高,内存连续 低,节点分散堆内存
sort 使用 std::sort 只能调用成员函数 sort ()

2. list 常用接口

复制代码
#include <list>
using namespace std;

void test_list_api()
{
    list<int> lt;
    //头插、尾插
    lt.push_back(10);
    lt.push_back(20);
    lt.push_front(5);

    //头删、尾删
    lt.pop_back();
    lt.pop_front();

    //获取头尾元素
    int front_val = lt.front();
    int back_val = lt.back();

    //大小判空
    lt.size();
    lt.empty();

    //任意位置插入,pos是迭代器
    auto pos = lt.begin();
    lt.insert(pos, 100); //在pos前面插入元素

    //删除迭代器指向节点,返回下一个有效迭代器
    lt.erase(pos);

    //链表专属成员函数
    lt.reverse();      //反转链表
    lt.sort();         //链表排序
    lt.unique();       //去重(需要元素有序)
    list<int> lt2{1,3,5};
    lt.merge(lt2);     //合并两个有序链表,lt2会变空

    //遍历,只能迭代器/范围for
    for(auto it = lt.begin(); it != lt.end(); ++it)
    {
        cout << *it << " ";
    }
    for(auto x : lt)
    {
        cout << x << " ";
    }
}

注意:list 的end()迭代器指向哨兵头结点 ,不是最后一个有效节点;begin 指向第一个有效节点。因为是循环链表,--lt.begin()等价于lt.end()

3. list 底层节点与迭代器原理

list 的节点,保存数据、前驱指针_prev、后继指针_next。 因为内存不连续,原生指针不能作为迭代器。vector 迭代器本质就是T*原生指针;list 迭代器是一个类对象,封装节点指针,重载 ++、--、*、->、!=、== 运算符,模拟指针行为。

迭代器分三类:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。 list 迭代器属于双向迭代器 :支持++it--it,但是不支持it + nit - n

4. list 模拟实现

模拟实现 STL 标准:带头结点双向循环链表,实现核心接口:构造、析构、push_back、push_front、pop_back、pop_front、insert、erase、begin、end、size、empty,实现迭代器类。

复制代码
#include <iostream>
using namespace std;

//链表节点
template<class T>
struct ListNode
{
    T _data;
    ListNode<T>* _prev;
    ListNode<T>* _next;

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

//list迭代器类
template<class T, class Ref, class Ptr>
struct ListIterator
{
    typedef ListNode<T> Node;
    typedef ListIterator<T, Ref, Ptr> Self;

    Node* _node;

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

    //重载*,返回引用
    Ref operator*()
    {
        return _node->_data;
    }

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

    //前置++
    Self& operator++()
    {
        _node = _node->_next;
        return *this;
    }

    //后置++
    Self operator++(int)
    {
        Self temp = *this;
        _node = _node->_next;
        return temp;
    }

    //前置--
    Self& operator--()
    {
        _node = _node->_prev;
        return *this;
    }

    //后置--
    Self operator--(int)
    {
        Self temp = *this;
        _node = _node->_prev;
        return temp;
    }

    bool operator!=(const Self& it) const
    {
        return _node != it._node;
    }

    bool operator==(const Self& it) const
    {
        return _node == it._node;
    }
};

//模拟list容器
template<class T>
class List
{
public:
    typedef ListNode<T> Node;
    //普通迭代器、const迭代器
    typedef ListIterator<T, T&, T*> iterator;
    typedef ListIterator<T, const T&, const T*> const_iterator;

private:
    Node* _head; //哨兵头结点

    //创建哨兵头结点
    void CreateHead()
    {
        _head = new Node;
        _head->_next = _head;
        _head->_prev = _head;
    }

public:
    //构造函数
    List()
    {
        CreateHead();
    }

    //拷贝构造
    List(const List<T>& lt)
    {
        CreateHead();
        for(auto& x : lt)
        {
            push_back(x);
        }
    }

    //赋值运算符重载
    List<T>& operator=(List<T> lt)
    {
        swap(_head, lt._head);
        return *this;
    }

    //析构
    ~List()
    {
        clear();
        delete _head;
        _head = nullptr;
    }

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

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

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

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

    bool empty() const
    {
        return _head->_next == _head;
    }

    size_t size() const
    {
        size_t cnt = 0;
        auto it = begin();
        while(it != end())
        {
            cnt++;
            ++it;
        }
        return cnt;
    }

    //在pos迭代器之前插入val,返回新元素迭代器
    iterator insert(iterator pos, const T& val)
    {
        Node* cur = pos._node;
        Node* newNode = new Node(val);
        Node* prevNode = cur->_prev;

        prevNode->_next = newNode;
        newNode->_prev = prevNode;
        newNode->_next = cur;
        cur->_prev = newNode;

        return iterator(newNode);
    }

    //尾插
    void push_back(const T& val)
    {
        insert(end(), val);
    }

    //头插
    void push_front(const T& val)
    {
        insert(begin(), val);
    }

    //删除pos位置节点,返回下一个有效迭代器
    iterator erase(iterator pos)
    {
        Node* delNode = pos._node;
        Node* prevNode = delNode->_prev;
        Node* nextNode = delNode->_next;

        prevNode->_next = nextNode;
        nextNode->_prev = prevNode;
        delete delNode;

        return iterator(nextNode);
    }

    //尾删
    void pop_back()
    {
        erase(--end());
    }

    //头删
    void pop_front()
    {
        erase(begin());
    }

    //清空所有有效节点,哨兵结点保留
    void clear()
    {
        iterator it = begin();
        while(it != end())
        {
            it = erase(it);
        }
    }

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

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

//测试模拟实现
void test_mylist()
{
    List<int> mylt;
    mylt.push_back(1);
    mylt.push_back(2);
    mylt.push_front(0);

    for(auto x : mylt)
    {
        cout << x << " ";
    }
    cout << endl;

    mylt.pop_back();
    mylt.pop_front();

    for(auto x : mylt)
    {
        cout << x << " ";
    }
}

int main()
{
    test_mylist();
    return 0;
}

5. 关键细节解析

  1. 哨兵头结点的好处 :空链表的时候_head->_next = _headbegin() = end(),插入删除不需要单独判断链表是否为空,代码逻辑统一。
  2. 迭代器模板参数:ListIterator<T,Ref,Ptr>,Ref 代表引用类型,Ptr 代表指针;一份迭代器类模板,实例化出普通迭代器和 const 迭代器,不用写两套代码。
  3. insert:list 的 insert 不会失效任何迭代器。插入节点,原有节点指针只是修改指向,原有迭代器封装的节点地址不变。
  4. erase:只有被删掉节点的迭代器失效,其他迭代器保持有效。erase 返回下一个有效迭代器,遍历删除元素标准写法:it = lt.erase(it);,不要lt.erase(it); ++it;,会迭代器失效。
  5. push_back、push_front 复用 insert,不需要重复写指针操作逻辑。

6. list 使用坑点

  1. ❌不要用std::sort(lt.begin(), lt.end()),std::sort 要求随机访问迭代器,list 只有双向迭代器,编译报错;要用lt.sort()成员函数。
  2. ❌不能随机访问,不支持it + 3,双向迭代器只允许++it--it
  3. list 每个节点 new,频繁增删小对象会产生大量内存碎片。
  4. unique 去重只会去掉相邻重复元素,调用 unique 之前必须先 sort 排序。
  5. merge 合并链表,要求两个链表都是有序;merge 之后被合并的链表会被置空。

7. vector 与 list 选型

  • 如果需要大量随机访问,少量头部中间增删 → 使用 vector。
  • 如果大量在中间、头部插入删除,不在乎遍历速度 → 使用 list。
  • 实际开发中 vector 使用频率远高于 list,list 缓存失效带来的性能损耗往往比数组搬移更大。
相关推荐
帅气的小峰4 小时前
pybind11与nanobind可以共存吗?如何迁移?
c++·python·cuda·推理引擎
syker4 小时前
SuperNova Compiler v4.2.9 完整开发手册
c++
zhy295634 小时前
在 QAIRT Genie SDK 上部署 Llama 3.2 1B 模型:从环境准备到 C++ 推理
开发语言·c++·llama
云上飞476369624 小时前
Windows WSL2 + Docker 环境下NVIDIA PhysicsNeMo 安装指南
windows·docker·physicsai·physicsnemo
richard_yuu4 小时前
JSON vs XML vs 二进制:3种序列化终极选型
xml·c++·qt·学习·json
weixin_6684 小时前
取消Windows 11 默认精简的鼠标右键文件夹菜单
windows·计算机外设
love530love5 小时前
彻底清理 Windows 右键“打开方式“中的重复/失效程序项(PyCharm 多版本残留实战 + 自动化脚本)
运维·人工智能·windows·pycharm·jetbrains·toolbox
郝学胜-神的一滴5 小时前
并查集深度入门:从玄学抽象到 QuickFind & QuickUnion 源码实战
数据结构·c++·python·程序人生·算法·软件开发
ShineWinsu13 小时前
对于C++:IO流状态的详细解析
c++·面试·io·cin·io流状态·goodbit·failbit
Lzg_na15 小时前
constexpr的优势
c++