【C++】: list介绍以及模拟实现

【C++】: list介绍以及模拟实现

List是STL中的双向链表,我们通常使用它来作为一种可灵活进行**数据更换(即插入和删除)**的数据结构。

std::list 是 C++ STL 中的一个容器类,底层实现是 双向链表 ,相比于 vector(底层是数组),它在插入和删除 元素时性能更好(不需要移动元素),但在随机访问方面效率较低。

它定义在头文件 <list> 中。

我们来看看它基本的特性都有哪些。

一、基本特性

特性 说明
存储结构 双向链表(每个节点含有前驱和后继指针)
随机访问 不支持(不像 vector[i]
插入/删除效率 高效,O(1),特别适合频繁插入和删除
内存使用 vector 更高,因为每个元素多了两个指针
迭代器稳定性 插入删除不会使其他元素的迭代器失效

二、list的使用

因为list的接口较多,而且大部分都是STL其他数据结构的同类接口,所以这里只介绍它较为特殊和带有自身特性的。

访问接口

接口 功能
front() / back() 获取首/尾元素的引用
begin() / end() 获取迭代器范围(正向)
rbegin() / rend() 反向迭代器范围

修改接口

接口 功能
push_back(val) / push_front(val) 尾部/头部插入元素
pop_back() / pop_front() 删除尾部/头部元素
insert(pos, val) 在迭代器 pos 前插入元素
insert(pos, n, val) 插入 nval
insert(pos, first, last) 插入一段区间
emplace(pos, args...) 原地构造插入
erase(pos) 删除 pos 位置的元素
erase(first, last) 删除区间元素
clear() 清空链表

链表操作接口

接口 功能
remove(val) 删除所有等于 val 的元素
remove_if(pred) 根据条件删除元素
unique() 删除连续重复元素
sort() 排序(归并排序)
reverse() 反转链表
merge(list& other) 合并两个有序链表,other 清空
splice(pos, other) other 整体剪切到 pos
splice(pos, other, it) other 的一个元素移动过来
splice(pos, other, first, last) 移动区间元素

三、list的迭代器详解

对于 std::list 来说,它的迭代器是一个双向迭代器(Bidirectional Iterator),支持:

  • 向前 ++it
  • 向后 --it

而对于反向迭代器来说,它的 ++ 就是正向迭代器的 -- ,灵活变通,可以互相成为对方。

另外关于迭代器失效问题,可参考下表。

操作 是否导致迭代器失效 说明
insert() 插入 ❌ 不失效(除非插入位置的迭代器) 其他迭代器仍有效
push_front/back() ❌ 不失效 因为底层是链表,不会移动其它元素
erase(it) 删除 it 失效,其他迭代器仍有效 必须用返回的新迭代器继续
clear() 清空 ✅ 所有迭代器失效 全部节点销毁
splice(), merge() ✅ 涉及元素的迭代器失效 非目标容器的迭代器失效

总结起来就是:在 std::list 中,只有 你删除了元素清空容器 时,相关迭代器才会失效;插入和其他元素无关的操作不会导致其他迭代器失效 ,这也是它比 vector 更安全的一大优势。

四、list模拟实现

好,接下来就是list 的模拟实现。

实现了一个简化版的 C++ 双向链表模板类 List<T>,并自定义了双向迭代器与反向迭代器,功能上模拟了 std::list 的许多核心操作。🧩 代码详解:自定义双向链表 joolin::List<T>


命名空间声明

cpp 复制代码
namespace joolin { ... }

节点结构体 ListNode<T>

cpp 复制代码
template <typename T>
struct ListNode {
    T data;
    ListNode<T>* prev;
    ListNode<T>* next;

    ListNode(const T &val = T()) : data(val), prev(nullptr), next(nullptr) {}
};
  • data: 存储节点中的值。
  • prev, next: 分别指向前一个和后一个节点。
  • 构造函数:默认值为 T(),指针为空。

用于实现双向链表结构的核心单元。


类定义 List<T>

基本成员

cpp 复制代码
private:
    ListNode<T> *head;
    ListNode<T> *tail;
    size_t size;
  • head, tail: 指向链表的头和尾。
  • size: 当前链表中元素数量。

嵌套类:正向迭代器 Iterator

cpp 复制代码
class Iterator {
private:
    ListNode<T>* node;

public:
    Iterator(ListNode<T>* n = nullptr) : node(n) {}
    T& operator*() { return node->data; }
    ...
};
  • 自定义一个双向迭代器类,支持:
    • 解引用 *
    • 前向移动 ++
    • 后向移动 --
    • 比较运算符 ==, !=

可以用它像 STL 一样遍历链表:

cpp 复制代码
for (auto it = list.begin(); it != list.end(); ++it) {
    std::cout << *it << std::endl;
}

嵌套类:反向迭代器 ReverseIterator

cpp 复制代码
class ReverseIterator { ... }
  • 功能类似 Iterator,但:
    • ++ 实际上是向前走 prev
    • 用于从尾到头遍历链表。

类似于 std::list::rbegin()rend()


构造 & 析构函数

cpp 复制代码
List() : head(nullptr), tail(nullptr), size(0) {}

~List() {
    while (head) {
        ListNode<T>* temp = head;
        head = head->next;
        delete temp;
    }
    tail = nullptr;
    size = 0;
}
  • 默认构造函数:初始化为空链表。
  • 析构函数:释放所有节点的内存。

清空链表 clear()

cpp 复制代码
void clear() {
    while (head) {
        ListNode<T>* temp = head;
        head = head->next;
        delete temp;
    }
    tail = nullptr;
    size = 0;
}

和析构函数类似,但不销毁对象,只清空元素。


排序函数 sort()

cpp 复制代码
void sort() {
    if (size < 2) return;
    for (ListNode<T>* i = head; i != nullptr; i = i->next) {
        for (ListNode<T>* j = i->next; j != nullptr; j = j->next) {
            if (i->data > j->data) {
                std::swap(i->data, j->data);
            }
        }
    }
}
  • 使用 冒泡排序 对链表排序(稳定、适用于链表)。
  • 排序的是 data 值,而不是节点指针。

插入元素:push_back()push_front()

cpp 复制代码
void push_back(const T &value) { ... }
void push_front(const T &value) { ... }
  • 创建新节点并插入尾部/头部。
  • 更新 tailhead 指针。
  • 空链表时两者处理方式相同。

删除元素:pop_back()pop_front()

cpp 复制代码
void pop_back() { ... }
void pop_front() { ... }
  • 删除头或尾部元素。
  • 更新 headtail,释放内存。

元素访问接口

cpp 复制代码
T front() const { ... }
T back() const { ... }
size_t get_size() const { return size; }
  • front()back():返回第一个和最后一个元素。
  • 抛出异常如果链表为空。
  • get_size():返回链表当前长度。

插入指定位置 insert(pos, value)

cpp 复制代码
void insert(size_t pos, const T &value) { ... }
  • 在指定 pos 位置插入(0为头,size为尾)。
  • 如果越界则抛出 out_of_range 异常。
  • 中间插入时更新前后指针连接。

删除指定位置 erase(pos)

cpp 复制代码
void erase(size_t pos) { ... }
  • 越界检查。

  • pos == 0 调用 pop_front()

  • pos == size - 1 错误!⚠️

    cpp 复制代码
    if(pos = size - 1)  // ❌ 错误!应该是 ==

    应该写成:

    cpp 复制代码
    if (pos == size - 1)
  • 中间删除时,断开连接、释放节点。


begin()/end() 与 rbegin()/rend()

cpp 复制代码
Iterator begin() { return Iterator(head); }
Iterator end() { return Iterator(nullptr); }

ReverseIterator rbegin() { return ReverseIterator(tail); }
ReverseIterator rend() { return ReverseIterator(nullptr); }

提供正向和反向遍历的起点与终点。


总代码如下:

cpp 复制代码
#include <iostream>
namespace joolin
{
    template <typename T>
    struct ListNode
    {
        T data;
        // 两个指针
        ListNode<T> *prev;
        ListNode<T> *next;

        // 构造函数
        ListNode(const T &val = T())
            : data(val), prev(nullptr), next(nullptr) {}
    };

    template <typename T>
    class List
    {
    private:
        ListNode<T> *head;
        ListNode<T> *tail;
        size_t size;

    public:
        //迭代器
        class Iterator{
            private:
            ListNode<T>* node;

            public:
            Iterator(ListNode<T>* n = nullptr) : node(n) {}
            T& operator *() { return node->data; }
            
            Iterator& operator++() {
                node = node->next;
                return *this;
            }
            Iterator operator++(int) {
                Iterator temp = *this;
                node = node->next;
                return temp;
            }
            Iterator& operator--() {
                node = node->prev;
                return *this;
            }
            Iterator operator--(int) {
                Iterator temp = *this;
                node = node->prev;
                return temp;
            }
            bool operator==(const Iterator& other) const { return node == other.node; }
            bool operator!=(const Iterator& other) const { return node != other.node; }

        };

        //返回迭代器
        Iterator begin() { return Iterator(head); }
        Iterator end() { return Iterator(nullptr); }

        //反向迭代器
        // 反向迭代器
        class ReverseIterator {
            private:
                ListNode<T>* node;
    
            public:
                ReverseIterator(ListNode<T>* n = nullptr) : node(n) {}
    
                T& operator*() { return node->data; }
                ReverseIterator& operator++() {
                    node = node->prev;
                    return *this;
                }
                ReverseIterator operator++(int) {
                    ReverseIterator temp = *this;
                    node = node->prev;
                    return temp;
                }
                bool operator==(const ReverseIterator& other) const { return node == other.node; }
                bool operator!=(const ReverseIterator& other) const { return node != other.node; }
            };
    
            //返回反向迭代器
            ReverseIterator rbegin() { return ReverseIterator(tail); }
            ReverseIterator rend() { return ReverseIterator(nullptr); }


        // 构造函数
        List()
            : head(nullptr), tail(nullptr), size(0)
        {
        }

        // 析构函数
        ~List()
        {
            while (head)
            {
                ListNode<T> *temp = head;
                head = head->next;
                delete temp;
            }
            tail = nullptr;
            size = 0;
        }

        //清除
        void clear()
        {
            while (head)
            {
                ListNode<T>* temp = head;
                head = head -> next;
                delete temp;
                /* code */
            }
            tail =nullptr;
            size = 0;   
        }

        //排序
        void sort()
        {
            if(size < 2) return;
            for (ListNode<T>* i = head; i != nullptr; i = i->next) {
                for (ListNode<T>* j = i->next; j != nullptr; j = j->next) {
                    if (i->data > j->data) {
                        std::swap(i->data, j->data);
                    }
                }
            }
        }



        // 增加
        void push_back(const T &value)
        {
            ListNode<T> *newNode = new ListNode<T>(value);
            if (!head)
            {
                head = tail = newNode;
            }
            else
            {
                tail->next = newNode;
                newNode->prev = tail;
                tail = newNode;
            }
            ++size;
        }

        void push_front(const T &value)
        {
            ListNode<T> *newNode = new ListNode<T>(value);
            if (!head)
            {
                head = tail = newNode;
            }
            else
            {
                newNode->next = head;
                head->prev = newNode;
                head = newNode;
            }
            ++size;
        }

        // 删除
        void pop_back()
        {
            if (!tail)
                return;
            if (tail == head)
            {
                delete tail;
                head = tail = nullptr;
            }
            else
            {
                ListNode<T> *temp = tail;
                tail = tail->prev;
                tail->next = nullptr;
                delete temp;
            }
            --size;
        }

        void pop_front()
        {
            if (!head)
                return;
            if (head == tail)
            {
                delete head;
                head = tail = nullptr;
            }
            else
            {
                ListNode<T> *temp = head;
                head = head->next;
                head->prev = nullptr;
                delete temp;
            }
            --size;
        }

        // 返回
        T front() const
        {
            if (!head) {
                throw std::runtime_error("List is empty!");
            }
            return head->data;
        }
        T back() const
        {
            if (!tail) {
                throw std::runtime_error("List is empty!");
            }
            return tail->data;
        }
        size_t get_size() const
        {
            return size;
        }

        // 插入和消除
        void insert(size_t pos, const T &value)
        {
            if (pos > size)
                throw std::out_of_range("Position out of range");
            if (pos == 0)
            {
                push_front(value);
                return;
            }
            if (pos == size)
            {
                push_back(value);
                return;
            }

            ListNode<T> *current = head;
            for (size_t i = 0; i < pos - 1; i++)
            {
                current = current->next;
            }

            ListNode<T> *newNode = new ListNode<T>(value);
            newNode->next = current->next;
            newNode->prev = current;
            current->next->prev = newNode;
            current->next = newNode;

            ++size;
        }

        void erase(size_t pos)
        {
            if (pos >= size) throw std::out_of_range("Position out of range");
            if(pos == 0){
                pop_front();
                return;
            }
            if(pos = size - 1){
                pop_back();
                return;
            }
            
            ListNode<T>* current = head;
            for (size_t i = 0; i < pos; i++)
            {
                current = current -> next;
            }

            current->prev->next = current ->next;
            current->next->prev = current -> prev;

            --size;            
        }
    };

} // namespace joolin
相关推荐
Q741_1477 分钟前
C++ 位运算 高频面试考点 力扣 面试题 17.19. 消失的两个数字 题解 每日一题
c++·算法·leetcode·面试·位运算
初圣魔门首席弟子23 分钟前
C++ STL string(字符串)学习笔记
c++·笔记·学习
玲小珑38 分钟前
LangChain.js 完全开发手册(十三)AI Agent 生态系统与工具集成
前端·langchain·ai编程
AA陈超1 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P04-12 可缩放浮点数的曲线表
c++·游戏·ue5·游戏引擎·虚幻
旭意1 小时前
C++微基础备战蓝桥杯之数组篇10.1
开发语言·c++·蓝桥杯
飞哥数智坊2 小时前
AI 写代码总跑偏?试试费曼学习法:让它先复述一遍!
人工智能·ai编程
青草地溪水旁2 小时前
VSCode C/C++ 构建任务配置文件 `tasks.json` 全字段深度解析
c语言·c++·vscode
爱和冰阔落4 小时前
C++模板进阶 非类型模板参数 模板的特化 分离编译的深入探索
c++·面试·编译原理·模板
charlie11451419110 小时前
精读C++20设计模式:行为型设计模式:中介者模式
c++·学习·设计模式·c++20·中介者模式
楼田莉子10 小时前
Qt开发学习——QtCreator深度介绍/程序运行/开发规范/对象树
开发语言·前端·c++·qt·学习