【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
相关推荐
景彡先生3 小时前
C++编译期计算:常量表达式(constexpr)全解析
服务器·c++
tan77º3 小时前
【Linux网络编程】应用层自定义协议与序列化
linux·运维·服务器·网络·c++·tcp/ip
悠哉清闲4 小时前
Android Studio C++/JNI/Kotlin 示例 三
c++·kotlin·android studio
AI迅剑4 小时前
模块三:现代C++工程实践(4篇)第二篇《性能调优:Profile驱动优化与汇编级分析》
汇编·c++
byte轻骑兵6 小时前
【Bluedroid】蓝牙协议栈enable流程深度解析
android·c++·bluedroid
我爱一条柴ya6 小时前
【AI大模型】BERT微调文本分类任务实战
人工智能·pytorch·python·ai·分类·bert·ai编程
量子位6 小时前
国产 Deep Research 杀出一匹「裸奔」黑马:免费开放,过程透明,网页报告一键即出
ai编程·deepseek
量子位6 小时前
小扎自曝挖人秘诀:小团队我亲自带,豪掷数百亿建 GW 集群,大家不图天价薪酬只为 “造神”
ai编程
量子位6 小时前
刘璐也被 Meta 挖走了!华南理工校友,创造了 4o 吉卜力爆款
meta·ai编程
用户3521802454757 小时前
MCP极简入门:第一个服务器DesktopCommanderMCP
ai编程·mcp