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 缓存失效带来的性能损耗往往比数组搬移更大。
相关推荐
Tairitsu_H11 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
HugoStudio_SWAN11 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活
会周易的程序员13 小时前
5Draft(五帝)测试报告
服务器·c++·分布式·raft·共识算法·共识·算力服务器
UIU11413 小时前
scanf与cout的误区:探究其内部的机制
c++·学习·c#·scanf
闻缺陷则喜何志丹13 小时前
【栈 运算系统】P3719 [AHOI2017初中组] rexp|普及+
c++·算法··洛谷·运算系统
bnmoel14 小时前
C++ 基础入门篇(一):命名空间,输入&输出,缺省参数,函数重载
c++·函数重载·语法·命名空间·缺省参数
Zixhy14 小时前
多点巡检机器人项目技术总结
linux·c++·机器人·自动驾驶
技术不好的崎鸣同学15 小时前
第一章:PEB结构解析与内存定位
windows·安全·系统安全
Pointer Pursuit16 小时前
C++11特性(二)
前端·c++·算法
tudousisi22217 小时前
CSP 第3题训练 Day 5|CSP202509C HTTP 头信息|Blog A
c++