C++ : list

💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!

👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!


1. 基本概念

数据结构本质:std::list 是 C++ 标准库中的双向链表容器,元素存储在独立节点中,每个节点通过指针连接,内存非连续分配。

使用时包含头文件 <list> ;部分/全部展开命名空间。

核心特性

  • 作为序列式容器,支持双向迭代(可前后遍历);
  • 任意位置插入 / 删除操作时间复杂度为常数级(O (1)) ,相比 array、vector、deque,此类操作效率更高。

与 forward_list 的区别

forward_list 是单链表(仅支持单向迭代),结构更简单、空间更高效;list 为双向链表(支持双向迭代)。

主要缺陷

  • 不支持通过索引(operator[] 或 迭代器+/-n)随机访问,访问第 n 个元素需从头部 / 尾部逐个移动;
  • 额外空间开销(每个节点需存储前驱 / 后继指针,对存储小元素的大 list 影响较明显)。

2. list 的常见重要接口

2.1 构造

|------------------------------------------------|---------------------------------|
| list() | 默认构造:创建空链表 |
| list (size_type n, const value_type& val) | 创建包含 n 个值为 val 的元素的链表 |
| list (InputIterator first, InputIterator last) | 用迭代器 [first, last) 范围内的元素构造链表 |
| list (const list& other) | 拷贝构造:复制 other 的所有元素 |

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

int main()
{
    // 默认构造
    list<int> mylist1;

    // 创建包含 n 个值为 val 的元素的链表
    list<int> mylist2(5, 10);

    // 用迭代器 [first, last) 范围内的元素构造链表
    int arr[] = { 1, 2, 3, 4, 5 };
    list<int> mylist3(arr, arr + 5);

    // 拷贝构造
    list<int> mylist4(mylist2);

    cout << "mylist1: ";
    for (auto it = mylist1.begin(); it != mylist1.end(); ++it)
        cout << *it << " ";
    cout << endl;

    cout << "mylist3: ";
    for (auto it = mylist3.begin(); it != mylist3.end(); ++it)
        cout << *it << " ";
    cout << endl;

    return 0;
}

2.2 迭代器

|------------|----------------------|
| begin() | 返回指向第一个元素的正向迭代器 |
| end() | 返回指向最后一个元素后一位置的正向迭代器 |
| rbegin() | 返回指向最后一个元素的反向迭代器 |
| rend() | 返回指向第一个元素前一位置的反向迭代器 |

正向遍历:

cpp 复制代码
​for (auto it = mylist.begin(); it != mylist.end(); ++it)
    {cout << *it << " ";}
cout << endl;

反向遍历:

cpp 复制代码
for (auto it = mylist.rbegin(); it != mylist.rend(); ++it)
    {cout << *it << " ";}
cout << endl;

2.3 容量

|---------|-------------------------------|
| empty() | 检测list是否为空,空则返回true,否则返回false |
| size() | 返回list中有效节点的个数 |

2.4 元素访问

|-----------|----------------------------|
| front() | 返回第一个元素的引用(未定义行为:链表为空时调用) |
| back() | 返回最后一个元素的引用(未定义行为:链表为空时调用) |

cpp 复制代码
cout << "第一个元素: " << mylist.front() << endl;
cout << "最后一个元素: " << mylist.back() << endl;

2.5 修改

|-------------------|------------------------------------|
| push_front(val) | 在头部插入元素 val |
| pop_front() | 删除头部元素 |
| push_back(val) | 在尾部插入元素 val |
| pop_back() | 删除尾部元素 |
| insert(it, val) | 在迭代器 it 指向的位置处插入 val,返回新元素的迭代器 |
| erase(it) | 删除迭代器 it 指向的元素,返回下一个元素的迭代器 |
| clear() | 清空链表 |
| swap(other) | 交换两个链表的内容,仅交换内部指针 |

使用 insert() 或 erase() 时要定位元素,list 中可以使用 std::advance(it, n) 。

it :一个迭代器的引用,表示待移动的迭代器

n :移动的步数(整数类型)

  • n > 0:后移;
  • n < 0:前移(仅当迭代器支持反向操作时有效,如双向迭代器)

使用时要包含头文件 <iterator>

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

int main()
{
    list<int> mylist;

    mylist.push_front(1);
    mylist.push_front(2);
    mylist.push_front(3);
    cout << "使用 push_front 头插元素后:";
    for (int val : mylist)
        cout << val << " ";
    cout << endl;

    mylist.push_back(4);
    mylist.push_back(5);
    cout << "使用 push_back 尾插元素后:";
    for (int val : mylist)
        cout << val << " ";
    cout << endl;

    mylist.pop_front();
    cout << "使用 pop_front 删除头部元素后:";
    for (int val : mylist)
        cout << val << " ";
    cout << endl;

    mylist.pop_back();
    cout << "使用 pop_back 删除尾部元素后:";
    for (int val : mylist)
        cout << val << " ";
    cout << endl;

    auto it = mylist.begin();
    advance(it, 1); // 头部迭代器后移1位
    auto new_it = mylist.insert(it, 99); // 第2个位置处插入
    cout << "使用 insert 在指定位置插入 99 后:";
    for (int val : mylist)
        cout << val << " ";
    cout << endl;

    it = mylist.begin();
    advance(it, 2); // 头部迭代器后移2位
    auto next_it = mylist.erase(it); // 第三个位置处删除
    cout << "使用 erase 删除指定位置元素后:";
    for (int val : mylist)
        cout << val << " ";
    cout << endl;

    mylist.clear();
    cout << "使用 clear 清空链表后,链表是否为空:"
        << (mylist.empty() ? "是" : "否") << endl;

    return 0;
}

2.6 迭代器失效问题

  • 插入操作:插入元素不会使任何迭代器 / 引用失效(新节点独立分配内存)。
  • 交换操作:交换两个链表的内容不会使迭代器失效(迭代器仍指向原链表的元素,但原链表已与另一个链表交换内容)。
  • 删除操作:仅使指向被删除元素的迭代器 / 引用失效,其他迭代器不受影响。

易错点1:循环中直接删除元素后继续使用原迭代器

cpp 复制代码
for (auto it = list.begin(); it != list.end(); ++it) 
{
    if (*it == target) 
    {
        list.erase(it);  // 删除后it已失效,继续++it会导致崩溃
    }
}

改正:用 erase 的返回值更新迭代器

cpp 复制代码
for (auto it = list.begin(); it != list.end(); ) 
{
    if (*it == target) 
    {
        it = list.erase(it);  // 返回下一个有效迭代器
    } 
    else 
    {
        ++it;  // 不删除时递增
    }
}

易错点2:删除元素后访问已失效的迭代器

cpp 复制代码
auto it = list.begin();
list.erase(it);
std::cout << *it;  // 访问已失效的迭代器

改正:删除后立即通过 erase 返回值获取新迭代器

cpp 复制代码
auto it = list.begin();
it = list.erase(it);  // it 现在指向下一个元素或 end()

3. list 的模拟实现

3.1 反向迭代器

cpp 复制代码
template<class Iterator>
class ReverseListIterator
{
public:
    // 明确告诉编译器 Ref/Ptr 是 Iterator 的嵌套类型(而非静态成员)
    typedef typename Iterator::Ref Ref;
    typedef typename Iterator::Ptr Ptr;
    typedef ReverseListIterator<Iterator> Self;

public:
    // 构造函数:用原迭代器初始化反向迭代器
    ReverseListIterator(Iterator it) : _it(it) {}

    // 解引用操作:返回原迭代器前一个位置的元素
    Ref operator*() {
        Iterator temp(_it);  // 创建临时迭代器副本
        --temp;              // 临时迭代器左移(对应反向迭代器的"当前位置")
        return *temp;        // 返回临时迭代器指向的元素
    }

    // 箭头操作符:通过解引用获取元素地址
    Ptr operator->() {
        return &(operator*());  // 依赖 operator*() 返回左值引用
    }

    // 前置递增:反向迭代器右移(原迭代器左移)
    Self& operator++() {
        --_it;  // 原迭代器左移,对应反向迭代器右移
        return *this;
    }

    // 后置递增:返回旧值后递增
    Self operator++(int) {
        Self temp(*this);  // 保存旧状态
        --_it;             // 原迭代器左移
        return temp;       // 返回旧状态
    }

    // 前置递减:反向迭代器左移(原迭代器右移)
    Self& operator--() {
        ++_it;  // 原迭代器右移,对应反向迭代器左移
        return *this;
    }

    // 后置递减:返回旧值后递减
    Self operator--(int) {
        Self temp(*this);  // 保存旧状态
        ++_it;             // 原迭代器右移
        return temp;       // 返回旧状态
    }

    // 不等比较:基于原迭代器的不等关系
    bool operator!=(const Self& other) const {
        return _it != other._it;  // 直接比较原迭代器
    }

    // 相等比较:修正原代码的逻辑错误
    bool operator==(const Self& other) const {
        return _it == other._it;  
    }

private:
    Iterator _it;  // 反向迭代器的核心:持有原迭代器
};
面/笔试高频考点 具体内容
typename的作用 明确Iterator::Ref/Ptr是迭代器的嵌套类型(非静态成员),解决模板依赖类型歧义问题
与原迭代器的操作映射 反向迭代器++对应原迭代器----对应原迭代器++operator*需取原迭代器前一位置元素
操作符重载细节 前置++/--返回引用(修改自身),后置++/--返回旧值(先保存再修改); operator->依赖operator*返回左值引用
相等 / 不等比较逻辑 operator==需直接比较原迭代器,与operator!=互斥

3.2 双向链表节点与迭代器实现

cpp 复制代码
template <typename T>
struct ListNode {
    T data;
    ListNode* prev;
    ListNode* next;
    explicit ListNode(const T& val = T()) : data(val), prev(nullptr), next(nullptr) {}
};

template <typename T>
class ListIterator {
public:
    using iterator_category = std::bidirectional_iterator_tag;
    using value_type = T;
    using pointer = T*;
    using reference = T&;

    ListIterator(ListNode<T>* node) : m_node(node) {}

    reference operator*() const { return m_node->data; }
    pointer operator->() const { return &m_node->data; }

    // 前置++
    ListIterator& operator++() {
        m_node = m_node->next;
        return *this;
    }
    // 后置++
    ListIterator operator++(int) {
        ListIterator tmp = *this;
        m_node = m_node->next;
        return tmp;
    }

    // 前置--
    ListIterator& operator--() {
        m_node = m_node->prev;
        return *this;
    }
    // 后置--
    ListIterator operator--(int) {
        ListIterator tmp = *this;
        m_node = m_node->prev;
        return tmp;
    }

    bool operator==(const ListIterator& other) const { return m_node == other.m_node; }
    bool operator!=(const ListIterator& other) const { return m_node != other.m_node; }

private:
    ListNode<T>* m_node;
};

3.3 链表核心类实现

cpp 复制代码
template <typename T>
class MyList {
public:
    // 类型别名(面试常考)
    using value_type = T;
    using iterator = ListIterator<T>;
    using const_iterator = ListIterator<const T>;

    // 默认构造:初始化哨兵头节点
    MyList() {
        m_head = new ListNode<T>();
        m_head->prev = m_head;
        m_head->next = m_head;
    }

    // 拷贝构造(深拷贝)
    MyList(const MyList& other) {
        m_head = new ListNode<T>();
        m_head->prev = m_head;
        m_head->next = m_head;
        for (auto it = other.begin(); it != other.end(); ++it) {
            push_back(*it);
        }
    }

    // 赋值运算符(深拷贝 + 自赋值安全)
    MyList& operator=(const MyList& other) {
        if (this != &other) {
            clear();
            for (auto it = other.begin(); it != other.end(); ++it) {
                push_back(*it);
            }
        }
        return *this;
    }

    // 析构函数(释放所有节点)
    ~MyList() {
        clear();
        delete m_head;
    }

    // 迭代器接口
    iterator begin() { return iterator(m_head->next); }
    iterator end() { return iterator(m_head); }

    // 核心操作:头插 & 尾插
    void push_front(const T& val) {
        insert(begin(), val);
    }

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

    // 核心操作:头删 & 尾删
    void pop_front() {
        erase(begin());
    }

    void pop_back() {
        erase(--end()); // 尾迭代器需先递减
    }

    // 插入操作(面试高频)
    iterator insert(iterator pos, const T& val) {
        ListNode<T>* curr = pos.m_node;
        ListNode<T>* newNode = new ListNode<T>(val);
        // 调整指针顺序:prev -> newNode -> curr
        newNode->prev = curr->prev;
        newNode->next = curr;
        curr->prev->next = newNode;
        curr->prev = newNode;
        return iterator(newNode); // 返回新节点迭代器
    }

    // 删除操作(面试高频)
    iterator erase(iterator pos) {
        if (pos == end()) return pos; // 不能删除end()
        ListNode<T>* delNode = pos.m_node;
        ListNode<T>* nextNode = delNode->next;
        // 断开指针连接
        delNode->prev->next = nextNode;
        nextNode->prev = delNode->prev;
        delete delNode; // 释放内存
        return iterator(nextNode); // 返回下一个元素迭代器
    }

    // 清空链表
    void clear() {
        auto curr = m_head->next;
        while (curr != m_head) {
            auto next = curr->next;
            delete curr;
            curr = next;
        }
        // 恢复哨兵头节点自环
        m_head->prev = m_head;
        m_head->next = m_head;
    }

    // 辅助功能
    bool empty() const { return m_head->next == m_head; }
    size_t size() const {
        size_t cnt = 0;
        for (auto it = begin(); it != end(); ++it) cnt++;
        return cnt;
    }

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

private:
    ListNode<T>* m_head; // 哨兵头节点(不存储数据)
};

4. 对比 list 与 vector

对比维度 vector list
底层结构 动态顺序表,一段连续内存空间 带头结点的双向循环链表
随机访问 支持随机访问,时间复杂度 O (1) 不支持随机访问,时间复杂度 O (N)
插入和删除 任意位置插入 / 删除效率低(需搬移元素,时间复杂度 O (N)); 可能触发增容(开辟新空间→拷贝→释放旧空间) 任意位置插入 / 删除效率高(无需搬移元素,时间复杂度 O (1))
空间利用率 连续内存,不易产生内存碎片; 缓存利用率高 节点动态开辟,小节点易产生内存碎片; 缓存利用率低
迭代器 原生态指针(如 T*) 对节点指针(如 ListNode*)的封装类
迭代器失效 插入可能因扩容导致所有迭代器失效; 删除需重新赋值当前迭代器否则失效 插入不会导致迭代器失效; 删除仅当前迭代器失效,其他迭代器不受影响
使用场景 需要高效存储、支持随机访问,不关心插入 / 删除效率的场景 大量插入 / 删除操作,不关心随机访问效率的场景
相关推荐
虾球xz2 小时前
CppCon 2016 学习:GAME ENGINE USING C++11
大数据·开发语言·c++·学习
Jet45052 小时前
第100+42步 ChatGPT学习:R语言实现阈值调整
开发语言·学习·chatgpt·r语言
虾球xz2 小时前
CppCon 2016 学习:fixed_point Library
开发语言·c++·学习
希希不嘻嘻~傻希希2 小时前
CSS 字体与文本样式笔记
开发语言·前端·javascript·css·ecmascript
HaiQinyanAN2 小时前
【学习笔记】nlohmannjson&&cjson
c++·笔记·学习·json
C语言小火车3 小时前
【C语言】银行账户管理系统丨源码+解析
c语言·c++·算法·课程设计
寄思~3 小时前
Python学习笔记:错误和异常处理
开发语言·笔记·python·学习
clmm1233 小时前
Java动态生成Nginx服务配置
java·开发语言·nginx
东方芷兰3 小时前
Leetcode 刷题记录 17 —— 堆
java·c++·b树·算法·leetcode·职场和发展
lzb_kkk4 小时前
【MFC】编辑框、下拉框、列表控件
c语言·开发语言·c++·mfc·1024程序员节