【C++初阶】双向循环链表:List底层结构的完整实现剖析

🎬 博主名称键盘敲碎了雾霭
🔥 个人专栏 : 《C语言》《数据结构》 《C++》 《Matlab》 《Python》

⛺️指尖敲代码,雾霭皆可破


文章目录

一、链表节点设计:双向链表的基石

1.1 节点类的实现

双向链表的核心是节点(Node),每个节点需要存储数据、指向前驱节点的指针和指向后继节点的指针。我们将节点定义为模板结构体,以支持任意类型的数据存储:

cpp 复制代码
template<class T>
struct ListNode
{
    ListNode(const T& x = T())
        : _data(x)
        , _next(nullptr)
        , _pre(nullptr)
    {}

    T _data;           // 存储的数据
    ListNode* _next;   // 指向下一个节点
    ListNode* _pre;    // 指向前一个节点
};

设计要点解析

  • 构造函数提供了默认参数T(),使得节点可以被默认构造(对于内置类型会进行零初始化)
  • 使用初始化列表而非在构造函数体内赋值,效率更高
  • 节点本身不负责内存管理,由链表容器统一控制

二、list框架与核心成员函数

2.1 list类的成员变量

我们的list容器采用带哨兵节点的双向循环链表结构。这种设计可以优雅地处理边界条件,避免空指针判断:

cpp 复制代码
template<class T>
class list
{
    typedef ListNode<T> Node;
private:
    Node* _head;     // 哨兵节点指针
    size_t _size;    // 链表元素个数
};

哨兵节点的作用

  • _head不存储实际数据,仅作为链表的起点和终点标记
  • 空链表时,_head->_next_head->_pre都指向_head自身
  • 这使得end()可以直接返回_head,而begin()返回_head->_next

2.2 构造函数与初始化

我们提供一个empty_init()函数来完成链表的初始状态设置:

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

list()
{
    empty_init();
}

2.3 深拷贝

拷贝构造函数需要执行深拷贝,即创建新节点并复制数据,而非简单复制指针:

cpp 复制代码
list(const list& lt)
{
    empty_init();                      // 先初始化空链表
    const_iterator it = lt.begin();    // 使用const迭代器遍历源链表
    
    while (it != lt.end())
    {
        push_back(*it);                // 逐个拷贝元素
        it++;
    }
}

2.4 赋值运算符重载:传统写法 vs 现代写法

传统写法(不推荐)需要处理自我赋值、释放原有资源、执行深拷贝三个步骤,代码冗长且容易出错。

现代写法(Copy-and-Swap惯用法)利用传值拷贝构造的临时对象,通过交换指针高效完成赋值:

cpp 复制代码
void Swap(list& tmp)
{
    std::swap(_size, tmp._size);   // 注意:先交换_size
    std::swap(_head, tmp._head);   // 再交换_head
}

list& operator=(list tmp)   // 传值,自动调用拷贝构造
{
    Swap(tmp);              // 交换资源
    return *this;           // tmp析构时自动释放原资源
}

现代写法的优势

  • 天然支持自我赋值
  • 异常安全(如果拷贝构造抛出异常,原对象状态不变)
  • 代码简洁优雅

2.5 构造函数重载与冲突解决

我们还提供了initializer_list构造函数,支持类似list<int> l = {1, 2, 3, 4, 5}的初始化方式:

cpp 复制代码
list(std::initializer_list<T> li)
{
    empty_init();
    for (auto& e : li)
    {
        push_back(e);
    }
}

三、修改操作:插入与删除

3.1 插入操作insert

insert是所有插入操作的底层实现,在指定位置之前插入新元素:

cpp 复制代码
void insert(iterator pos, const T& x)
{
    Node* cur = pos._node;      // 当前位置节点
    Node* pre = cur->_pre;      // 前驱节点
    Node* newnode = new Node(x);
    
    // 建立双向连接
    cur->_pre = newnode;
    pre->_next = newnode;
    newnode->_next = cur;
    newnode->_pre = pre;
    
    _size++;
}

基于insert,我们可以方便地实现push_backpush_front

cpp 复制代码
void push_back(const T& x)
{
    insert(end(), x);    // end()指向哨兵节点,在其前插入即尾部插入
}

void push_front(const T& x)
{
    insert(begin(), x);  // begin()指向首节点,在其前插入即头部插入
}

3.2 删除操作erase

erase删除指定位置的节点,并返回下一个有效节点的迭代器:

cpp 复制代码
iterator erase(iterator pos)
{
    assert(pos != end());  // 不能删除哨兵节点
    
    Node* pre = pos._node->_pre;
    Node* next = pos._node->_next;
    
    pre->_next = next;
    next->_pre = pre;
    
    delete pos._node;
    _size--;
    
    return iterator(next);  // 返回被删除节点的后继
}

同样,pop_backpop_front可以基于erase实现:

cpp 复制代码
void pop_back()
{
    erase(--end());    // end()前一个节点即尾节点
}

void pop_front()
{
    erase(begin());    // begin()即首节点
}

clear函数的正确实现

cpp 复制代码
void clear()
{
    iterator it = begin();
    while (it != end())
    {
        it = erase(it);  // erase返回下一个有效节点,不需要it++
    }
}

四、其他关键函数实现

4.1 容量操作

cpp 复制代码
size_t size() const
{
    return _size;
}

bool empty() const
{
    return _size == 0;
}

4.2 自定义swap的高效实现

cpp 复制代码
void swap(list& other)
{
    std::swap(_head, other._head);
    std::swap(_size, other._size);
}

这个自定义swap只交换指针和计数器,时间复杂度O(1),远优于默认的逐元素交换。


文章结语

感谢你读到这里~我是「键盘敲碎了雾霭」,愿这篇文字帮你敲开了技术里的小迷雾 💻

如果内容对你有一点点帮助,不妨给个暖心三连吧👇

👍 点赞 | ❤️ 收藏 | ⭐ 关注

(听说三连的小伙伴,代码一次编译过,bug绕着走~)

你的支持,就是我继续敲碎技术雾霭的最大动力 🚀

🐶 小彩蛋:

复制代码
      /^ ^\
     / 0 0 \
     V\ Y /V
      / - \
    /    |
   V__) ||

摸一摸毛茸茸的小狗,赶走所有疲惫和bug~我们下篇见 ✨

相关推荐
REDcker2 小时前
C++ 包管理工具概览
开发语言·c++
努力努力再努力wz2 小时前
【C++高阶系列】告别内查找局限:基于磁盘 I/O 视角的 B 树深度剖析与 C++ 泛型实现!(附B树实现源码)
java·linux·开发语言·数据结构·c++·b树·算法
承渊政道2 小时前
【优选算法】(实战攻坚BFS之FloodFill、最短路径问题、多源BFS以及解决拓扑排序)
数据结构·c++·笔记·学习·算法·leetcode·宽度优先
lcj25112 小时前
字符函数,字符串函数,内存函数
c语言·开发语言·c++·windows
吃着火锅x唱着歌2 小时前
深度探索C++对象模型 学习笔记 第三章 Data语意学(2)
c++·笔记·学习
Imxyk2 小时前
P9242 [蓝桥杯 2023 省 B] 接龙数列
c++·算法·图论
郝学胜-神的一滴2 小时前
二叉树后序遍历:从递归到非递归的优雅实现
数据结构·c++·程序人生·算法·
亚马逊云开发者2 小时前
GameLift Servers DDoS防护实战:Player Gateway + Ping Beacons延迟优化 + C++ SDK集成
c++·gateway·ddos
念恒123065 小时前
继承(下) (Inheritance)
c++