C++入门之stl六大组件--List源码深度剖析及模拟实现

文章目录

前言

一、List源码阅读

二、List常用接口模拟实现

1.定义一个list节点

2.实现一个迭代器

2.2const迭代器

3.定义一个链表,以及实现链表的常用接口

三、List和Vector

总结


前言

本文中出现的模拟实现经过本地vs测试无误,文件已上传gitee,地址:list: 模仿实现stl的list - Gitee.com


一、List源码阅读

首先我们阅读源码,阅读源码我按照如下方式:先找到单个节点的定义,再找到list里面的主要成员函数。

list_node链表节点的定义,如下:有三个成员,prev,next指针,数据域。

实现链表的接口:增删改查,回想我们用C语言实现的顺序表的时候,使用指针去实现。使用C++的时候,模拟实现string,也是使用迭代器。迭代器就是模拟指针的行为,但是链表直接使用指针++,不一定能访问到下一个节点,所以我们要封装一下原生指针--迭代器,去实现对list的访问。

阅读源码,_list_iterator这个迭代器,有三个模板参数,猜测T应该是类型,Ref是引用,Ptr是指针,为什么有三个模板参数?后续再分析。这个迭代器里实现了一个原生迭代器,一个const迭代器,const调用const对象,然后还有一个self,暂时不明白什么意思。继续往下。

 阅读到这里,重定义了一些参数。注意这里:

  • typedef _list_node<T> * link_type;  
  • link_type node;
  • _list_iterator(link type x):node(x){}

和C语言一样,这里定义了一个节点的指针,作为实现迭代器的最小单元

继续往下阅读,这里有一些运算符重载的函数,比较节点是否相等,以及引用,注意这里引用的返回值使用了reference,上面看出来将Ref重定义成了reference,所以这里可以猜测,ref就是返回引用的值

 这里实现了运算符重载函数,使用迭代器,去访问前后的节点。注意这里的返回值是self,前面读到self就是_list_iterator的一个模板参数。对比之前实现日期类的时候,日期类++,返回的就是一个日期类对象。这里使用迭代器进行++,猜测这里返回的就是一个迭代器。

继续查看迭代器的使用:在list的成员函数中,查看到begin:指向头节点的下一个,end指向头节点。rbegin指向头节点,rend指向头节点的下一个。

判断链表是否为空:判断头节点的下一个是否指向头节点

计算链表的size:根据begin,end去计算

 以及链表中最重要的插入insert,通过迭代器找到pos的位置,根据T去构建一个Node,再进行插入。头插尾插都可以复用Insert。头插头删的时候先保留现在的指针指向情况,再进行修改,修改完了之后再删除这个节点

 往下阅读,到了list的主体部分,有两个模板参数。alloc暂时不明白是什么

 这里实现了一种创建节点的成员函数,create_node:通过T类型的参数x创建一个节点.

empty_initialize 空的初始化,只创建一个节点

二、List常用接口模拟实现

通过上面阅读源码,我们来模仿实现list的一些常用接口。

1.定义一个list节点

template<class T>
struct list_node
{T _data;list_node<T> * _next;list_node<T> * _prev;
}

2.实现一个迭代器

 迭代器要么就是原生指针,要么就是自定义类型对原生指针的封装,模拟指针的行为。

list用一个节点的指针,去构造一个迭代器

template<class T>
struct _list_iterator
{typedef list_node<T> node;typedef _list_iterator<T> self;//定义一个指针,指向链表node * _node;//构造函数 用一个节点指针去构造迭代器_list_iterator(node * n):_node(n){}//实现迭代器的功能//指针++向后遍历,就如日期类,返回的还是一个日期类对象;迭代器++,返回一个迭代器对象self& operator++(){_node = _node->_next;return *this;}self& operator++(int) //实现前置++{self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self& operator++(int) //实现前置++{self tmp(*this);_node = _node->_prev;return tmp;}T& operator*(){return _ndoe->data;}bool operator == (const self& s){return _node == s._node;}bool operator !=(const self &s){return _node != s._node;}

2.2const迭代器

假设我们传了一个const对象,

  • void print_list(const list<int>&lt)。
  • 对象的类型是一个const list<int> * ,调用不了普通迭代器,是一个经典的权限放大,所以我们要对this加一个const。此时变成了const* _head,指针本身不能改变,但是指向的内容可以改变。即我们还是可以对对象进行改变。
  • 在stl库中,它使用const修饰*this,返回值也是一个const_iterator。但是,对于所有的成员函数都使用const修饰*this和返回值类型很冗余,所以我们这里增加了一个模板参数class Ref。
	template<class T>struct __list_const_iterator{typedef list_node<T> node;typedef __list_const_iterator<T> self;node* _node;__list_const_iterator(node* n):_node(n){}//保护返回的值不能被修改const T& operator*(){return _node->_data;}//... 其他成员函数都相同};
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 * n):_node(n){}Ref operator*(){return _ndoe->data;}//注意这里 如果我定义了一个AA类型的链表,通过迭代器去访问,指针类型为AA*// 现在要访问它的成员 可以这样: *(it)._a1; //也可以it->->a1 一个是运算符重载调用,it是自定义类型,无法直接使用箭头,it-> 就相当于运算符重载operator-> AA*//一个是找成员 //这里为了增强可读性 省略了一个箭头 it->_a1;Ptr operator->(){return &_node->data;}self& operator--(){}//其余运算符操作类似
}template<class T>
class list
{//注意这里模板参数 调用普通迭代器 T&传给ref, 调用const迭代器 const T& 传给ref typedef _list_iterator<T,T&,T*> iterator;typedef _list_iterator<T,const T&, const T*> const_iterator;iterator begin(){return iterator(_head->_next);}const iterator begin(){return const_iterator(_head->_next);}//..其余类似
}

3.定义一个链表,以及实现链表的常用接口

template<class T>
struct _list
{typedef list_node<T> node;typedef _list_iterator<T> iterator;//list的成员node* _head;//list的构造 初始只有一个头节点list(){_head = new node;_head->_next = _head;_head->_prev = _head;}//list的一些成员函数 //通过迭代器定位begin和enditerator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}//通过迭代器对指定pos位置进行增删改查void  insert(iterator pos, const T& x){node new_node = new node(x);//iterator cur = pos;//这里是要通过pos的指针,找到这个节点 对pos进行解引用node * cur = pos._node;node * prev = cur->_prev;prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;}void push_back(){insert(end(),x);}void push_front(){insert(begin(),x);}void 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;}void pop_back(){erase(end());}void pop_front(){erase(begin());}//打印void print_list(const list<T> & lt){iterator it = lt.begin();while(it != lt.end()){cout<<*it;++it;}cout<<endl;}}

三、List和Vector对比

vector与list都是stl中非常重要的序列容器,由于两个容器的底层结构不同,导致特性以及应用场景不同

vectorlist
底层结构动态顺序表,一段连续的空间带头节点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除在任意位置插入删除元素效率较低,时间复杂度O(N),插入可能需要扩容,开辟新空间,拷贝元素,释放旧空间任意位置插入和删除效率高,不需要搬移元素。时间复杂度O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生指针对原生指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能导致扩容,导致原来迭代器失效,删除时,也可能失效。需要重新给迭代器赋值插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,因为那个节点已经被删除。其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

总结

本文主要对stl源码中list内容进行阅读,并模拟实现。技术有限,如有错误请指正。

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

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

相关文章

Word中如何断开表格中线段

Word中如何断开表格中线段_word表格断线怎么弄_仰望星空_LiDAR的博客-CSDN博客有时候为了美观&#xff0c;需要实现如下的效果&#xff0c;即第2条线段被断开成3段步骤如下&#xff1a;选中需要断开的格网&#xff0c;如下&#xff0c;再选择段落、针对下框标即可。_word表格断…

ROS激光雷达数据解析和获取

整体思路 catkin_create_pkg lidar_pkg roscpp rospy sensor_msgs lidar_node.cpp #include<ros/ros.h> #include<sensor_msgs/LaserScan.h>void LidarCallback(const sensor_msgs::LaserScan msg) {float fMidDist msg.ranges[180];//解析数据msg里面的数组是最…

web前端html

文章目录 快捷方式一、html5的声明二、html5基本骨架 2.1 html标签 2.2 head标签 2.3 body和head同级 2.4 body标签 2.5 title标签 2.6 meta标签 三、标题标签介绍与应用 3.1 标题的介绍 3.2 标题标签位置摆放 3.3 标签之段落、换行、水平线 3.3 标签之图片 3.3.1 图…

【Docker】DockerFile

目录 一、镜像原理 二、如何制作镜像 1、容器转镜像 2、DockerFile 三、DockerFile关键字​编辑 四、案例&#xff1a;部署SpringBoot项目 一、镜像原理 docker镜像是由一个特殊的文件系统叠加而成的&#xff0c;他的最低端是bootfs&#xff0c;并使用宿主机的bootfs&…

ruoyi若依 组织架构设计--[ 角色管理 ]

ruoyi若依 组织架构设计--[ 角色管理 ] 角色新增后端代码 角色修改后端代码 角色查询角色删除角色分配数据权限后端代码 角色分配用户 角色新增 后端代码 有一点&#xff0c;我认为新增的时候&#xff0c;也需要修改redis中的权限。 角色修改 后端代码 因为修改了role_menu表了…

Java8 list多属性去重

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;一个奋斗在互联网的打工人。 在 Java 开发中&#xff0c;我们经常会面临对 List 中的对象属性去重的需求。然而&#xff0c;当需要根据多个属性来进行去重时&#xff0c;情况会稍微复杂一些。本篇…

51单片机程序烧录教程

STC烧录步骤 &#xff08;1&#xff09;STC单片机烧录方式采用串口进行烧录程序&#xff0c;连接的方式如下图&#xff1a; &#xff08;2&#xff09;所以需要先确保USB转串口驱动是识别到&#xff0c;且驱动运行正常&#xff1b;是否可通过电脑的设备管理器查看驱动是否正常…

数据结构——红黑树

文章目录 一.红黑树的定义二.红黑树的插入1.红黑树节点的定义2.红黑树的插入操作3.总结&#xff1a; 三.红黑树与AVL树的比较四.检验手写的红黑树五.源码 一.红黑树的定义 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff…

Llama 2 with langchain项目详解(一)

Llama 2 with langchain项目详解(一) 2023年2月25日,美国Meta公司发布了Llama 1开源大模型。随后,于2023年7月18日,Meta公司发布了Llama 2开源大模型,该系列包括了70亿、130亿和700亿等不同参数规模的模型。相较于Llama 1,Llama 2的训练数据增加了40%,上下文长度提升至…

【ArcGIS Pro二次开发】(57):地图系列

在ArcGIS Pro中&#xff0c;有一个地图系列&#xff0c;可以在一个布局中导出多个地图。 在SDK中为ArcGIS.Desktop.layout.MapSeries类和映射系列导出选项&#xff0c;可以以支持多页导出。 MapSeries类提供了一个静态CreateSpatialMapSeries方法&#xff0c;该方法使用指定的…

SpringBoot核心内容梳理

1.SpringBoot是什么? Spring Boot是一个基于Spring框架的快速开发应用程序的工具。它简化了Spring应用程序的创建和开发过程&#xff0c;使开发人员能够更快速地创建独立的、生产就绪的Spring应用程序。它采用了“约定优于配置”的原则&#xff0c;尽可能地减少开发人员需要进…

【Linux命令详解 | mkdir命令】Linux系统中用于创建新目录的命令

文章标题 简介一&#xff0c; 参数列表2&#xff0c;使用介绍1. 基础用法2. 使用 -p 参数创建多级目录3. 使用 -m 参数设置目录权限4. 使用 -v 参数查看详细信息5. 多个目录的创建6. 创建带有特殊字符的目录7. 重复创建目录8. 创建只读目录 总结 简介 mkdir命令在Linux系统中被…

【C++】透过STL源码深度剖析及模拟实现vector

鉴于读者的响应&#xff0c;打算将文章拆分一下&#xff0c;方便观看&#xff0c;基本接口可看 深入浅出STL之vector类 一、源码引入 以下我所介绍的都是基于【SGI】版本的STL&#xff0c;对源码有兴趣的同学可以去看看 侯捷老师的《STL源码剖析》 然后呢我们就去调出【vector…

【数据结构OJ题】删除有序数组中的重复项

原题链接&#xff1a;https://leetcode.cn/problems/remove-duplicates-from-sorted-array/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 用双指针算法&#xff0c;定义两个变量src和dst&#xff0c;一开始让src和dst指向num[ ]数组的第一个元素&a…

微服务——DSL查询文档+搜索结果处理

DSL Query分类 DSL Query的基本语法 全文检索查询 常用场景 match查询 要填一个存在的字段&#xff0c;已经要检索的内容 匹配度越高排名越前&#xff0c;这里all字段包含三个字段在里面。 multi_match查询 精确查询 不分词的查询 查询语法 term查询 range查询 gte是大于等…

Kubernetes关于cpu资源分配的设计

kubernetes资源 在K8s中定义Pod中运行容器有两个维度的限制: 资源需求(Requests):即运行Pod的节点必须满足运行Pod的最基本需求才能运行Pod。如 Pod运行至少需要2G内存,1核CPU。(软限制)资源限额(Limits):即运行Pod期间,可能内存使用量会增加,那最多能使用多少内存,这…

如何在终端设置代理(设置jupyter notebook同理)

设置代理 在终端(我用的gitbash)下执行 set HTTP_PROXYhttp://<user>:<password><proxy server>:<proxy port> set HTTPS_PROXYhttp://<user>:<password><proxy server>:<proxy port>其中&#xff1a; user、password&#…

IO模型-信号驱动IO

linux内核中存在一个信号SIGIO&#xff0c;这个信号就是用于实现信号驱动IO的。当应用程序中想要以信号驱动IO的模型读写硬件数据时&#xff0c;首先注册一个SIGIO信号的信号处理函数,当硬件数据就绪&#xff0c;硬件会发起一个中断&#xff0c;在硬件的中断处理函数中向当前进…

Windows搭建Snort环境及使用方式

目录 0x01 前置环境0x02修改配置文件0x03 自测0x04 使用0x05 感言 0x01 前置环境 环境描述windows10snort2.9.2https://www.snort.org/downloads 先把上面环境下载好&#xff01; 需要注意的是安装npcap这个软件 0x02修改配置文件 软件安装目录&#xff1a;C:/Snort/ 配置文…

MySQL的常用函数大全

一、字符串函数 常用函数&#xff1a; 函数功能CONCAT(s1, s2, …, sn)字符串拼接&#xff0c;将s1, s2, …, sn拼接成一个字符串LOWER(str)将字符串全部转为小写UPPER(str)将字符串全部转为大写LPAD(str, n, pad)左填充&#xff0c;用字符串pad对str的左边进行填充&#xff0…