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 操作等。但以上代码提供了理解其底层机制和设计思路的良好基础。

相关推荐
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
模板template
开发语言·c++
大邳草民1 小时前
Python 中 global 与 nonlocal 的语义与机制
开发语言·笔记·python
charlie1145141911 小时前
通用GUI编程技术——图形渲染实战(二十九)——Direct2D架构与资源体系:GPU加速2D渲染入门
开发语言·c++·学习·架构·图形渲染·win32
小肝一下1 小时前
每日两道力扣,day8
c++·算法·leetcode·哈希算法·hot100
历程里程碑1 小时前
二叉树---二叉树的中序遍历
java·大数据·开发语言·elasticsearch·链表·搜索引擎·lua
CheerWWW2 小时前
C++学习笔记——线程、计时器、多维数组、排序
c++·笔记·学习
无限进步_2 小时前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
浅时光_c2 小时前
12 指针
c语言·开发语言
charlie1145141912 小时前
嵌入式现代C++工程实践——第10篇:HAL_GPIO_Init —— 把引脚配置告诉芯片的仪式
开发语言·c++·stm32·单片机·c
呼啦啦5612 小时前
C++动态内存管理
c++