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

【C++】 list 容器模拟实现解析

一、链表节点(list_node)的定义

链表节点是 list 容器的基本组成单元,用于存储数据并维护节点间的连接关系。

template<class T>
struct list_node
{T _data;               // 存储节点数据list_node<T>* _next;   // 指向后一个节点的指针list_node<T>* _prev;   // 指向前一个节点的指针// 构造函数,默认值为T的默认构造list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}
};

解析

  • 节点采用双向链表设计,通过_next_prev指针维护前后节点关系,支持双向遍历。
  • 构造函数允许通过参数初始化数据,默认参数T()确保即使未提供初始值,也能正确构造(调用 T 的默认构造函数)。

二、迭代器(__list_iterator)的实现

迭代器是容器与算法交互的桥梁,用于遍历容器元素。list 的迭代器需要模拟指针的行为,适配双向链表的特性。

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){}// 前置++:移动到下一个节点,返回自身引用self& operator++(){_node = _node->_next;return *this;}// 前置--:移动到上一个节点,返回自身引用self& operator--(){_node = _node->_prev;return *this;}// 后置++:先记录当前状态,再移动,返回原状态self operator++(int){self tmp(*this);  // 拷贝当前迭代器_node = _node->_next;return tmp;}// 后置--:先记录当前状态,再移动,返回原状态self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}// 解引用:返回节点数据的引用(Ref适配const/非const)Ref operator*(){return _node->_data;}// ->运算符:返回节点数据的指针(Ptr适配const/非const)Ptr operator->(){return &_node->_data;}// 不等比较:判断两个迭代器是否指向不同节点bool operator!=(const self& s){return _node != s._node;}// 相等比较:判断两个迭代器是否指向同一节点bool operator==(const self& s){return _node == s._node;}
};

解析

  • 模板参数Ref(引用类型)和Ptr(指针类型)用于适配普通迭代器(T&T*)和 const 迭代器(const T&const T*),避免重复实现。
  • 迭代器本质是对节点指针的封装,通过重载++--实现双向移动,重载*->实现数据访问,重载==!=实现迭代器比较。
  • 前置 / 后置自增 / 自减的区别:前置返回修改后的自身(效率更高),后置返回修改前的副本(需要额外拷贝)。

三、list 类的核心实现

list 类是对双向链表的封装,提供了容器的各种操作接口,内部通过 “哨兵节点”(头节点)简化边界条件处理。

3.1 类型定义与成员变量

template<class T>
class list
{typedef list_node<T> Node;  // 节点类型别名
public:// 普通迭代器:Ref为T&,Ptr为T*typedef __list_iterator<T, T&, T*> iterator;// const迭代器:Ref为const T&,Ptr为const T*typedef __list_iterator<T, const T&, const T*> const_iterator;private:Node* _head;    // 哨兵节点(头节点,不存储有效数据)size_t _size;   // 容器中有效元素的个数
};

解析

  • 哨兵节点_head的作用:统一链表的空状态和非空状态处理,避免操作时判断边界(如插入第一个元素或删除最后一个元素时的特殊处理)。
  • _size记录有效元素个数,避免每次需要大小时遍历链表(O (1) 时间复杂度)。

3.2 迭代器接口(begin/end)

// const版本:返回指向第一个元素的const迭代器
const_iterator begin() const
{return const_iterator(_head->_next);  // 第一个有效元素是_head的下一个节点
}// const版本:返回指向链表末尾的const迭代器(哨兵节点)
const_iterator end() const
{return const_iterator(_head);
}// 普通版本:返回指向第一个元素的迭代器
iterator begin()
{return _head->_next;  // 隐式转换为iterator(利用迭代器构造函数)
}// 普通版本:返回指向链表末尾的迭代器(哨兵节点)
iterator end()
{return _head;
}

解析

  • begin()返回指向第一个有效元素的迭代器(_head->_next),end()返回指向哨兵节点的迭代器(作为尾后标记)。
  • 区分普通迭代器和 const 迭代器:const 对象调用 const 版本,确保不能修改元素;非 const 对象调用普通版本,支持读写。

3.3 初始化与构造函数

// 初始化哨兵节点(空链表状态)
void empty_init()
{_head = new Node;       // 创建哨兵节点(数据为默认值,无实际意义)_head->_next = _head;   // 哨兵节点的next指向自身_head->_prev = _head;   // 哨兵节点的prev指向自身_size = 0;              // 初始元素个数为0
}// 默认构造函数
list()
{empty_init();
}// 拷贝构造函数(深拷贝)
list(const list<T>& lt)
{empty_init();  // 先初始化当前链表为空白状态// 遍历源链表,将每个元素插入到当前链表尾部for (auto e : lt){push_back(e);}
}// 交换两个list的内容
void swap(list<T>& lt)
{std::swap(_head, lt._head);  // 交换哨兵节点指针std::swap(_size, lt._size);  // 交换元素个数
}// 赋值运算符重载(现代写法:利用拷贝构造+交换)
list<T>& operator=(list<T> lt)  // 传值参数,自动拷贝一份源对象
{swap(lt);  // 交换当前对象与拷贝的临时对象return *this;  // 临时对象销毁时自动释放原数据
}// 析构函数
~list()
{clear();         // 清空所有有效元素delete _head;    // 释放哨兵节点_head = nullptr; // 避免野指针
}

解析

  • empty_init():初始化哨兵节点形成 “自环”,使空链表的操作与非空链表统一(无需额外判断_head是否为nullptr)。
  • 拷贝构造:采用 “深拷贝”,通过遍历源链表插入元素,确保新链表与原链表独立(修改新链表不影响原链表)。
  • 赋值运算符:利用 “值传递” 产生临时副本,再通过 swap 交换,既简化实现,又自动处理自赋值问题(临时副本与自身交换不影响)。
  • 析构函数:先调用clear()删除所有有效节点,再删除哨兵节点,避免内存泄漏。

3.4 元素操作:插入与删除

// 在指定位置插入元素
iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;    // 获取迭代器指向的节点Node* newnode = new Node(x);  // 创建新节点Node* prev = cur->_prev;  // 记录当前节点的前一个节点// 调整指针关系:prev <-> newnode <-> curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;  // 元素个数+1return iterator(newnode);  // 返回指向新节点的迭代器
}// 删除指定位置的元素
iterator erase(iterator pos)
{Node* cur = pos._node;    // 获取要删除的节点Node* prev = cur->_prev;  // 前一个节点Node* next = cur->_next;  // 后一个节点delete cur;  // 释放当前节点内存// 调整指针关系:prev <-> nextprev->_next = next;next->_prev = prev;--_size;  // 元素个数-1return iterator(next);  // 返回指向删除节点下一个位置的迭代器
}

解析

  • insert:通过调整指针将新节点插入到pos之前,时间复杂度 O (1)(双向链表的优势)。
  • erase:删除pos指向的节点后,返回下一个节点的迭代器(原迭代器失效,需用返回值更新)。

3.5 首尾操作(push/pop)

基于inserterase实现的便捷接口:

// 尾插:在end()位置(哨兵节点前)插入
void push_back(const T& x)
{insert(end(), x);
}// 头插:在begin()位置(第一个元素前)插入
void push_front(const T& x)
{insert(begin(), x);
}// 头删:删除begin()位置的元素
void pop_front()
{erase(begin());
}// 尾删:删除end()前一个位置的元素(--end())
void pop_back()
{erase(--end());
}

解析

  • 利用已实现的inserterase简化首尾操作,无需重复编写指针调整逻辑,体现代码复用。

3.6 清空与大小

// 清空所有有效元素(保留哨兵节点)
void clear()
{iterator it = begin();while (it != end()){it = erase(it);  // 循环删除,用返回值更新迭代器(原迭代器失效)}
}// 返回元素个数
size_t size()
{return _size;
}

解析

  • clear()只删除有效元素,保留哨兵节点,使链表仍可继续使用(如需完全销毁需调用析构函数)。
  • size()直接返回_size,效率 O (1)(优于遍历计数的 O (n))。

四、辅助结构与函数

用于测试 list 容器的示例结构和打印函数:

// 测试用结构体AA
struct AA
{AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;
};// 打印容器内容的模板函数(支持所有有const迭代器的容器)
template<typename Container>
void print_container(const Container& con)
{// 注意:需要用typename声明迭代器类型(依赖模板参数)typename Container::const_iterator it = con.begin();while (it != con.end()){cout << *it << " ";++it;}cout << endl;
}

解析

  • AA结构体用于测试 list 存储自定义类型的情况。
  • print_container是通用打印函数,通过 const 迭代器遍历容器,确保不会修改元素,typename用于告知编译器Container::const_iterator是类型(而非静态成员)。

总结

该实现完整模拟了 C++ 标准库中 list 容器的核心功能,采用双向链表 + 哨兵节点设计,通过迭代器封装节点访问,提供了高效的插入、删除操作(O (1))。关键特点包括:

  1. 哨兵节点简化边界处理;
  2. 迭代器模板适配 const / 非 const 场景;
  3. 拷贝构造和赋值运算符实现深拷贝,确保容器独立性;
  4. 接口设计与标准库一致,便于理解和使用。

文章转载自:

http://6VC7z041.mjmtm.cn
http://bjk8vQKW.mjmtm.cn
http://cI1DyCol.mjmtm.cn
http://FBY880Yp.mjmtm.cn
http://DGkUbLGZ.mjmtm.cn
http://R7oguJqm.mjmtm.cn
http://sQemH3e3.mjmtm.cn
http://DUD8gw83.mjmtm.cn
http://UEC8IAWX.mjmtm.cn
http://vSE4Qpz5.mjmtm.cn
http://zaoVlbaD.mjmtm.cn
http://vdfA4PWo.mjmtm.cn
http://Ko6qD1eU.mjmtm.cn
http://xgX0sjCj.mjmtm.cn
http://nPmfQGn4.mjmtm.cn
http://UktQ0Mgk.mjmtm.cn
http://tL64bQzq.mjmtm.cn
http://f5LKJA4a.mjmtm.cn
http://lKKHzitE.mjmtm.cn
http://GTizxZys.mjmtm.cn
http://T27Q7Ic6.mjmtm.cn
http://2YwLBTML.mjmtm.cn
http://0FEcB2g7.mjmtm.cn
http://Zah8KAR9.mjmtm.cn
http://ZlQhdot9.mjmtm.cn
http://VI46wsn0.mjmtm.cn
http://ByRYbiPv.mjmtm.cn
http://kRKJOnDq.mjmtm.cn
http://JmY0NwMw.mjmtm.cn
http://skoq0gcY.mjmtm.cn
http://www.dtcms.com/a/368270.html

相关文章:

  • 鹿客发布旗舰新品AI智能锁V6 Max,打造AI家庭安全领域新标杆
  • 【GEOS-Chem 输入数据】使用 AWS CLI 访问 GEOS-Chem 数据
  • 23种设计模式——原型模式 (Prototype Pattern)详解
  • 《Cocos Creator的2D、3D渲染使用记录》
  • Conda 使用py环境隔离
  • 数据结构:栈和队列力扣算法题
  • 深度学习之第八课迁移学习(残差网络ResNet)
  • 数据一致性、AI样本可追溯性与数据治理
  • 基于MATLAB的CNN大气散射传播率计算与图像去雾实现
  • 【Redis】初识 Redis 与基础数据结构
  • 分布式常见面试题整理
  • “卧槽,系统又崩了!”——别慌,这也许是你看过最通俗易懂的分布式入门
  • 数字时代的 “安全刚需”:为什么销售管理企业都在做手机号码脱敏
  • 乐观并发: TCP 与编程实践
  • 两条平面直线之间通过三次多项式曲线进行过渡的方法介绍
  • if __name__=‘__main__‘的用处
  • MySQL知识回顾总结----数据类型
  • WeaveFox AI智能开发平台介绍
  • Oracle:select top 5
  • sub3G、sub6G和LB、MB、HB、MHB、LMHB、UHB之间的区别和联系
  • Tenda AC20路由器缓冲区溢出漏洞分析
  • 52核心52线程,Intel下一代CPU憋了个大的
  • 50kNm风能传动轴扭转疲劳检测试验台指标
  • 蓓韵安禧DHA温和配方:安全营养的智慧守护
  • Kafka面试精讲 Day 8:日志清理与数据保留策略
  • 轨迹文件缺少时间
  • 国产数据库之YashanDB:新花怒放
  • 医疗AI中GPU集群设计与交付实践
  • 基于Compute shader的草渲染
  • go webrtc - 1 go基本概念