【初阶C++】list

C++ std::list 深度详解|接口、迭代器失效、模拟实现、与 vector 对比

本文完整整理 STL list 全套知识点,底层带头双向循环链表,常用接口、迭代器失效重点、手写模拟实现、反向迭代器原理、和 vector 对比,面试高频考点,可直接发布博客。

std::list 底层:带头哨兵节点的双向循环链表 。每个节点独立堆开辟,节点存_prev前驱指针、_next后继指针、数据。end()返回哨兵头结点迭代器,begin()返回第一个有效节点。

一、list 常用接口

1.1 构造函数

表格

构造函数 功能说明
list() 构造空 list
list(size_type n, const value_type& val) 构造 n 个 val 元素
list(const list& x) 拷贝构造,深拷贝
list(InputIterator first, InputIterator last) 迭代器区间[first,last)构造

示例代码

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

void TestListConstruct()
{
    list<int> l1;
    list<int> l2(5,10);
    list<int> l3(l2);
    int arr[] = {1,2,3,4};
    list<int> l4(arr, arr+sizeof(arr)/sizeof(int));
}

1.2 迭代器接口

表格

接口 说明
begin() / end() 正向迭代器;begin()第一个有效节点;end()哨兵头结点
rbegin() / rend() 反向迭代器;rbegin()等价end()rend()等价begin()

注意:

  1. 正向迭代器++向后走(向链表尾部);
  2. 反向迭代器++向前走(向链表头部)
cpp 复制代码
void TestListIterator()
{
    list<int> l = {1,2,3,4,5};
    //正向遍历
    list<int>::iterator it = l.begin();
    while(it != l.end())
    {
        cout << *it << " ";
        ++it;
    }
    //反向遍历
    list<int>::reverse_iterator rit = l.rbegin();
    while(rit != l.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
}

1.3 容量与元素访问

表格

接口 说明
size() 有效节点个数
empty() 判空
front() 返回第一个元素引用
back() 返回最后一个元素引用

❗list不支持随机访问,没有operator[]下标运算符 !不能写l[0],编译报错。

1.4 修改接口 modifiers

表格

接口 功能
push_front(val) 头插元素
pop_front() 头删
push_back(val) 尾插
pop_back() 尾删
insert(pos,val) 在 pos 迭代器之前 插入元素;插入不会让任何迭代器失效
erase(pos) 删除 pos 位置节点;仅被删除的迭代器失效,其他全部有效;返回下一个有效迭代器
swap(l2) 交换两个 list,只交换哨兵头结点指针,O (1)
clear() 清空全部有效节点;所有迭代器失效
cpp 复制代码
void TestListMod()
{
    list<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_front(0);

    auto pos = l.begin();
    l.insert(pos, 100); //pos迭代器仍然有效,insert不会失效迭代器

    pos = l.erase(pos); //erase后原来pos失效,要用返回值接收新迭代器
}

二、list 迭代器失效(面试超级重点)

vector:连续数组,扩容全部迭代器失效;erase 之后当前及后面迭代器失效。 list 底层双向链表,节点独立堆开辟,插入只修改指针,不移动、不释放原有节点内存

✅list 迭代器失效规则:

  1. insert、push_back、push_front:插入操作,不会造成任何迭代器失效!
  2. erase:只有被删除节点对应的迭代器失效,其他所有迭代器、引用、指针依旧有效
  3. clear():销毁全部节点,所有迭代器全部失效

❌错误遍历删除代码

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

✅两种正确写法

cpp 复制代码
//写法1:接收erase返回值(推荐)
void TestGoodErase1()
{
    int array[] = {1,2,3,4,5,6};
    list<int> l(array, array+sizeof(array[0]));
    auto it = l.begin();
    while(it != l.end())
    {
        it = l.erase(it); //接收erase返回的下一个有效迭代器
    }
}

//写法2:后置++,it++先拷贝旧值再自增
void TestGoodErase2()
{
    int array[] = {1,2,3,4,5,6};
    list<int> l(array, array+sizeof(array[0]));
    auto it = l.begin();
    while(it != l.end())
    {
        l.erase(it++);
    }
}

面试问答:为什么 list 插入不会迭代器失效? list 每个节点独立在堆上,插入只是修改相邻节点的 prev、next 指针,原有节点内存地址完全不变,指向旧节点的迭代器依旧有效。

三、list 模拟实现完整代码 + 逐段剖析

底层三部分:

  1. list_node<T> 链表节点结构体;
  2. list_iterator<T>迭代器类,封装节点指针,重载* -> ++ -- != ==
  3. list<T>容器类,保存哨兵头节点_head
  4. 反向迭代器适配器ReverseIterator
cpp 复制代码
#include <cassert>
#include <algorithm>

namespace bit
{
    //1.链表节点
    template<class T>
    struct list_node
    {
        list_node<T>* _prev;
        list_node<T>* _next;
        T _data;

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

    //2.list迭代器封装节点指针
    template<class T, class Ref, class Ptr>
    struct list_iterator
    {
        typedef list_node<T> Node;
        typedef list_iterator<T, Ref, Ptr> Self;

        Node* _node;

        list_iterator(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 tmp(*this);
            _node = _node->_next;
            return tmp;
        }
        //前置--
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }
        //后置--
        Self operator--(int)
        {
            Self tmp(*this);
            _node = _node->_prev;
            return tmp;
        }

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

    // =========反向迭代器适配器模板(重点typename考点)=========
    template<class Iterator>
    class ReverseListIterator
    {
    public:
        // typename告诉编译器 Iterator::Ref 是一个类型,不是静态成员变量
        typedef typename Iterator::Ref Ref;
        typedef typename Iterator::Ptr Ptr;
        typedef ReverseListIterator<Iterator> Self;

        Iterator _it;
    public:
        ReverseListIterator(Iterator it)
            :_it(it)
        {}

        Ref operator*()
        {
            Iterator temp(_it);
            --temp;
            return *temp;
        }
        Ptr operator->()
        {
            return &(operator*());
        }

        //反向迭代器++等价正向迭代器--
        Self& operator++()
        {
            --_it;
            return *this;
        }
        Self operator++(int)
        {
            Self tmp(*this);
            --_it;
            return tmp;
        }
        //反向迭代器--等价正向迭代器++
        Self& operator--()
        {
            ++_it;
            return *this;
        }
        Self operator--(int)
        {
            Self tmp(*this);
            ++_it;
            return tmp;
        }

        bool operator!=(const Self& l) const
        {
            return _it != l._it;
        }
        bool operator==(const Self& l) const
        {
            return _it == l._it;
        }
    };

    // =========list容器本体=========
    template<class T>
    class list
    {
    public:
        typedef list_node<T> Node;
        typedef list_iterator<T, T&, T*> iterator;
        typedef list_iterator<T, const T&, const T*> const_iterator;

        typedef ReverseListIterator<iterator> reverse_iterator;
        typedef ReverseListIterator<const_iterator> const_reverse_iterator;

        //构造:创建哨兵头结点,双向自环
        list()
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
        }

        //迭代器区间构造
        template<class InputIterator>
        list(InputIterator first, InputIterator last)
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
            while(first != last)
            {
                push_back(*first);
                ++first;
            }
        }

        //拷贝构造
        list(const list<T>& l)
        {
            _head = new Node;
            _head->_next = _head;
            _head->_prev = _head;
            auto it = l.begin();
            while(it != l.end())
            {
                push_back(*it);
                ++it;
            }
        }

        //现代swap拷贝赋值
        list<T>& operator=(list<T> l)
        {
            swap(l);
            return *this;
        }

        //析构,释放全部节点,释放哨兵头结点
        ~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);
        }

        reverse_iterator rbegin()
        {
            return reverse_iterator(end());
        }
        reverse_iterator rend()
        {
            return reverse_iterator(begin());
        }

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

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

        // insert 在pos迭代器之前插入val,list核心函数
        iterator insert(iterator pos, const T& val)
        {
            Node* newNode = new Node(val);
            Node* cur = pos._node;
            Node* prev = cur->_prev;

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

            return iterator(newNode);
        }

        // erase删除pos节点,返回下一个迭代器
        iterator erase(iterator pos)
        {
            assert(pos != end());
            Node* del = pos._node;
            Node* prev = del->_prev;
            Node* next = del->_next;

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

            return iterator(next);
        }

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

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

        void swap(list<T>& l)
        {
            std::swap(_head, l._head);
        }

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

    private:
        Node* _head; //双向循环链表哨兵头结点
    };
}

模拟实现高频面试考点剖析

  1. 迭代器为什么是类,不是原生指针? list 节点内存离散,不能直接用裸指针做迭代器,封装一层类,重载*、->、++、--、==、!=,对外提供和指针一样的行为。
  2. typename Iterator::Reftypename干什么? 模板里面编译器不知道Iterator::Ref类型名 还是静态成员变量,typename强制告诉编译器这是一个类型,否则编译报错。反向迭代器必考。
  3. list 的insert为什么迭代器不会失效? 只修改节点内部 prev/next 指针,原有节点堆内存地址完全不变。
  4. list 没有operator[]? 底层链表,随机访问需要遍历,O (N),标准直接不提供下标。想要访问第 n 个元素只能迭代器循环走 n 步。
  5. list 的size()模拟实现版本需要遍历统计;STL 标准 list 内部存了_size 成员变量 O (1) 取 size。

四、list vs vector 完整对比表(面试必背)

表格

对比项 vector list
底层结构 动态顺序表,一段连续堆内存 带头结点双向循环链表,节点离散堆分配
随机访问 ✅支持operator[] O(1) ❌不支持下标访问,访问元素 O (N)
任意位置插入删除 效率 O (N),需要搬移元素;插入可能扩容,代价更大 效率 O (1),仅修改指针,不需要移动元素
空间、缓存 连续内存,缓存命中率高;无内存碎片 节点分散开辟,容易产生内存碎片,缓存命中率差
迭代器本质 原生 T * 指针 封装节点指针的类迭代器
迭代器失效规则 扩容:全部迭代器失效;erase:当前及后面迭代器失效 insert 完全不失效;erase 仅被删除迭代器失效,其余全部有效;clear 全部失效
适用场景 频繁随机访问;尾插尾删,中间插入删除少 大量任意位置插入删除;不关心随机访问

总结选型:

  • 需要下标随机访问,选vector
  • 频繁在中间位置插入删除,不需要随机访问,选list

五、面试问答汇总

Q1:list 的 erase 之后,其他迭代器还能用吗?

A:除了传入 erase 的那个迭代器失效,其他所有迭代器、引用、指针都可以正常使用。erase 会返回下一个有效迭代器。
Q2:list insert 会不会迭代器失效?

A:不会,插入节点只会修改指针,原有节点地址不变,全部旧迭代器保持有效。
Q3:list 为什么没有 operator \[\]?

A:底层链表,要访问第 n 个元素必须从头遍历,时间复杂度 O (N),提供下标没有意义,STL 不实现。
Q4:反向迭代器原理?

A:适配器模式,内部保存正向迭代器;反向迭代器++等价正向--;反向--等价正向++;解引用的时候先把正向迭代器减一再取值。
Q5:list swap 效率?

A:O (1),只交换哨兵头结点的指针,完全不拷贝节点数据。
Q6:list 和 vector 哪个删除中间元素快?

A:list O (1);vector 需要挪动后面所有元素 O (N)。

相关推荐
SunnyDays10111 小时前
如何使用 Python 添加和编辑 Excel VBA 宏(无需安装 Excel)
开发语言·python·excel·vba
郝学胜-神的一滴1 小时前
CMake 047:解锁安装阶段自定义操作,告别配置阶段提前执行坑
运维·服务器·c++·游戏引擎·图形渲染·opengl
zhanghaha13141 小时前
Python进阶教程:24_Markdown 转 HTML 零基础超详细教程
开发语言·python·html
似水এ᭄往昔1 小时前
【QT】--常用控件(QWidget的核心属性)
服务器·开发语言·qt
吴声子夜歌1 小时前
Guava——反射
java·开发语言·guava
lingran__2 小时前
Git 完全指南(三):远程仓库与标签管理
开发语言·git·gitee·ssh·团队协作·远程仓库·分布式版本控制
for_ever_love__2 小时前
python爬虫: 数据解析
开发语言·爬虫·python·xpath
学习星球2 小时前
AI 一句话生成 3D 游戏世界:腾讯 HY-World 2.0 开源深度解析与本地实战
开发语言·人工智能·游戏·3d·ai·课程设计·ai编程
名字还没想好☜2 小时前
Go 用 -race 抓数据竞争:一个偶发崩溃的排查、原理与修复
开发语言·后端·golang·go