[ C++ ] STL---list的模拟实现

目录

结点类的模拟实现

迭代器类的模拟实现

构造函数

前置++与后置++

前置- -与后置 - -

== 与 !=运算符重载

* 运算符重载

-> 运算符重载

普通迭代器总体实现代码

list类的实现

list类的成员变量

构造函数

迭代器

insert()

erase()

push_front/push_back/pop_front/pop_back

front/back

clear()

empty()

swap()

拷贝构造函数

赋值运算符重载

析构函数


结点类的模拟实现

list 的底层结构为带头双向循环链表,所以结点类里的成员变量

T _data(数据)ListNode<T>* _prev(前驱指针)ListNode<T>* _next(后继指针)

成员函数只需要构造函数即可(指针初始化为nullptr,数据以缺省参数的形式进行初始化);

template<class T>
struct ListNode
{//结点中的成员变量T _data;//数据ListNode<T>* _prev;//前驱指针ListNode<T>* _next;//后继指针//结点类的构造函数ListNode(const T& val=T()): _data(val), _prev(nullptr), _next(nullptr){}
};

迭代器类的模拟实现

迭代器并不关心容器底层的数据结构为顺序表、链表或者树型结构,提供统一的方式访问、修改容器中的数据并且遍历区间为左闭右开[begin,end);

vector与string的模拟实现中,迭代器均为原生指针,是因为vector和string底层物理空间连续(顺序表),那么list可否采用原生指针(结点指针)作为迭代器?

思考一:

若it为结点指针,it++,能否从链表的当前位置指向链表当前位置的下一位置?  (×)

思考二:

若it为结点指针,*it,能否得到链表结点中的数据_data?(×

解决方案:

采用原生指针作为list的迭代器均不满足需求,运算符重载可以对已有的运算符重新进行定义,赋予其另一种功能,从而满足需求,但是运算符重载的前提为自定义类型,而指针本身为内置类型,只能将结点指针封装为自定义类型,从而使用运算符重载满足需求;

迭代器的成员变量 : Node* _node;(_node为指向结点的指针)
迭代器的成员函数 : 运算符重载函数;

template<class T>
//使用struct关键字封装结点指针,方便访问数据成员_node
//若使用class关键字封装节点指针,需要提供函数接口访问_node
struct __List_iterator
{typedef ListNode<T> Node;Node* _node;
};

构造函数

//__List_iterator it();
//需要结点指针对_node进行有参构造且不能给定缺省值为nullptr,
//否则解引用操作导致系统崩溃
//__List_iterator(Node* node=nullptr)(×)
//因此迭代器遍历链表时必须给定有效参数,参数为结点指针;
__List_iterator(Node* node):_node(node){}

思考:迭代器内部需要实现析构函数,拷贝构造函数吗?

      1. 提供链表的结点指针给迭代器,方便迭代器访问链表结点,并不需要释放结点;

          而且对于内置类型(指针)成员变量,编译器自动生成的析构函数不做任何处理;

      2. 将一个迭代器拷贝给另一个迭代器,只需要两个迭代器指向同一个链表结点,

          而编译器自动生成的拷贝构造函数实现了浅拷贝,所以不需要实现拷贝构造函数;

前置++与后置++

前置++,this指针出作用域销毁,但是this指针指向的对象在函数结束不会被销毁,以传引用的方式返回以提升效率

//++it
__List_iterator<T>& operator++()
{_node = _node->_next;return *this;
}

返回类型太长,使用typedef重定义类型名;

typedef __List_iterator<T> self;
self& operator++()
{_node = _node->_next;return *this;
}

C++规定:

后置++重载时多增加一个int类型的参数,但调用函数时参数不需要传递,编译器自动传递;

后置++,tmp为临时拷贝对象,出作用域销毁,只能以传值的方式返回

self operator++(int)
{self tmp(*this);_node = _node->_next;return tmp;
}

前置- -与后置 - -

//--it
self& operator--()
{_node = _node->_prev;return *this;
}
//it--
self operator--(int)
{self tmp(*this);_node = _node->_prev;return tmp;
}

== 与 !=运算符重载

==运算符重载比较两个迭代器对象的_node指针指向是否相同;

bool operator==(const self& s)
{return _node == s._node;
}

!=运算符重载比较两个迭代器对象的_node指针指向是否相同;

bool operator!=(const self& s)
{return _node != s._node;
}

* 运算符重载

重载 * 运算符的目的是为了得到迭代器对象的_node指针所指向的数据;

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

-> 运算符重载

struct Date
{int _year = 2024;int _month = 1;int _day = 1;
};
int main()
{//链表中的数据成员为自定义类型Datelist<Date> lt;Date d1;lt.push_back(d1);//it为封装结点指针的迭代器类list<Date>::iterator it = lt.begin();//结点中的数据成员访问方式1: 结构体变量.结构体成员cout << (*it)._year << " " << (*it)._month << " " << (*it)._day << endl;cout << it.operator*()._year << " " << it.operator*()._month << " " << it.operator*()._day << endl;//结点中的数据成员访问方式2: 结构体指针->结构体成员//it->_year本应该为it->->_year,但由于->->可读性差,编译器优化为->;//第一个->去调用operator->重载函数返回Date*的指针,第二个->用来去访问自定义类型的成员变量;cout << it->_year << " " << it->_month << " " << it->_day << endl;cout << it.operator->()->_year << " " << it.operator->()->_month <<" " << it.operator->()->_day<< endl;
}

当迭代器内部重载了operator->()函数,且该函数返回结点中的数据成员的地址,便可以使用->访问自定义类型数据中的成员变量;

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

普通迭代器总体实现代码

template<class T>
struct __List_iterator
{typedef ListNode<T> Node;Node* _node;__List_iterator(Node* node):_node(node){}typedef __List_iterator<T> self;//++itself& operator++(){_node = _node->_next;return *this;}//it++self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}//--itself& operator--(){_node = _node->_prev;return *this;}//it--self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node == s._node;}bool operator!=(const self& s){return _node != s._node;}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}
};

无论是普通迭代器还是const迭代器,均需要迭代器遍历容器中的内容,因此迭代器本身可以被修改,区别仅在于迭代器指向的内容是否可以被修改,那么该如何实现const迭代器类呢?

由于const迭代器本质为保护迭代器指向的内容不允许被修改,若实现const迭代器类,只需要普通迭代器的operator*()与operator->()两个接口的返回值采用const修饰,便保护容器中的内容不会被修改其余接口均保持不变;

template<class T>
struct __List_const_iterator
{typedef ListNode<T> Node;Node* _node;__List_const_iterator(Node* node):_node(node){}typedef __List_const_iterator<T> self;self& operator++();self operator++(int);self& operator--();self operator--(int);bool operator==(const self& s);bool operator!=(const self& s);const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}
};

上述实现方案,在同一份文件中存在普通迭代器类与const迭代器类,两者之间仅有两个接口的返回值不同,如此便造成了代码的冗余,导致可读性变差,那么该如何改进呢?

迭代器类增加两个模版参数,使用时便可实例化出普通迭代器与const迭代器;

//迭代器实现最终总体代码,只给出函数声明与普通迭代器代码相同
template<class T,class Ref,class Ptr>
struct __List_iterator
{typedef ListNode<T> Node;Node* _node;__List_iterator(Node* node):_node(node){}typedef __List_iterator<T> self;self& operator++();self operator++(int);self& operator--();self operator--(int);bool operator==(const self& s);bool operator!=(const self& s);Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}
};

当在list类中定义两个迭代器类,普通迭代器类,const迭代器类(Ref为 T&/const T& 类型,Ptr为 T*/const T* 类型)

typedef __List_iterator<T,T&,T*> iterator;
typedef __List_iterator<T,const T&,const T*> const_iterator;

当使用普通迭代器对象时,实例化出普通迭代器类(iterator),使用const迭代器对象时,实例化出const迭代器类(const_iterator) ;

list类的实现

list类的成员变量

list类的成员变量只需要一个头结点,便可通过迭代器访问其他节点元素;

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;//重命名const迭代器
private:Node* _head;
};

构造函数

list()
{_head = new Node; // 申请一个头结点_head->_next = _head; // 后继指针指向自己_head->_prev = _head; // 前驱指针指向自己
}

迭代器

begin() : 构造出指针指向第一个结点的迭代器对象;
end() :    构造出指针指向头结点的迭代器对象;

iterator begin()
{//return iterator(_head->_next);//单参数的构造函数支持类型转换__List_iterator(Node* node)//支持Node* 转换为 迭代器对象return _head->_next;
}iterator end()
{return _head;
}const_iterator begin() const
{return _head->_next;
}const_iterator end() const
{return _head;
}

insert()

  1. 新开辟一个结点newnode(值为val),得到当前结点的指针,前驱结点的指针;
  2. 前驱结点的_next 指向 newnode,newnode的_prev指向前驱结点;
  3. newnode的_next 指向当前结点,当前结点的_prev指向newnode;
iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;//当前结点指针Node* prev = cur->_prev;//前驱结点指针Node* newnode = new Node(x);//新开辟结点prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;//return iterator(newnode);return newnode;
}

erase()

  1. 得到前驱结点和后继结点的指针;
  2. 前驱结点的_next 指向后继结点;
  3. 后继结点的_prev指向前驱结点;
  4. 删除当前结点,返回删除位置的下一个位置;
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;return next;//返回删除位置的下一位置
}

push_front/push_back/pop_front/pop_back

push_back :  尾插即在头结点前插入一个结点;
pop_back   :  尾删,删除最后一个结点;
push_front  :  头插即在第一个结点前(非头结点)插入一个结点;
pop_front   :   头删,删除第一个结点(非头结点);

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());
}

front/back

front() : 返回第一个结点数据的引用;
end()  :  返回最后一个结点数据的引用;

T& front()
{  //*begin-->T& operator*()return *begin();
}
const T& front()const
{return *begin();
}
T& back()
{return *(--end());
}
const T& back()const
{return *(--end());
}

clear()

双向循环链表只保留头结点,遍历链表时调用erase接口进行删除,注意调用erase后迭代器it已经失效,使用返回值接收,自动指向下一结点;

void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}

empty()

//判断容器是否非空
bool empty()const
{return begin() == end();
}

swap()

//交换容器的头指针
void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}

拷贝构造函数

  1. 申请一个头结点,构造空双向循环链表(新容器);
  2. 将 lt 中的数据拷贝到新构造的容器中;
list(const list<T>& lt)
{_head = new Node; // 申请一个头结点_head->_next = _head; // 后继指针指向自己_head->_prev = _head; // 前驱指针指向自己for (const auto& e : lt) // 拷贝到新构造的容器中{push_back(e);}
}

赋值运算符重载

传统写法:

  1. 释放除了头结点以外的所有结点;
  2. 将 lt 中的数据拷贝到新构造的容器中;
list<T>& operator=(const list<T>& lt)
{// 防止自己给自己赋值if (this != &lt){clear(); // 清空数据for (const auto& e : lt) // 拷贝到新构造的容器中{push_back(e);}}return *this; // 支持连续赋值
}

现代写法:

  1. 拷贝构造出 lt 对象
  2. 交换 this 和 lt 的 _head 指针,出了作用域,lt 调用析构函数,释放掉原this的结点
list<T>& operator=(list<T> lt) //拷贝构造lt对象
{std::swap(_head, lt._head); //交换指针return *this; //支持连续赋值
}

析构函数

  1. 使用 clear() 释放除了头结点以外的结点;
  2. 释放掉头结点;
~list()
{clear();delete _head;_head = nullptr;
}

欢迎大家批评指正,博主会持续输出优质内容,谢谢大家观看,码字不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

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

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

相关文章

【SQL】1527. 患某种疾病的患者(like;通配符)

前述 知识点回顾&#xff1a; MySQL 使用OR在LIKE查询中比较多个字段 %&#xff1a;表示任意字符&#xff08;包括0个或多个&#xff09;_&#xff1a;表示任意单个字符匹配空格&#xff1a;直接用空格就行&#xff0c;例如&#xff0c;% DIAB1%可以匹配字符串ACNE DIAB100 …

Python Flask框架 -- ORM模型与表的映射

ORM模型 对象关系映射(Object Relationship Mapping)&#xff0c;简称ORM&#xff0c;是一种可以用Python面向对象的方式来操作关系型数据库的技术&#xff0c;具有可以映射到数据库表能力的Python类我们称之为ORM模型。一个ORM模型与数据库中一个表相对应&#xff0c;ORM模型中…

【国家计算机二级考试C语言.2024】学习备忘录

说明 分值 4060100 40分&#xff1a; 这里面有一大堆程序结果选这题&#xff0c;如果手速还可以。那遇到有疑问的情况就自己去倒计算器的ad E上面去打一打。能够跑出来&#xff0c;结果那是100%的没问题。 有些概念题比较讨厌&#xff0c;只能自己去记忆了。要去背诵熟熟的。…

GPU 使用率监测及可视化

1. 使用 nvidia-smi可视化 直接在终端输入nvidia-smi动态查看GPU的使用情况watch -n 0.5 nvidia-smi其中0.5表示每隔0.5秒更新一次,时间可以调整 2. 使用nvitop可视化 2.1 nvitop的使用 (1) 安装 pip install nvitop(2) 查看GPU使用率 nvitop

springboot296基于个性化定制的智慧校园管理系统设计与开发

智慧校园管理系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统校园综合信息管理难度大&…

maya打开bvh脚本

目录 maya打开脚本编辑器 运行打开bvh脚本 maya导出bvh脚本 maya打开脚本编辑器 打开Maya软件&#xff0c;点击右下角 “脚本编辑器” 运行打开bvh脚本 https://github.com/jhoolmans/mayaImporterBVH/blob/master/bvh_importer.py import os import re from typing impo…

训练不了AI,怎么办?

即使直接训练人工智能&#xff08;AI&#xff09;模型对许多人来说可能是一个技术上和资源上的挑战&#xff0c;仍然有多种方式可以参与、利用和推动AI技术的发展&#xff0c;而不必直接参与到模型的训练过程中。以下是一些可以考虑的途径&#xff1a; 1. 使用现有的AI服务和工…

excel处理_多个excel文件合并

data文件夹内&#xff0c;有多个xls文件。每个xls文件格式一致&#xff0c; 表头占两行&#xff0c;表位汇总数据占一行。 表头两行&#xff0c;拼接前第二行设置为表头&#xff0c;且删除第二行。 在python读入的dataframe中&#xff0c;成本表是表头&#xff0c;第一行是线路…

40 openlayers setCenter 之后 绘制了Overlay 地图定位异常

前言 这是之前在 生产环境碰到的一个问题 这个其实就是 业务上一个地图点击点位展示详情, 然后再点击另外一个点位 展示详情, 切换中心店的这个过程 其主要的问题是 使用 openlayers 的 Map.View.setCenter() 了之后, 整个地图的中心点切换到了一个莫名其妙的地方 然后 经…

MySQL多表联查会重复查找记录

在做尚上优选项目时&#xff0c;根据商品id查询商品参加的活动信息。需要根据skuid&#xff08;商品id&#xff09;对商品信息表、活动表、活动规则表进行多表联查。 但是发现&#xff0c;查询出来的数据会重复&#xff0c;如下图所示&#xff1a; 后把sql语句放在navicat中进…

web集群-lvs-DR模式基本配置

目录 环境&#xff1a; 一、配置RS 1、安装常见软件 2、配置web服务 3、添加vip 4、arp抑制 二、配置LVS 1、添加vip 2、安装配置工具 3、配置DR 三、测试 四、脚本方式配置 1、LVS-DR 2、LVS-RS 环境&#xff1a; master lvs 192.168.80.161 no…

opencv函数使用查找

opencv官方文档地址&#xff1a;https://docs.opencv.org/4.x/index.html 先选对应的版本opencv-python 以这个函数为例子 model cv2.face.LBPHFaceRecognizer.create() 点开后找face类的LBP里面就有create函数的用法

什么是智能物联网关?有哪些作用?

随着物联网技术的不断发展和普及&#xff0c;智能物联网关已经成为连接物理世界与数字世界的桥梁&#xff0c;成为实现万物互联的重要枢纽。那么&#xff0c;什么是智能物联网关&#xff1f;它又有哪些价值呢&#xff1f;今天&#xff0c;就让我们一起走进HiWoo Box的世界&…

5、双亲委派机制

双亲委派机制指的是&#xff1a;当一个类加载器接收到加载类的任务时&#xff0c;会自底向上查找是否加载过&#xff0c; 再由顶向下进行加载。 详细流程&#xff1a; 每个类加载器都有一个父类加载器。父类加载器的关系如下&#xff0c;启动类加载器没有父类加载器&#xff1…

Python将字符串转换为datetime

有这样一些字符串&#xff1a; 1710903685 20240320110125 2024-03-20 11:01:25 要转换成Python的datetime 代码如下&#xff1a; import functools import re from datetime import datetime, timedelta from typing import Union# pip install python-dateutil from date…

鸿蒙一次开发,多端部署(十五)常见问题

如何查询设备类型 设备类型分为default&#xff08;默认设备&#xff09;、tablet、tv、wearable、2in1等&#xff0c;有多种查询设备类型的方式。 通过命令行的方式查询设备类型。 通过命令行查询指定系统参数&#xff08;const.product.devicetype&#xff09;进而确定设备…

Linux(Centos)安装mysql 8 并且使用systemctl管理服务

1.下载mysql包 地址 MySQL :: Download MySQL Community Server (Archived Versions) 注&#xff1a;下载我圈住的减压之后里面会有tar.gz 再次减压才会是软件主体 2.安装和准备 yum -y install numactl 安装numactl tar -xvf mysql-8.0.30-el7-x86_64.tar 拆分 …

逆向爬虫技术的进阶应用与实战技巧

前言 在互联网的海洋中&#xff0c;数据是无价的财富。爬虫技术作为获取这些数据的重要手段&#xff0c;一直备受关注。然而&#xff0c;随着网站反爬虫机制的日益完善&#xff0c;简单的爬虫程序已经很难满足我们的需求。因此&#xff0c;掌握爬虫逆向技术&#xff0c;突破反爬…

【计算机网络篇】数据链路层(3)差错检测

文章目录 &#x1f95a;误码&#x1f354;两种常见的检错技术⭐奇偶校验⭐循环冗余校验&#x1f388;例子 &#x1f95a;误码 误码首先介绍误码的相关概念 &#x1f354;两种常见的检错技术 ⭐奇偶校验 奇校验是在待发送的数据后面添加1个校验位&#xff0c;使得添加该校验…

NVIDIA最新 Blackwell架构简介

NVIDIA Blackwell架构简介 在AI和大型语言模型&#xff08;LLMs&#xff09;迅速发展的领域中&#xff0c;追求实时性能和可扩展性至关重要。从医疗保健到汽车行业&#xff0c;组织正深入探索生成性AI和加速计算解决方案的领域。对生成性AI解决方案的需求激增&#xff0c;促使企…