C++STL list容器模拟实现详解

好的,这是一个关于C++ STL list 容器模拟实现的逐步讲解。list 通常基于双向循环链表 实现,提供了高效的插入和删除操作(时间复杂度为 O(1)),但随机访问效率较低(时间复杂度为 O(n))。下面我们将从节点结构开始,逐步实现一个简化的 list 模板类。

1. 节点结构 (ListNode)

链表的基本单位是节点。对于双向链表,每个节点需要包含三个部分:

  • _data: 存储实际数据。
  • _prev: 指向前一个节点的指针。
  • _next: 指向后一个节点的指针。
cpp 复制代码
template<class T>
struct ListNode {
    T _data;
    ListNode<T>* _prev;
    ListNode<T>* _next;

    // 构造函数,方便节点初始化
    ListNode(const T& data = T(), ListNode<T>* prev = nullptr, ListNode<T>* next = nullptr)
        : _data(data)
        , _prev(prev)
        , _next(next)
    {}
};

2. List 类框架 (MyList)

list 类需要管理整个链表。关键成员包括:

  • _head: 指向哨兵节点(头节点)的指针。这个节点不存储有效数据,其 _prev 指向链表的最后一个节点,其 _next 指向链表的第一个节点。这样形成一个循环结构。
  • _size: 记录链表中有效元素的数量(可选,但实现 size()O(1) 复杂度需要它)。
cpp 复制代码
template<class T>
class MyList {
public:
    // 类型定义 (简化版)
    typedef ListNode<T> Node;
    // 后续会添加迭代器类型定义

    // 构造函数
    MyList() {
        // 创建头节点 (哨兵节点)
        _head = new Node();
        _head->_prev = _head; // 指向自己
        _head->_next = _head; // 指向自己
        _size = 0;
    }

    // 析构函数 - 需要释放所有节点
    ~MyList() {
        clear(); // 清空所有有效节点
        delete _head; // 释放头节点
        _head = nullptr;
    }

    // 其他成员函数 (push_back, pop_back, insert, erase, begin, end, size, empty 等)
    // ...

private:
    Node* _head; // 指向哨兵节点
    size_t _size; // 记录元素个数
};

3. 迭代器设计 (iterator)

STL 容器的精髓在于迭代器。对于链表,迭代器需要封装一个指向节点的指针,并重载相应的操作符使其行为像指针。

cpp 复制代码
template<class T>
class MyList {
public:
    // ... 之前的代码 ...

    // 迭代器类 (嵌套在 MyList 内)
    class iterator {
    public:
        iterator(Node* node = nullptr)
            : _node(node)
        {}

        // 解引用操作符 - 获取数据引用
        T& operator*() {
            return _node->_data;
        }

        // 前置++
        iterator& operator++() {
            _node = _node->_next;
            return *this;
        }

        // 后置++
        iterator operator++(int) {
            iterator tmp = *this;
            _node = _node->_next;
            return tmp;
        }

        // 前置--
        iterator& operator--() {
            _node = _node->_prev;
            return *this;
        }

        // 后置--
        iterator operator--(int) {
            iterator tmp = *this;
            _node = _node->_prev;
            return tmp;
        }

        // 比较操作符 (通常还需要实现 !=)
        bool operator==(const iterator& it) const {
            return _node == it._node;
        }

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

    private:
        Node* _node; // 封装当前指向的节点
        friend class MyList<T>; // 允许 MyList 访问 _node
    };

    // 获取指向第一个有效元素的迭代器
    iterator begin() {
        return iterator(_head->_next); // 头节点的 next 是第一个元素
    }

    // 获取尾后迭代器 (最后一个元素的下一个位置)
    iterator end() {
        return iterator(_head); // 头节点本身是尾后位置
    }

    // ... 后续添加 const_iterator ...
};

4. 基本操作实现

利用节点指针和迭代器,可以实现各种操作:

  • push_back (尾插):

    cpp 复制代码
    void push_back(const T& value) {
        Node* tail = _head->_prev; // 当前最后一个节点
        Node* newnode = new Node(value, tail, _head); // 新节点: prev=tail, next=_head
        tail->_next = newnode; // 原尾节点指向新节点
        _head->_prev = newnode; // 头节点指向新尾节点
        ++_size;
    }
  • pop_back (尾删):

    cpp 复制代码
    void pop_back() {
        if (_size == 0) return; // 空链表不操作
        Node* tail = _head->_prev;
        Node* newtail = tail->_prev;
        newtail->_next = _head;
        _head->_prev = newtail;
        delete tail;
        --_size;
    }
  • insert (在迭代器位置前插入):

    cpp 复制代码
    iterator insert(iterator pos, const T& value) {
        Node* cur = pos._node; // pos 指向的节点
        Node* prev = cur->_prev;
        Node* newnode = new Node(value, prev, cur); // 新节点: prev=prev, next=cur
        prev->_next = newnode;
        cur->_prev = newnode;
        ++_size;
        return iterator(newnode); // 返回指向新节点的迭代器
    }
  • erase (删除迭代器指向的元素):

    cpp 复制代码
    iterator erase(iterator pos) {
        if (pos == end()) return pos; // 不能删除 end()
        Node* cur = pos._node;
        Node* prev = cur->_prev;
        Node* next = cur->_next;
        prev->_next = next;
        next->_prev = prev;
        delete cur;
        --_size;
        return iterator(next); // 返回被删除元素的下一个元素的迭代器
    }
  • clear (清空链表):

    cpp 复制代码
    void clear() {
        iterator it = begin();
        while (it != end()) {
            it = erase(it); // 利用 erase 逐个删除
        }
        _size = 0;
    }
  • sizeempty:

    cpp 复制代码
    size_t size() const {
        return _size;
    }
    bool empty() const {
        return _size == 0;
    }

5. 使用示例

cpp 复制代码
int main() {
    MyList<int> lst;
    lst.push_back(1);
    lst.push_back(2);
    lst.push_back(3);

    // 使用迭代器遍历
    MyList<int>::iterator it = lst.begin();
    while (it != lst.end()) {
        std::cout << *it << " ";
        ++it;
    }
    std::cout << std::endl; // 输出: 1 2 3

    // 在第二个元素前插入
    it = lst.begin();
    ++it; // 指向第二个元素 (2)
    lst.insert(it, 99);

    // 删除第一个元素
    lst.erase(lst.begin());

    // 再次遍历
    for (auto num : lst) { // 基于迭代器的范围 for (需 begin/end 支持)
        std::cout << num << " ";
    }
    std::cout << std::endl; // 输出: 99 2 3

    return 0;
}

总结

这个实现展示了 STL list 的核心思想:

  1. 双向循环链表结构:通过带哨兵头节点的双向链表实现,确保首尾操作高效。
  2. 迭代器封装 :将节点指针封装成迭代器类,重载操作符使其提供类似指针的接口,支持 ++, --, * 等操作。
  3. 模板化 :支持存储任意数据类型 T
  4. 基本操作 :实现了 push_back, pop_back, insert, erase, begin, end, size, empty 等基本接口。

实际 STL 库的 std::list 实现更为复杂,考虑了内存分配器、异常安全、const 迭代器、反向迭代器、splice 操作等。但以上代码提供了理解其底层机制和设计思路的良好基础。

相关推荐
仰泳的熊猫3 小时前
题目1453:蓝桥杯历届试题-翻硬币
数据结构·c++·算法·蓝桥杯
云中飞鸿3 小时前
VS编写QT程序,如何向linux中移植?
linux·开发语言·qt
Boop_wu3 小时前
简单介绍 JSON
java·开发语言
超龄超能程序猿3 小时前
Python 反射入门实践
开发语言·python
Katecat996633 小时前
Faster R-CNN在药片边缘缺陷检测中的应用_1
开发语言·cnn
会叫的恐龙3 小时前
C++ 核心知识点汇总(第11日)(排序算法)
c++·算法·排序算法
晚风_END3 小时前
Linux|操作系统|elasticdump的二进制方式部署
运维·服务器·开发语言·数据库·jenkins·数据库开发·数据库架构
devmoon3 小时前
Polkadot SDK 自定义 Pallet Benchmark 指南:生成并接入 Weight
开发语言·网络·数据库·web3·区块链·波卡
爱吃生蚝的于勒3 小时前
【Linux】线程概念(一)
java·linux·运维·服务器·开发语言·数据结构·vim