当前位置: 首页 > article >正文

【C++指南】C++ list容器完全解读(二):list模拟实现,底层架构揭秘

.

💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++指南》
期待您的关注
在这里插入图片描述

文章目录

      • 引言
      • 一、链表节点设计:双向链表的基石
        • 1.1 节点类的实现
      • 二、list框架与核心成员函数
        • 2.1 list类的成员变量
        • 2.2 构造函数与初始化
        • 2.3 深拷贝
        • 2.4 赋值运算符重载:传统写法 vs 现代写法
        • 2.4 构造函数重载与冲突解决
      • 三、修改操作:插入与删除
        • 3.1 插入操作`insert`
        • 3.2 删除操作`erase`
      • 四、其他关键函数实现
        • 4.1 容量操作
        • 4.1 交换函数
        • 4.2 自定义swap的高效实现
      • 结语

引言

在上一篇文章【C++指南】STL list容器完全解读(一):从入门到掌握基础操作中,我们深入探讨了list容器的核心特性、使用场景及接口规范。
本文作为系列第二篇,将聚焦于list的底层模拟实现,通过手写双向链表结构,揭示其高效插入删除的底层逻辑。

通过本文,您将掌握:

  • 双向链表的节点设计与内存管理
  • list核心成员函数的实现原理
  • 深拷贝与现代C++优化技巧
  • STL容器设计中的关键思想

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

1.1 节点类的实现

list_node是链表的原子单位,需包含数据域前后指针

template <class T>
struct list_node {T data;            // 数据域list_node<T>* next; // 后继指针list_node<T>* prev; // 前驱指针// 构造函数:支持默认值初始化list_node(const T& x = T()) : data(x), next(nullptr), prev(nullptr) {}
};

关键点

  • 模板化设计支持任意数据类型
  • 默认构造函数初始化指针为nullptr,避免野指针

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

2.1 list类的成员变量
template <class T>
class list {
private:typedef list_node<T> Node;Node* _head;    // 头节点(哨兵节点)size_t _size;   // 元素数量
public:// 迭代器声明(下篇详解)typedef list_iterator<T, T&, T*> iterator;// ... 其他成员函数
};

在这里插入图片描述

核心设计思想

  • 头节点作为哨兵节点,使空链表的begin()end()统一指向_head
  • _size记录元素数量,避免遍历统计
2.2 构造函数与初始化
// 默认构造:创建空链表
list() { empty_init(); }// 初始化函数:构建头节点闭环
void empty_init() {_head = new Node();_head->next = _head;_head->prev = _head;_size = 0;
}

注意事项

  • 头节点的nextprev均指向自身,形成环形结构
2.3 深拷贝

拷贝构造

list(const list<T>& s) {empty_init();for (auto& i : s) push_back(i); // 深拷贝
}
2.4 赋值运算符重载:传统写法 vs 现代写法

传统写法(显式深拷贝)

list<T>& operator=(const list<T>& s) {  if (this != &s) {          // 防止自赋值  clear();               // 清空当前链表  for (auto& val : s) {  push_back(val);    // 逐元素深拷贝  }  }  return *this;  
}  

缺点:代码冗余,需手动处理资源释放与拷贝。

现代写法(资源交换)

list<T>& operator=(list<T> s) {  swap(s);    // 传递临时对象,利用拷贝构造完成深拷贝  return *this;  
}  

优势

  • 利用拷贝构造函数生成临时对象s,自动完成深拷贝
  • 通过swap交换资源,临时对象s析构时自动释放旧数据

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

1. 多个val值的构造

// 填充构造函数:创建n个值为val的元素  
list(size_t n, const T& val = T()) {  empty_init();  for (size_t i=0; i<n; ++i) push_back(val);  
}  // 重载int版本,避免与迭代器范围构造冲突  
list(int n, const T& val = T()) {  empty_init();  for (int i=0; i<n; ++i) push_back(val);  
}  

2. 初始化列表构造

list(initializer_list<T> il) {  empty_init();  for (auto& elem : il) push_back(elem);  
}  

3. 迭代器范围构造

template <class InputIterator>  
list(InputIterator first, InputIterator last) {  empty_init();  while (first != last) {  push_back(*first);  ++first;  }  
}  

为什么需要重载int n版本?
若只有模板版本的迭代器范围构造,当用户调用list<int> lst(5, 1)时:
如下图所示,错误的调用了为迭代器准备的模板函数,就会对int解引用
在这里插入图片描述

  • 编译器优先匹配InputIterator版本(int被推导为迭代器类型)
  • 导致逻辑错误(试图对整数51解引用)
    解决方案:提供int n的重载版本,明确匹配数值构造场景。

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

3.1 插入操作insert
iterator insert(iterator pos, const T& val) {Node* cur = pos._node;      // 当前节点Node* prev = cur->prev;     // 前驱节点Node* new_node = new Node(val); // 新节点// 链接新节点prev->next = new_node;new_node->prev = prev;new_node->next = cur;cur->prev = new_node;_size++;return iterator(new_node);  // 返回新节点迭代器
}

复用插入实现push_back/push_front

void push_back(const T& x) { insert(end(), x); }
void push_front(const T& x) { insert(begin(), x); }
3.2 删除操作erase
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); // 返回下一节点迭代器
}

复用删除实现pop_back/pop_front

void pop_back() { erase(--end()); }
void pop_front() { erase(begin()); }

四、其他关键函数实现

4.1 容量操作
// 清空链表(保留头节点)
void clear() {iterator it = begin();while (it != end()) it = erase(it);
}bool empty()//判空
{return _size == 0;
}size_t size()//获取链表元素个数
{return _size;
}
4.1 交换函数

STL的std::swap通过三次拷贝完成交换:

template <class T>  
void swap(T& a, T& b) {  T tmp(a);  a = b;  b = tmp;  
}  

问题

  • 对链表而言,逐节点拷贝效率极低(时间复杂度O(n))
4.2 自定义swap的高效实现

类内swap:直接交换头指针与_size

void swap(list<T>& other) {  std::swap(_head, other._head); // O(1)交换  std::swap(_size, other._size);  
}  

全局swap适配:确保ADL正确调用

template <class T>  
void swap(list<T>& a, list<T>& b) {  a.swap(b); // 调用类内swap  
}  

为何需要全局swap?

  • 若用户调用swap(lst1, lst2),编译器优先查找参数关联的命名空间
  • 全局swap确保调用自定义实现,而非低效的std::swap

结语

本文从双向链表的节点设计出发,逐步实现了list的核心功能,揭示了STL容器设计中的内存管理与接口复用思想。
在下一篇文章中,我们将深入探讨迭代器的封装与类型萃取技术

下篇预告:《【C++指南】C++ list容器完全解读(三):list迭代器的实现与优化》—— 揭秘STL迭代器如何实现“透明访问”与高效遍历!

http://www.lryc.cn/news/2395315.html

相关文章:

  • [神经网络]使用olivettiface数据集进行训练并优化,观察对比loss结果
  • 小明的Java面试奇遇之智能家装平台架构设计与JVM调优实战
  • n8n:技术团队的智能工作流自动化助手
  • Flink 核心机制与源码剖析系列
  • 华院计算出席信创论坛,分享AI教育创新实践并与燧原科技共同推出教育一体机
  • 华为OD机试真题——会议接待 /代表团坐车(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
  • LabVIEW Val (Sgnl) 属性
  • STM32G4 电机外设篇(三) TIM1 发波 和 ADC COMP DAC级联
  • DAY 35 超大力王爱学Python
  • 【数据结构】图的存储(十字链表)
  • 005 flutter基础,初始文件讲解(4)
  • Redis最佳实践——秒杀系统设计详解
  • STM32软件spi和硬件spi
  • MATLAB实战:人脸检测与识别实现方案
  • 深度刨析树结构(从入门到入土讲解AVL树及红黑树的奥秘)
  • 【Linux】shell的条件判断
  • 第九天:java注解
  • 十一、【核心功能篇】测试用例管理:设计用例新增编辑界面
  • react-native的token认证流程
  • ERP系统中商品定价功能设计:支持渠道、会员与批发场景的灵活定价机制
  • Spring是如何实现属性占位符解析
  • 数据结构之ArrayList
  • DDR4读写压力测试
  • uniapp 开发企业微信小程序时,如何在当前页面真正销毁前或者关闭小程序前调用一个api接口
  • WPF 按钮点击音效实现
  • 编写测试用例
  • 解释程序(Python)不需要生成机器码 逐行解析 逐行执行
  • 每日Prompt:隐形人
  • TensorFlow深度学习实战(19)——受限玻尔兹曼机
  • 告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南