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.hqwc.cn/news/56830.html

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

相关文章

数据结构 | 利用二叉堆实现优先级队列

目录 一、二叉堆的操作 二、二叉堆的实现 2.1 结构属性 2.2 堆的有序性 2.3 堆操作 队列有一个重要的变体&#xff0c;叫作优先级队列。和队列一样&#xff0c;优先级队列从头部移除元素&#xff0c;不过元素的逻辑顺序是由优先级决定的。优先级最高的元素在最前&#xff…

rk3399移植linux kernel

rk3399移植linux kernel 0.前言一、移植ubuntu根文件系统二、移植linux1.支持NFS(可选)2.配置uevent helper3.支持etx4文件系统(默认已支持)4.配置DRM驱动5.有线网卡驱动6.无线网卡驱动 三、设备树四、内核镜像文件制作五、烧录六、总结 参考文章&#xff1a; 1.RK3399移植u-bo…

nginx编译以及通过自定义生成证书配置https

1. 环境准备 1.1 软件安装 nginx安装编译安装以及配置https&#xff0c;需要gcc-c pcre-devel openssl openssl-devel软件。因此需要先安装相关软件。 yum -y install gcc-c pcre-devel openssl openssl-devel wgetopenssl/openssl-devel&#xff1a;主要用于nginx编译的htt…

程序员编写文档的 10 个技巧

编写好的文档在软件开发领域具有重大意义。文档是概述特定问题陈述、方法、功能、工作流程、架构、挑战和开发过程的书面数据或指令。文档可以让你全面了解解决方案的功能、安装和配置。 文档不仅是为其他人编写的&#xff0c;也是为自己编写的。它让我们自己知道我们以前做过什…

水果店小程序开发

水果店小程序是一款集合了多种实用功能的应用程序&#xff0c;旨在为用户提供方便快捷的购买水果的方式。以下是该小程序的主要功能介绍&#xff1a; 1. 水果浏览&#xff1a;用户可以通过小程序浏览水果店的所有水果产品。每个水果都有详细的介绍&#xff0c;包括产地、口感、…

C++类的定义和对象的创建

一、问题引入 C类和对象到底是什么意思&#xff1f; 1、C 中的类&#xff08;Class&#xff09;可以看做C语言中结构体&#xff08;Struct&#xff09;的升级版。结构体是一种构造类型&#xff0c;可以包含若干成员变量&#xff0c;每个成员变量的类型可以不同&#xff1b; …

electron+vue3全家桶+vite项目搭建【25】使用electron-updater自动更新应用

文章目录 引入实现效果实现步骤引入依赖配置electron-buidler文件封装版本升级工具类主进程调用版本更新校验渲染进程封装方法调用 测试版本更新 引入 demo项目地址 electron-updater官网 我们不可能每次发布新的版本都让用户去手动下载安装最新的包&#xff0c;而是应用可以…

再次斩获第一,文心3.5霸榜国内大模型

目录 1 什么是文心一言&#xff1f;2 体验与文心一言对话3 文心3.5霸榜国内大模型 1 什么是文心一言&#xff1f; 文心一言是百度全新一代知识增强大语言模型&#xff0c;文心大模型家族的新成员&#xff0c;能够与人对话互动&#xff0c;回答问题&#xff0c;协助创作&#xf…

wxwidgets Ribbon使用wxRibbonToolBar实例

wxRibbonToolBar就是工具栏&#xff0c;一下是实现的效果&#xff0c;界面只是功能展示&#xff0c;没有美化 实现代码如下所示&#xff1a; MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(800, 600)) …

Python爬虫—破解JS加密的Cookie

前言 在进行网站数据爬取时&#xff0c;很多网站会使用JS加密来保护Cookie的安全性&#xff0c;而为了防止被网站反爬虫机制识别出来&#xff0c;我们通常需要使用代理IP来隐藏我们的真实IP地址。 本篇文章将介绍如何结合代理IP破解JS加密的Cookie&#xff0c;主要包括以下几个…

Acwing.876 快速幂求逆元

题目 给定n组ai ,pi&#xff0c;其中p;是质数,求α;模p;的乘法逆元&#xff0c;若逆元不存在则输出impossible。 输入格式 第一行包含整数n。 接下来n行&#xff0c;每行包含一个数组ai, pi&#xff0c;数据保证p;是质数。 输出格式 输出共n行&#xff0c;每组数据输出一…

谈谈DNS是什么?它的作用以及工作流程

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、DNS是什么&#xff1f; 二、DNS的作用 三、DNS查询流程 1、查看浏览器缓存 2、查看系统缓存 3、查看路由器缓存 4、查看ISP …