【C++】深度解析:用 C++ 模拟实现 list 类,探索其底层实现细节

 

目录

list介绍

list模拟实现

list 节点类

list 的迭代器

定义 

构造函数

解引用

operator前置++和--与后置++和--

operator==与operator!=

list 类

构造函数

 begin()和end()

 拷贝构造

erase()

clear()

析构函数

insert

 push_back 和 push_front

pop_back 和 pop_front

完整代码


 

⭐list介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)。

⭐list模拟实现

  1. list的底层是双向链表结构,包含有一个哨兵节点。
  2. 模拟实现list,要实现下列三个类:
  •         ①list节点类
  •         ②迭代器的类
  •         ③list主要功能的类(size(),empty()...)

模拟实现list的类的基本功能(增删等操作)要建立在迭代器类和节点类均已实现好的情况下才得以完成。

✨list 节点类

  • 定义list中的节点ListNode,包含前驱指针,后驱指针和数据变量;
  • 使用struct而不使用class定义类,是为了方便访问每个一个节点 ,struct默认是pbulic,而class中成员变量要定义为private,不方便访问。
	template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};

✨list 的迭代器

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

1. 原生态指针,比如:vector

2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

  1. 指针可以解引用,迭代器的类中必须重载operator*()
  2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
  4. 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
  5. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

📖定义 

	template<class T,class Ref,class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T,Ref,Ptr> Self;//内置类型 指针Node* _node;}
  • 可以发现这里list的模板含有三个类型参数,这样做是为了方便让编译器能依照模板自动生成一个const迭代器,而不需要我们手动写。
  • 两个参数名Ref(reference:引用)和Ptr(pointer:指针),见名知义。

📖构造函数

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

 迭代器指向所传节点

📖解引用

//*it 解引用
//T& operator*()
Ref operator*()
{return _node->_data;
}
//it->
//T* operator->()
Ptr operator->()
{return &_node->_data;
}
  • 重载 * ,解引用就可以直接访问到节点里面的数据data 
  • 如果访问的数据是Date类型的,那么重载 -> 就可以访问到Date类里面的_year、_day等(如果是Date类,那data就说Date类里面的数据) 

📖operator前置++和--与后置++和--

//前置++
Self& operator++()
{_node = _node->_next;return *this;
}
//后置++
Self operator++(int)
{Self tmp(*this);_node = _node->_next;return tmp; // tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用
}
//前置--
Self& operator--()
{_node = _node->_prev;return *this;
}
//后置--
Self operator--(int)
{Self tmp(*this);_node = _node->_prev;return tmp;// tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用
}

注:

  • 这里值得注意的是,为了区分前置和后置,我们会在后置的重载函数中传缺省值int,从而与前置构成重载 
  • 局部变量不能返回引用

📖operator==与operator!=

bool operator!=(const Self& it)
{return _node != it._node;
}
bool operator==(const Self& it)
{return _node == it._node;
} 
  • 这个比较的就是两个迭代器中指向的节点的地址是否相等,从而可以判断迭代器是否指向了end() 。

✨list 类

template<class T>
class list
{typedef ListNode<T> Node;public :typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T,const T&,const T*> const_iterator;
private:Node* _head;size_t _size;
}
  • 迭代器写成三个参数类型的模板,可以让编译器生成两个类,一个普通的iterator和一个const_iterator
  • const_iterator 只能读取它所指向的元素,不能修改。

📖构造函数

//带头双向循环链表的构造函数
list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}
  • 初始化时,new一个头节点,然后使这个头节点的前后指针都指向自己 

📖begin()和end()

iterator begin()
{return _head->_next;//也可以直接这么写,进行隐式类型转换(单参数的构造函数支持隐式类型转换)
}
iterator end()
{return iterator(_head);//匿名对象,局部变量 不能返回引用
}
//const迭代器需要的是迭代器指向的内容不能修改
//const iterator 是迭代器本身不能修改
const_iterator begin() const
{return _head->_next;
}
const_iterator end()const
{return const_iterator(_head);
}
  • 返回时使用了匿名对象,不用实例化一个新的对象
  • 不能引用返回的匿名对象
  • const迭代器指向的内容不能修改 

 📖拷贝构造

//lt2(lt)
list(const list<T>& lt) 
{empty_init();for (auto& e : lt){push_back(e);}	
}
  • list的每个节点不连续,需要一个个拷贝 
  • 需要析构的时候,一般就需要自己写深拷贝

📖erase()

iterator erase(iterator pos)
{//prev cur nextNode* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;_size--;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);//匿名对象,局部变量 不能返回引用
}
  • delete删除节点,每一个节点都是动态开辟出来的

  • 返回被删除元素后面一个元素的迭代器位置

📖clear()

void clear()
{iterator it = begin();while (it != end()){it = erase(it);}//不清除头节点 
}
  • erase之后,it更新到下一个节点的位置继续erase
  • clear()不删除头节点

📖析构函数

~list()
{clear();delete _head;_head = nullptr;
}
  • clear()删除除去头节点以外的所有节点
  • delete删除头节点

📖insert

void insert(iterator pos, const T& val)
{Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;_size++;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}
  • 插入到pos位置之前 

 📖push_back 和 push_front

void push_back(const T& x)
{insert(end(), x);
}
void push_front(const T& x)
{insert(begin(), x);
}
  • 复用insert ()

📖pop_back 和 pop_front

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

✨完整代码

template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};template<class T,class Ref,class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T,Ref,Ptr> Self;//内置类型 指针Node* _node;ListIterator(Node* node):_node(node){}//*it 解引用//T& operator*()Ref operator*(){return _node->_data;}//it->//T* operator->()Ptr operator->(){return &_node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}//后置++Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp; // tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用}//前置--Self& operator--(){_node = _node->_prev;return *this;}//后置--Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;// tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;} };template<class T>class list{typedef ListNode<T> Node;public ://typedef ListIterator<T> iterator;//typedef ListConstIterator<T> const_iterator;//重新写一个ListConstIterator类(这个方法比较冗余)typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T,const T&,const T*> const_iterator;//写成模板,让编译器生成两个类,而不是我们自己写/*iterator begin(){return iterator(_head->_next);}*/iterator begin(){return _head->_next;//也可以直接这么写,进行隐式类型转换(单参数的构造函数支持隐式类型转换)}iterator end(){return iterator(_head);//匿名对象,局部变量 不能返回引用}//const迭代器需要的是迭代器指向的内容不能修改//const iterator 是迭代器本身不能修改const_iterator begin() const{return _head->_next;}const_iterator end()const{return const_iterator(_head);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){//this->empty_init();empty_init();}//lt2(lt)list(const list<T>& lt) {empty_init();for (auto& e : lt){push_back(e);}}//需要析构,一般就需要自己写深拷贝void swap(list<T>& lt){//lt是局部变量std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}//不清除头节点 }~list(){clear();delete _head;_head = nullptr;}/*void push_back(const T& x){Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}*/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());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;_size++;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;}iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;_size--;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);//匿名对象,局部变量 不能返回引用}size_t size()const{return _size;}bool empty(){return _size == 0;}private:Node* _head;size_t _size;};

____________________

⭐感谢你的阅读,希望本文能够对你有所帮助。如果你喜欢我的内容,记得点赞关注收藏我的博客,我会继续分享更多的内容。⭐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/875872.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode—769. 最多能完成排序的块【中等】

2024每日刷题&#xff08;149&#xff09; Leetcode—769. 最多能完成排序的块 实现代码 class Solution { public:int maxChunksToSorted(vector<int>& arr) {int ans 0;int mx INT_MIN;for(int i 0; i < arr.size(); i) {mx max(arr[i], mx);if(mx i) {a…

力扣高频SQL 50题(基础版)第六题

文章目录 1378. 使用唯一标识码替换员工ID题目说明思路分析实现过程结果截图总结 1378. 使用唯一标识码替换员工ID 题目说明 Employees 表&#xff1a; ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | ------…

登顶官方热榜,“超级智能体创造营”一期获奖名单公开!

自超级智能体创造营活动7月11日上线以来&#xff0c;受到很多平台开发者的关注&#xff0c;很开心看到首期创造营聚集了诸多优秀的平台开发者&#xff0c;共同参与到主题创作中&#xff0c;提交了100 的创意智能体&#xff01; 经过官方伙伴历经多轮、多维度的专业评审&#x…

yarn安装electron时报错RequestError:socket hang up

安装electron时候&#xff0c;出现RequestError:socket hang up这样的错误&#xff0c;找了半天很多方式都是用旧淘宝源&#xff0c;导致根本安装不上去。 在项目的根目录下创建.npmrc文件&#xff0c;添加以下内容 # registryhttps://mirrors.huaweicloud.com/repository/np…

乐尚代驾八订单执行三

司机到达代驾终点&#xff0c;代驾结束了。结束代驾之后&#xff0c; – 获取额外费用&#xff08;高速费、停车费等&#xff09; – 计算订单实际里程&#xff08;实际与预估有偏差&#xff09; – 计算代驾实际费用 – 系统奖励 – 分账信息 – 生成最终账单 计算订单…

{Spring Boot 原理篇} Spring Boot自动装配原理

SpringBootApplication 1&#xff0c;Spring Boot 应用启动&#xff0c;SpringBootApplication标注的类就是启动类&#xff0c;它去实现配置类中的Bean的自动装配 SpringBootApplication public class SpringbootRedis01Application {public static void main(String[] args)…

DL/T645、IEC104转BACnet网关实现实时数据采集

BA102网关是钡铼技术专为实现电力协议DL/T645、IEC104与楼宇自控协议BACnet相互转化而研发的。它下行采集支持Modbus RTU、Modbus TCP、DL/T645、IEC104等协议&#xff0c;上行转发则支持BACnet IP和BACnet MS/TP协议&#xff0c;从而实现了电力协议与楼宇自控协议之间的相互转…

切换数据失败0x1671分析

1、问题背景 切换双卡数据开关&#xff0c;无法切换成功&#xff0c;且单机必现该问题 2、问题分析 搜索Log发现相关拨号无法建立成功&#xff0c;返回0x1671&#xff0c;无法建立PDN连接。 相关拨号上层未下发相关AT命令&#xff0c;属于上层报错&#xff0c;并非网络问题&…

AI有关的学习和python

一、基本概念 AIGC&#xff08;AI Generated content AI 生成内容&#xff09; AI生成的文本、代码、图片、音频、视频。都可以成为AIGC。 Generative AI&#xff08;生成式AI&#xff09;所生成的内容就是AIGC AI指代计算机人工智能&#xff0c;模仿人类的智能从而解决问题…

解读:基于图的大模型提示技术

【引子】大模型的兴起&#xff0c; 使得读论文成为了学习中的一种常态。如果一篇论文没有读懂&#xff0c;不用担心&#xff0c;可以再读一篇该领域内容相近的论文&#xff0c;量变可能会产生质变。就像编程语言一样&#xff0c;你永远无法精通一门编程语言&#xff0c;除非&am…

为什么idea建议使用“+”拼接字符串

今天在敲代码的时候&#xff0c;无意间看到这样一个提示&#xff1a; 英文不太好&#xff0c;先问问ChatGPT&#xff0c;这个啥意思&#xff1f; IDEA 提示你&#xff0c;可以将代码中的 StringBuilder 替换为简单的字符串连接方式。 提示信息中说明了使用 StringBuilder 进行…

Android APP 音视频(01)MediaCodec解码H264码流

说明&#xff1a; 此MediaCodec解码H264实操主要针对Android12.0系统。通过读取sd卡上的H264码流Me获取视频数据&#xff0c;将数据通过mediacodec解码输出到surfaceview上。 1 H264码流和MediaCodec解码简介 1.1 H264码流简介 H.264&#xff0c;也被称为MPEG-4 AVC&#xff…

SCADA系统智能化管理工厂 操作方便易于使用

SCADA系统是将工厂进行智能化升级的专业系统&#xff0c;可根据需求与预算定制&#xff0c;从多设备的采集、数据的显示、数据可视化展示、分析图像、边缘计算、协议解析、数据转发、数据清洗、数据转换、数据存储、数据传输等等&#xff0c;各种功能均可根据实际需求进行定制&…

如何在测试中保护用户隐私!

在当今数据驱动的时代&#xff0c;用户隐私保护成为了企业和开发团队关注的焦点。在软件测试过程中&#xff0c;处理真实用户数据时保护隐私尤为重要。本文将介绍如何在测试中保护用户隐私&#xff0c;并提供具体的方案和实战演练。 用户隐私保护的重要性 用户隐私保护不仅是法…

Spring MVC 应用分层

1. 类名使⽤⼤驼峰⻛格&#xff0c;但以下情形例外&#xff1a;DO/BO/DTO/VO/AO 2. ⽅法名、参数名、成员变量、局部变量统⼀使⽤⼩驼峰⻛格 3. 包名统⼀使⽤⼩写&#xff0c;点分隔符之间有且仅有⼀个⾃然语义的英语单词. 常⻅命名命名⻛格介绍 ⼤驼峰: 所有单词⾸字⺟…

使用api 调试接口 ,配置 Header 、 body 远程调试 线上接口

学习目标&#xff1a; 目标 使用api 调试接口 &#xff0c;配置 Header 、 body 远程调试 线上接口 学习内容&#xff1a; 内容 设置请求方式 2. 选择 POST 提交 3.设置 Header 一般默认的 4个 header 属性就可以直接使用&#xff0c;如有特殊情况&#xff0c;需进行属性设…

『 Linux 』信号的捕捉及部分子问题

文章目录 信号的捕捉sigaction函数未决信号集的置零时机信号处理过程的阻塞可重入函数volatile 关键字SIGCHLD 信号 信号的捕捉 该图为基于信号处理为用户自定义动作的图解; 信号的捕捉 当一个信号被递达时,如果该信号的处理动作是用户自定义的函数(如int sighandler(int))时就…

文物实时状态监控的保护系统

文物是宝贵的历史遗产和文化瑰宝&#xff0c;其保护是我们共同的责任。为了实现文物的安全保护&#xff0c;现代科技提供了各种环境监测设备&#xff0c;可以实时监控文物的状态并采取相应的保护措施。本文将介绍一种利用各种环境监测设备实现文物实时状态监控的保护系统。 一…

【视频讲解】ResNet深度学习神经网络原理及其在图像分类中的应用|附Python代码

全文链接&#xff1a;https://tecdat.cn/?p37134 原文出处&#xff1a;拓端数据部落公众号 分析师&#xff1a;Canglin Li 本文深入探讨了卷积层&#xff08;Convolutional Layer&#xff09;在深度学习框架中的核心作用与操作机制&#xff0c;并分析了其在特征提取、网络构…

近期代码报错解决笔记

1.TypeError: ‘bool’ object is not callable 想print("Type of head:", type(entity_emb[head]))&#xff0c;结果报如下错误&#xff1a; 源代码&#xff1a; 因为 print 仍然被当作一个布尔值处理&#xff0c;而不是作为函数调用。这个问题的根源在于 print …