手撕C++STL list:深入理解双向链表的实现

目录

[1. 引言](#1. 引言)

[3. list 类的实现](#3. list 类的实现)

[(1) 基本结构](#(1) 基本结构)

[(2) 初始化与清理](#(2) 初始化与清理)

[(3) 插入与删除](#(3) 插入与删除)

[(4) 常用接口](#(4) 常用接口)

[(4) 常用接口](#(4) 常用接口)

[4. 测试代码](#4. 测试代码)

[5. 总结](#5. 总结)


1. 引言

在C++ STL中,list是一个基于双向链表 的容器,支持高效的头尾插入/删除操作(O(1)时间复杂度),但不支持随机访问(O(n)时间复杂度)。本文将带你手写一个简化版的 list ,并分析其核心实现。
2. 核心结构:list_node__list_iterator
(1) list_node:链表的节点

cpp 复制代码
template<class T>
struct list_node {
    list_node<T>* _next;  // 指向下一个节点
    list_node<T>* _prev;  // 指向前一个节点
    T _val;               // 存储的数据

    list_node(const T& val = T()) 
        : _next(nullptr), _prev(nullptr), _val(val) 
    {}
};
  • 双向链表节点 ,包含前驱(_prev)、后继(_next)指针和数据(_val)。
  • 默认构造函数 初始化指针为 nullptr,数据为默认值 T()

(2) __list_iterator:链表的迭代器

cpp 复制代码
template<class T, class Ref, class Ptr>
struct __list_iterator {
    typedef list_node<T> Node;
    typedef __list_iterator<T, Ref, Ptr> self;
    Node* _node;  // 当前指向的节点

    __list_iterator(Node* node) : _node(node) {}

    // 解引用(获取数据)
    Ref operator*() { return _node->_val; }

    // 成员访问(-> 运算符重载)
    Ptr operator->() { return &_node->_val; }

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

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

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

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

    // 比较运算符
    bool operator!=(const self& it) const { return _node != it._node; }
    bool operator==(const self& it) const { return _node == it._node; }
};
  • 迭代器核心功能
    • operator*():获取当前节点的数据。
    • operator->():访问当前节点的成员(如 it->_a1)。
    • ++ / --:支持双向遍历。
    • == / !=:判断迭代器是否指向同一节点。

3. list 类的实现

(1) 基本结构

cpp 复制代码
template<class T>
class list {
    typedef list_node<T> Node;
public:
    typedef __list_iterator<T, T&, T*> iterator;         // 普通迭代器
    typedef __list_iterator<T, const T&, const T*> const_iterator;  // const迭代器

    list() { empty_init(); }  // 默认构造
    ~list() { clear(); delete _head; _head = nullptr; }  // 析构

    // 拷贝构造
    list(const list<T>& lt) {
        empty_init();
        for (auto& e : lt) push_back(e);
    }

    // 赋值运算符(现代写法)
    list<T>& operator=(list<T> lt) {
        swap(lt);
        return *this;
    }

    // 交换两个链表
    void swap(list<T>& lt) {
        std::swap(_head, lt._head);
        std::swap(_size, lt._size);
    }
private:
    Node* _head;   // 哨兵头节点(不存储数据)
    size_t _size;  // 链表长度
};
  • 关键点
    • 哨兵节点 _head :简化边界条件处理(begin()_head->_nextend()_head)。
    • 拷贝构造:深拷贝,逐个插入元素。
    • 赋值运算符 :现代写法(参数传值 + swap)。

(2) 初始化与清理

cpp 复制代码
void empty_init() {
    _head = new Node;
    _head->_prev = _head;
    _head->_next = _head;
    _size = 0;
}

void clear() {
    iterator it = begin();
    while (it != end()) {
        it = erase(it);
    }
    _size = 0;
}
  • empty_init():初始化空链表(哨兵节点自环)。
  • clear():逐个删除节点,最后重置 _size

(3) 插入与删除

cpp 复制代码
// 在 pos 前插入
iterator insert(iterator pos, const T& x) {
    Node* cur = pos._node;
    Node* prev = cur->_prev;
    Node* newnode = new Node(x);

    prev->_next = newnode;
    newnode->_prev = prev;
    newnode->_next = cur;
    cur->_prev = newnode;

    ++_size;
    return iterator(newnode);
}

// 删除 pos 位置的节点
iterator erase(iterator pos) {
    assert(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);
}
  • insert:调整前后指针,插入新节点。
  • erase:调整指针后删除节点,返回下一个有效迭代器。

(4) 常用接口

cpp 复制代码
void push_back(const T& x) { insert(end(), x); }
void push_front(const T& x) { insert(begin(), x); }
void pop_back() { erase(--end()); }
void pop_front() { erase(begin()); }

iterator begin() { return iterator(_head->_next); }
iterator end() { return iterator(_head); }
const_iterator begin() const { return const_iterator(_head->_next); }
const_iterator end() const { return const_iterator(_head); }

size_t size() const { return _size; }

(4) 常用接口

cpp 复制代码
void push_back(const T& x) { insert(end(), x); }
void push_front(const T& x) { insert(begin(), x); }
void pop_back() { erase(--end()); }
void pop_front() { erase(begin()); }

iterator begin() { return iterator(_head->_next); }
iterator end() { return iterator(_head); }
const_iterator begin() const { return const_iterator(_head->_next); }
const_iterator end() const { return const_iterator(_head); }

size_t size() const { return _size; }

4. 测试代码

cpp 复制代码
void test_list() {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    for (auto e : lt) cout << e << " ";  // 1 2 3
    cout << endl;
    lt.pop_front();
    for (auto e : lt) cout << e << " ";  // 2 3
}

5. 总结

  • list 的核心是双向链表,插入/删除高效(O(1)),但不支持随机访问。
  • 迭代器是双向迭代器 ,支持 ++ / --,但不支持 + / - / []
  • 哨兵节点简化边界处理begin() 指向第一个元素,end() 指向哨兵。
  • 深拷贝需手动实现(拷贝构造、赋值运算符)。

通过手写 list,可以更深入理解STL容器的底层实现!

相关推荐
黑客老李29 分钟前
JavaSec | SpringAOP 链学习分析
java·运维·服务器·开发语言·学习·apache·memcached
开开心心就好37 分钟前
高效Excel合并拆分软件
开发语言·javascript·c#·ocr·排序算法·excel·最小二乘法
特立独行的猫a43 分钟前
Nuxt.js 中的路由配置详解
开发语言·前端·javascript·路由·nuxt·nuxtjs
勤奋的知更鸟1 小时前
Java编程之原型模式
java·开发语言·原型模式
Unpredictable2221 小时前
【VINS-Mono算法深度解析:边缘化策略、初始化与关键技术】
c++·笔记·算法·ubuntu·计算机视觉
珂朵莉MM1 小时前
2021 RoboCom 世界机器人开发者大赛-高职组(初赛)解题报告 | 珂学家
java·开发语言·人工智能·算法·职场和发展·机器人
香蕉炒肉1 小时前
Java优化:双重for循环
java·开发语言
傍晚冰川2 小时前
FreeRTOS任务调度过程vTaskStartScheduler()&任务设计和划分
开发语言·笔记·stm32·单片机·嵌入式硬件·学习
PingdiGuo_guo2 小时前
C++智能指针的知识!
开发语言·c++
黄雪超2 小时前
JVM——打开JVM后门的钥匙:反射机制
java·开发语言·jvm