(C++) list底层模拟实现

 个人主页:Lei宝啊 

愿所有美好如期而遇


首先,list底层是一个带头双向循环链表,再一个,我们还要解决一个问题,list的迭代器,vector和string的迭代器可以直接++,是因为他们的地址空间是连续的,而链表不是,所以链表的迭代器封装的不是原生指针,我们需要想办法解决这个问题。

我们要解决list<T>::iterator可以++,既然我们不能封装原生指针,那么我们就对他进行运算符重载,但是在我们模拟的list类里,是不应该有这样的用法:list<int> lt, lt.operater++(),我们想要的是迭代器的运算符重载,即: list<int>::iterator it = lt.begin(); it++  这样的用法,那么我们就写一个迭代器的类,在这个类里重载++运算符。

首先是链表,我们先写出带头双向循环链表的结构,以及他的构造函数,至于为什么不用class类,是因为这样做后续可直接使用他的成员变量,如果写成类,还需要get  set函数,比较麻烦,我们模拟实现理解原理即可,所以为了省事,不使用class。

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类的实现,第一个typedef是将链表的类型重命名,第二个typedef是将迭代器类进行重命名,第三个typedef也是将迭代器类进行重命名,并且这两个迭代器是为了区分const和非const,模版参数我们也可以看得出来。

template<class T>
class list
{typedef ListNode<T> Node;
public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;//empty_init方法是初始化一个哨兵位, 接着就是构造函数,也就是初始化一个哨兵位;void empty_init(){_head = new Node();_head->prev = _head;_head->next = _head;}//构造函数//list<int> lt(); &lt  thislist<T>(){empty_init();}void swap(list<T>& x){std::swap(_head, x._head);}//拷贝构造//对于拷贝构造,我们需要进行深拷贝,先初始化一个哨兵位,然后遍历被拷贝的链表,             //用push_back方 法不断尾插节点,完成深拷贝;list(list<T>& copy){			 empty_init();iterator it = copy.begin();while (it != copy.end()){push_back(it._node->data);it++;}}//最后就是赋值运算符重载,我们传参不传引用,就是为了在传参时拷贝构造出一个链表,           //然后我们只需要交换_head,就可以完成赋值,这里我们写一个swap方法。list<T>& operator=(list<T> tmp){swap(tmp);return *this;}//push_back方法,只需要理清楚插入节点newnode和_head的关系就好;void push_back(const T& x = T()){//_head->prev newnode _head//Node* newnode = new Node(x);//Node* tail = _head->prev;////tail->next = newnode;//newnode->prev = tail;//_head->prev = newnode;//newnode->next = _head;insert(end(), x);}//insert方法也是类似于push_back方法,我们写出insert方法后,                     //push_back完全可以复用insert方法,链表的insert没有迭代器失效问题;iterator insert(iterator pos, const T& x = T()){//pos->prev newnode posNode* cur = pos._node;Node* prev = cur->prev;//prev newnode curNode* newnode = new Node(x);newnode->next = cur;cur->prev = newnode;prev->next = newnode;newnode->prev = prev;return newnode;}//begin和end方法,也就是返回链表的开始位置和尾部位置,注意,                             //开始位置是_head->next,尾部位置是_head->prev;iterator begin(){return iterator(_head->next);}const_iterator begin() const{return _head->next;}iterator end(){return iterator(_head);}const_iterator end() const{return _head;}//erase方法我们使用时还是要注意迭代器失效问题,删除一个节点后,                          //迭代器就是一个野指针,也就失效了;iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->prev;Node* next = cur->next;prev->next = next;next->prev = prev;return iterator(next);}//clear方法,我们需要遍历链表释放所有节点,我们这里可以复用erase方法;void clear(){iterator it= begin();while (it != end()){erase(it++);}}//析构函数,我们调用clear释放掉其他节点后,我们再单独释放掉哨兵位;~list(){clear();delete _head;_head = nullptr;}private:Node* _head;};

__list_iterator类的实现

模版参数有三个,因为我们要明白,iterator是有解引用操作的,非const对象可以对值修改,但是const对象不可以,当然,我们可以写他的重载,但是有了这样一个模板参数,只需要修改返回值即可,并且我们重载了->运算符,这个返回值时一个指针,我们仍然需要区分是否是const对象。

template<class T, class ref, class ptr>
struct __list_iterator
{typedef ListNode<T> Node;public://构造函数__list_iterator(Node* node):_node(node){}//前置++__list_iterator& operator++(){_node = _node->next;return *this;}//后置++__list_iterator& operator++(int){//auto tmp = __list_iterator(this->_node);auto tmp = __list_iterator(*this);_node = _node->next;return tmp;}//前置--__list_iterator& operator--(){_node = _node->prev;return *this;}//后置--__list_iterator& operator--(int){//auto tmp = __list_iterator(this->_node);auto tmp = __list_iterator(*this);_node = _node->prev;return tmp;}ref operator*(){return _node->data;}ptr operator->(){return &_node->data;}bool operator!=(const __list_iterator& x){return _node != x._node;}Node* _node;
};

那么我们是如何使用的呢?

list<int>::iterator it = lt.begin(),lt是list<int>类型,非const,则调用非const begin方法,但是我们的begin返回值是_head->next,是个Node*类型的指针,我们返回值类型可是iterator,封装的是__list_iterator类,但是这个类的构造函数所传的参数是单参数,而且也是Node*,也就是说,会进行隐式类型转换,_head->next会构造出一个__list_iterator临时对象,然后进行返回,然后这个临时对象再拷贝构造it,如果编译器进行优化,也就变成了直接进行拷贝构造。

我们的主要问题,对++运算符的重载也就不是问题了,唯一需要注意的是前置和后置++,区分它们只能是通过参数了,我们一般是给后置++的参数加上int。

最后一个问题就是->的重载,如果我们使用是不是这样使用:

假设我们有一个结构体,AA{int a; int b},list<AA> lt; 我们要通过iterator访问他的成员变量,list<AA>::iterator it = lt.begin(),返回值就是AA的地址,那么我们如何访问他的成员变量呢?

it.operator->()->a,或者是it->->a?

编译器省略了一个->,所以我们使用时直接it->a即可

 

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

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

相关文章

An Association-Based Fusion Method for Multi-Modal Classification

F ∈ { C o n c a t ; A d d } \in\{Concat;Add\} ∈{Concat;Add} 辅助信息 作者未提供代码

Red Hat Enterprise Linux 9.3 安装图解

引导和开始安装 选择倒计时结束前&#xff0c;通过键盘上下键选择下图框选项&#xff0c;启动图形化安装过程。需要注意的不同主板默认或者自行配置的固件类型不一致&#xff0c;引导界面有所不同。也就是说使用UEFI和BIOS的安装引导界面是不同的&#xff0c;如图所示。若手动调…

[C#]C# winform部署yolov8目标检测的openvino模型

【官方框架地址】 https://github.com/ultralytics/ultralytics 【openvino介绍】 OpenVINO&#xff08;Open Visual Inference & Neural Network Optimization&#xff09;是由Intel推出的&#xff0c;用于加速深度学习模型推理的工具套件。它旨在提高计算机视觉和深度学…

HarmonyOS鸿蒙学习基础篇 - 什么是HarmonyOS

概述 HarmonyOS是华为开发的一款面向未来的全场景分布式智慧操作系统&#xff0c;将逐步覆盖18N全场景终端设备&#xff1b; 对消费者而言 HarmonyOS用一个‘统一的软件系统’ 从根本上解决消费者面对大量智能终端体验割裂的问题&#xff0c;为消费者带来同意便利安全的智慧化全…

【C语言】编译和链接深度剖析

文章目录 &#x1f4dd;前言&#x1f320; 翻译环境和运行环境&#x1f309;翻译环境 &#x1f320;预处理&#xff08;预编译&#xff09;&#x1f309;编译 &#x1f320;词法分析&#x1f320;语法分析 &#x1f309;语义分析&#x1f320;汇编 &#x1f309; 链接&#x1f…

dns正反解析配置

1.配置正向解析baidu.com 1、下载bind包 [rootlocalhost ~]# yum install bind -y 2、对配置文件修改 [rootlocalhost ~]# vim /etc/named.conf 3、对数据文件修改 [rootlocalhost ~]# vim /var/named/baidu 4、重启服务 [rootlocalhost ~]# systemctl restart named.service 5…

数据分析中常用的指标或方法

一、方差与标准差二、协方差三、皮尔逊系数四、斯皮尔曼系数五、卡方检验六、四分位法和箱线图七、 一、方差与标准差 总体方差 V a r ( x ) σ 2 ∑ i 1 n ( x i − x ˉ ) 2 n ∑ i 1 n x i 2 − n x ˉ 2 n E ( x 2 ) − [ E ( x ) ] 2 Var(x)\sigma^2\frac {\sum\l…

端口映射的定义、特点、场景、实例、常见问题回答(Port Mapping)

目 录 一、端口映射&#xff08;Port Mapping&#xff09; 二、端口映射应用场景&#xff08;什么时候用到端口映射&#xff09; &#xff08;一&#xff09;、使用端口映射的条件 &#xff08;二&#xff09;使用端口映射的具体场景 三、端口映射技术的特点 …

Redis--Geo指令的语法和使用场景举例(附近的人功能)

文章目录 前言Geo介绍Geo指令使用使用场景&#xff1a;附近的人参考文献 前言 Redis除了常见的五种数据类型之外&#xff0c;其实还有一些少见的数据结构&#xff0c;如Geo&#xff0c;HyperLogLog等。虽然它们少见&#xff0c;但是作用却不容小觑。本文将介绍Geo指令的语法和…

git使用的常用指令

git作为一个版本控制工具&#xff0c;和maven并合称为实习的两大杀手工具。今天我来给大家介绍一下git的常用指令&#xff0c;帮助大家在实习和多人协同开发的时候提供一些帮助。 找到git管理的文件夹 命令1 git init 这个命令是为了初始化本地库 命令2 查看当前的git状态…

AWS 亚马逊云服务专题学习

目录 1. 学习大纲2. 学习地址3. 系列博客4. AWS 初识 1. 学习大纲 AWS 基础知识&#xff1a;IAM、EC2、负载均衡、Auto Scaling、EBS、EFS、Route 53、RDS、ElastiCache、S3、CloudFrontAWS CLI&#xff1a;CLI 设置、在 EC2 上的使用、最佳实践、SDK、高级使用深度数据库比较…

微服务实战项目_天机学堂01_初识项目

文章目录 一.项目简述二.Jenkins三.模拟真实业务:紧急bug修复和代码阅读四.测试和部署五.代码阅读-获取登录用户 一.项目简述 Q:天机学堂是什么? A:天机学堂是一个基于微服务架构的生产级在线教育项目 主要有两个端(项目已上线,可以点击查看): 管理后台: https://tjxt-admi…