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

STL——list

介绍

list 双向链表,擅长中间位置 O(1) 插入/删除,但牺牲了随机访问性能与内存紧凑性;当需要频繁在非尾端增删元素时,list 是首选容器。

模拟实现

构造函数

成员初始化列表只能 初始化成员本身,不能在列表里访问成员的成员

template<class T>
struct myList_node
{myList_node(const T& val = T()):_val(val),_prev(nullptr),_next(nullptr){}T _val;myList_node* _prev;myList_node* _next;
};template<class T>
class myList {typedef myList_node<T> Node;
public:myList()	// 初始化头结点:_head(new Node){_head->_next = _head;_head->_prev = _head;}private:Node* _head;
};

拷贝构造

遍历旧链表,逐个节点拷贝到新链表

如果 T 是申请了内存资源的自定义类型,那么这里会触发 T 的拷贝构造,只要 T 的 拷贝构造 实现了深拷贝的逻辑,这里构造新节点时就不需要考虑深拷贝问题

	myList(const myList<T>& ml)	// 拷贝构造:_head(new Node){_head->_next = _head;_head->_prev = _head;Node* cur = ml._head->_next;	// 旧Node* tail = _head;	// 新while (cur != ml._head){Node* newNode = new Node(cur->_val);	// 构造新节点tail->_next = newNode;	// 链接新节点newNode->_prev = tail;tail = tail->_next;cur = cur->_next;}tail->_next = _head;	// 最后链接头节点_head->_prev = tail;}

迭代器

list 将迭代器封装为单独的类,并在类中实现运算符重载

构造

目前使用指针来实现迭代器

template<class T>
struct _myList_iterator
{typedef myList_node<T> Node;Node* _node;_myList_iterator(Node* node)	// 使用节点指针构造迭代器对象:_node(node){}
};

运算符重载

重载迭代器可能用到的运算符,使迭代器的运算符的功能符合我们的预期

    _myList_iterator<T>& operator++()	// 前置++{_node = _node->_next;return *this;}_myList_iterator<T> operator++(int)	// 后置++{_myList_iterator<T> tmp(_node);	// 构造临时对象_node = _node->_next;return tmp;}_myList_iterator<T>& operator--()	// 前置--{_node = _node->_prev;return *this;}_myList_iterator<T> operator--(int)	// 后置--{_myList_iterator<T> tmp(_node);	// 构造临时对象_node = _node->_prev;return tmp;}T& operator*(){return _node->_val;}// 加上 const 让 const 的迭代器也可以调用bool operator==(const _myList_iterator<T>& ml_it) const	// 判断迭代器的指向 是否相等{return _node == ml_it._node;}bool operator!=(const _myList_iterator<T>& ml_it) const{return !(_node == ml_it._node);}

另外,为迭代器类重载指针解引用运算符 -> 需要注意:

如果选择第一种,那么返回的是 T 的指针类型,随后可以继续接 -> 运算符来访问 T 中的成员;

如果选择第二种,那么返回的是 T 的引用,随后可以接 . 运算符来访问 T 中的成员;

    T* operator->(){return &_node->_val;}T& operator->(){return _node->_val;}

标准库实现的是第一种,那么实际上调用时,展开函数调用是这个样子:

_myList_iterator<T> it(_node);
it.operator->()->“T的成员”

这里就连续地使用了两个 -> 解引用运算符,编译器为了优化可读性,直接两个变一个:

_myList_iterator<T> it(_node);
it->“T的成员”

const 迭代器

const 迭代器无非就是不想让你修改 该迭代器 所指向的 对象 的 成员

那么在迭代器类中,涉及到对 迭代器 指向 对象 的操作的 方法,就是两个 解引用运算符的重载

将其 返回值类型 加上 const 属性 修饰即可

但是标准库中的实现:是为迭代器类添加了额外的 2 个模板参数,让迭代器类在实例化的时候,就确定该迭代器 是否为 const 迭代器

template<class T, class Ref, class Ptr>
struct _myList_iterator
{typedef myList_node<T> Node;Node* _node;......Ref operator*(){return _node->_val;}......Ptr operator->(){return &_node->_val;}
};

该迭代器实例化时传参示例如下:

	_myList_iterator<string, string&, string*>;_myList_iterator<string, const string&, const string*>;

在 list 中实例化

实例化迭代器

template<class T>
class myList {typedef myList_node<T> Node;typedef _myList_iterator<T, T&, T*> iterator;typedef _myList_iterator<T, const T&, const T*> const_iterator;public: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);}

析构函数

需要逐个释放节点,使用指针即可

    ~myList() {Node* cur = _head->_next;while (cur != _head) {Node* next = cur->_next; // 先保存下一个delete cur;	// 再释放当前cur = next;	// 继续}delete _head;_head = nullptr;}

operator=

 (copy-and-swap 的小技巧)

赋值运算符的重载,直接复用 拷贝构造 的逻辑:使用传值传参,这时形参是实参的拷贝,然后交换形参与被赋值对象的头节点指针;出了函数作用域, ml 会被自动调用析构函数释放掉

	myList<T>& operator=(myList ml){std::swap(_head, ml._head);return *this;}

insert

在迭代器 position 前,插入一个值为 val 的元素

	iterator insert(iterator position, const T& val){Node* cur = position->_node;Node* curPrev = cur->_prev;Node* newNode = new Node(val);	// new 一个新节点// 链接新节点curPrev->_next = newNode;newNode->_prev = curPrev;newNode->_next = cur;cur->_prev = newNode;return iterator(newNode);}

push_back

复用 insert

	void push_back(const T& val){insert(end(), val);}

erase

释放当前迭代器处的节点,并返回下个新节点的迭代器

但是注意不能删除头节点,破坏双链表结构

	iterator erase(iterator position){Node* cur = position._node;if (cur == _head)	// 不能删除头节点{return iterator(_head);}Node* curPrev = cur->_prev;	// 链接Node* curNext = cur->_next;curPrev->_next = curNext;curNext->_prev = curPrev;delete cur;return iterator(curNext);}

pop_back

复用 erase,也要注意不能删除头节点

	void pop_back(){if (_head->_prev != _head){erase(iterator(_head->_prev));}}

resize

void resize (size_t n, T val = T());

resize 依然要分段考虑,如果 n <= size( ),将链表从后向前缩减至 size() == n;(复用pop_back)

如果 n > size(),就 push_back n 个值为 val 的节点

那么首先实现 size(),难免要遍历一遍链表;当然,也可以提前在 myList 类中定义成员变量 _size,来记录当前链表的size

	size_t size(){size_t cnt = 0;iterator it = begin();while (it != end()){cnt++;it++;}return cnt;}void resize(size_t n, T val = T()){size_t cur_size = size();    // 提前缓存 size()while (cur_size > n){pop_back();cur_size--;}while (cur_size < n){push_back(val);cur_size++;}}

这里为避免每次循环都调用 size() ,提前缓存好即可(进一步提升性能还得是像标准库中一样,维护一个 _size 成员来记录)

小结

有了一些手撕容器的思路,要注意深拷贝问题,从 拷贝构造 到 赋值运算符重载;

元素 的 方法中,要注意构造对象时的操作; 方法中,要注意对资源的深度清理

迭代器的封装:主要是为了实现让迭代器的 运算符重载 支持预期的功能;以及迭代器需要实现两套(const

且在使用过程中,要注意迭代器的失效问题,在 vector 容器中扩容之后尤其注意;因为 list 不涉及扩容,所以迭代器失效问题不明显;但只要迭代器失效就不能再使用,否则行为未定义

注意被封装的迭代器中,对 -> 解引用运算符的重载,其实是为 list<string<T>> 这样的类型准备的;这样迭代器就可以通过 -> 解引用实现对 string<T> 成员的访问,实际上有两层解引用

http://www.dtcms.com/a/300525.html

相关文章:

  • 金融科技中的虚拟助手
  • 15.7 DeepSpeed实战:单卡38GB到多卡12GB,3倍效率提升的ZeRO-3配置全解
  • 【专题十五】BFS 解决 FloodFill
  • 多智能体系统设计:协作、竞争与涌现行为
  • 2025年7月25日-7月26日 · AI 今日头条
  • 【第六节】方法与事件处理器
  • 【计算机网络架构】网状型架构简介
  • C++ 多线程(一)
  • 详解力扣高频SQL50题之610. 判断三角形【简单】
  • Vscode的常用快捷键(摆脱鼠标计划)
  • [N1盒子] 斐讯盒子N1 T1通用刷机包(可救砖)
  • 金字塔降低采样
  • C语言:顺序表(上)
  • K8S 九 安全认证 TLS
  • 关于西门子博图基本指令的应用区别
  • VScode 支持 QNX 源码跳转
  • 【Python系列】从内存分析到性能剖析
  • Mysql 二进制安装常见问题
  • 2025年Solar应急响应公益月赛-7月wp
  • mac neo4j install verifcation
  • 论文阅读-IGEV
  • SecureCRT连接密钥交换失败
  • 基于LNMP架构的分布式个人博客搭建
  • 总结和对比Unity中的三种主要抗锯齿技术:FXAA、SMAA和TAA
  • 搭建DM数据守护集群
  • Java 代理机制详解:从静态代理到动态代理,彻底掌握代理模式的原理与实战
  • 【服务器与部署 30】Python内存优化实战:从内存泄漏到性能提升的完整解决方案
  • VLA-视觉语言动作模型
  • mac配置多版本jdk
  • 四、搭建springCloudAlibaba2021.1版本分布式微服务-加入openFeign远程调用和sentinel流量控制