learn C++ NO.13——list

前言

本文将从list的使用,再到根据sgi库对于list实现作为参考模拟实现一下list。通过模拟实现来增加对它的理解。

介绍list

list是一个由带头双向循环链表实现的STL容器,它提供常规时间内对数据进行插入和删除操作。
list在内存中存储不连续的空间存储,这样避免了连续存储的扩容问题。
list支持双向迭代器,即支持从前往后遍历容器和从后往前遍历容器。

list的使用

下面写一份简单的代码介绍一下list的基本接口。
在这里插入图片描述

在insert接口和erase接口的使用中,list相较于vector来说用起来有些区别。
vector调用insert()接口要在第二个元素位置之后,可以直接使用迭代器+2作为参数传递。而list不可以。list想要做到在第二个元素之后插入的话,需要先将迭代器移动到指定位置后,在插入数据。

int main()
{vector<int> v(4,0);list<int> lt(4,0);// 在第二个元素后插入一个值v.insert(v.begin() + 2, 10);auto ite = lt.begin();int n = 2while(n--){++ite;}lt.insert(ite, 10);return 0;
}

为什么list不像vector一样能直接将迭代器+2传参呢?我们知道vector存储在一段连续的空间中,而list不一定是存储在一段连续的空间中。从技术实现的角度来说,list使用迭代器+2找到第二个元素后的位置是肯定可以实现的。至于为什么不这么做呢?我个人看来,可能是标准委员会的大佬们觉得list头尾插入删除效率极佳,如果支持的话有些使用者不了解底层的实现原理,势必不了解list效率上的不足,这样一来可以引导程序员在大量数据删除、插入操作在容器中间位置时,去用vector存储数据以便操作。

既然谈到了vector和list存储特点上有区别,下面引申出一些关于迭代器为什么不实现了operator < 、operator >等,而是统一用operator!=或operator==做条件判断的操作符。因为list、map、set等容器都不是存储在一段连续的空间上,所以,并不意味着第一个元素的地址就在第二个元素的地址前面。迭代器这样实现上为了兼容非线性存储结构的容器也能正常的使用迭代器进行操作。

list迭代器失效问题

vector的insert()和erase()后都会导致迭代器失效问题,因为vector的insert()涉及到扩容操作会导致迭代器失效,以及erase接口删除特定位置元素后导致的迭代器失效问题。那么list会存在迭代器失效问题吗?答案是会。但是,insert接口不会涉及迭代器失效问题。list底层实现不涉及扩容操作,所也就不存在迭代器失效了。
在这里插入图片描述

而erase接口依旧会导致迭代器失效问题。
在这里插入图片描述

简介迭代器

STL库对于STL容器的迭代器实现分为三类,一种是单向迭代器(forward iterator),单链表(forward_list)、哈希表(unordered_map/unordered_set)使用的就是单向迭代器,单项迭代器只能从前往后进行迭代,所以只提供了operator++重载。另一种是双向迭代器(bidirectional iterator ),双向链表(list)、红黑树系列(map/set)所使用的是双向迭代器。双向迭代器提供了operator++、operator–。还有一种是随机迭代器(random access iterator )。vector、deque、string都是使用的随机迭代器。相较于双向迭代器,随机迭代器支持了对于迭代器的operator+、operator-的重载和operator[]的重载。

聊一聊list提供的操作接口

由于list并不支持随机迭代器,所以算法库中的sort是无法调用的,因为底层使用快速排序实现的,必须使用随机迭代器才能调用。而list库中提供的sort底层是用归并排序实现的。list的sort相比于算法库里的sort其实效率是拉胯的。
在百万级以上的数据排序下,list的sort甚至不如将list的数据拷贝到vector排序,再拷贝回list。下面做一组对照实验带大家看看。我们的两组对照组,排序数据是1000w个随机值,一组我们使用list库的sort,与另一个测试组,现将数据拷贝到vector后,再调算法库的sort,最后再拷贝回list。通过比对两者时间差距便知。

在这里插入图片描述

可以看到在1000w个数据的量级下,list排序和拷贝到vector排序后再拷回来的时间差距是一个接近二倍的差距。

list的sort,在数据量小的情况下其实使用起来问题不大。提供sort接口更多是为了可以配合以下的接口进行使用。merge()函数,用于归并两个有序地链表。unique函数,用于有序链表的去重操作。

归并两个有序链表的操作

在这里插入图片描述

对有序链表去重
在这里插入图片描述
reverse()逆置链表操作
在这里插入图片描述

remove()删除指定值的元素

在这里插入图片描述

splice()转移链表到指定位置。
在这里插入图片描述

模拟实现list

首先我们搭一个最简单的框架,然后再慢慢完善它。
在这里插入图片描述

首先是先把类给定义出来,这里不仅需要定义list类,还需要定义一个描述节点的类。
在这里插入图片描述
在这里插入图片描述
push_back()接口实现思路这里简单说一下,我们先定义一个局部变量tail存一下原链表的尾,以方便尾插。然后就是创建新节点。让新节点的_prev指向tail,让tail的_next指向新节点。让_head节点的_next结点指向新节点,最后让新节点的_prev指向_head即可。

模拟实现迭代器

由于list的迭代器需要重载operator++、operator–、operator!=、operator==以及operator* 等运算符重载。其中operator*是访问节点的数据而不是获取节点。我们需要用自定义类型来封装list的迭代器。这与string、vector等连续空间存储的容器不同,它们的迭代器可以是原生指针来定义的(vs用的是结构体定义自定义类型封装)。而list它是非连续空间存储。所以迭代器实现采用自定义类型封装。

template<class T>
struct __list_iterator
{typedef list_node<T> Node;//成员变量Node* _node;__list_iterator(Node* node):_node(node){}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_iterator<T>& operator++(int){__list_iterator<T> tmp(*this);_node = _node->_next;return tmp;}__list_iterator<T>& operator--(){_node = _node->_prev;return *this;}__list_iterator<T>& operator--(int){__list_iterator<T> tmp(*this);_node = _node->_prev;return tmp;}T& operator*(){return _node->_data;}bool operator!=(const __list_iterator<T>& it){return _node != it._node;}bool operator==(const __list_iterator<T>& it){return _node == it._node;}
};

这里我们就实现了一份简易的迭代器,下面我们简单测试一下。
在这里插入图片描述

那const迭代器要如何实现呢?直接定义吗?答案是不是的。list的const迭代器是为了保证指向的数据不被修改,但是迭代器本身还是要支持修改的。所以我们可以再上面的基础上把我们的operator*的参数修改一下即可。

template<class T>
struct __list_const_iterator
{typedef list_node<T> Node;//成员变量Node* _node;__list_const_iterator(Node* node):_node(node){}__list_const_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_const_iterator<T>& operator++(int){__list_const_iterator<T> tmp(*this);_node = _node->_next;return tmp;}__list_const_iterator<T>& operator--(){_node = _node->_prev;return *this;}__list_const_iterator<T>& operator--(int){__list_const_iterator tmp(*this);_node = _node->_prev;return tmp;}const T& operator*(){return _node->_data;}bool operator!=(const __list_const_iterator<T>& it){return _node != it._node;}bool operator==(const __list_const_iterator<T>& it){return _node == it._node;}
};

那这样设计是不是太冗余了,我们这时候可以参看一下SGI库大佬实现的思路了。多加两个模板参数,分别是Ref(用作operator*的返回值类型)以及Ptr(用作operator->的返回值类型)。这样我们就不用实现两份代码了,进需要控制模板的参数即可完成const迭代器和普通迭代器的实现了。

//typedef __list_iterator<T, T&, T*> iterator;
//typedef __list_iterator<T, const T&, const T*> const_iterator;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* node):_node(node){}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;}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}
};

补充聊一下编译器对于operator->调用时的特殊处理。
在这里插入图片描述
其实按照c语言的语法来看我们使用迭代器访问A类型的两个成员变量时,应该是it->->_a1。但是,由于这样写实在是太过于复杂,甚至还不如不实现这个operator->。于是c++委员会规定编译器可以对这个做一个特殊处理,省略一个->,以保证代码的可读性。

erase()和insert()的模拟实现

erase的实现思路如下,断言判断一下迭代器的有效性。定义两个变量保存前后的节点。修改前后节点的指向,然后释放节点,最后返回当前位置的下一个位置的迭代器即可。由于我们定义了成员变量来记录size的值,所以还需要注意修改。

在这里插入图片描述

向pop_back()、pop_front()这类接口复用erase即可。

insert()实现思路如下,定义两个变量保存前后节点,new一个新节点。修改前后节点的指向后,修改一下size的值,最后返回新节点。
在这里插入图片描述

clear()与析构函数的模拟实现

实现clear接口思路如下,遍历一遍链表依次erase即可,最后清空size的值即可。

析构函数实现思路如下只需要先调用clear()释放所有的有效节点,最后delete哨兵位头结点即可。
在这里插入图片描述

拷贝构造实现

我们需要new一个头结点并初始化它,随后我们可以依次将被拷贝对象尾插到头结点后。
在这里插入图片描述

operator=的模拟实现

使用现代写法实现,因为在operator=的形参是实参的临时拷贝,将它的内容交换给成员变量就可以达到赋值的效果。
在这里插入图片描述

总结

本文重点在于list的迭代器部分的实现,通过三个模板参数实现一份迭代器,不仅仅加深了我们对于泛型编程的理解,还加深了我们对于STL容器如何做到操作一致性的原理以及背后设计思路的理解。不得不感慨语言的发展真的是一个不亚于特破某个实体领域的技术壁垒。

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

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

相关文章

计算机组成原理(第二次笔记)

各种码 真值 (书写用)&#xff1a; 将用“”、“-” 表示正负的二进制数称为真值 机器不能识别书写格式&#xff0c;故用“0/1”表示“/-”符号。 机器码 (机器内部使用)&#xff1a; 将符号和数值一起编码表示的二进制数称为机器码。 常用机器码&#xff1a;原码、 反码、 补…

SpringCloud Alibaba之Nacos服务注册和配置中心

&#xff08;学习笔记&#xff09;nacos-server版本&#xff1a;2.2.3 总体介绍&#xff1a; 1、Nacos介绍 官网&#xff1a;Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网 Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字…

前端网页代码编辑器 Monaco Editor

前端网页代码编辑器 Monaco Editor Monaco Editor Monaco Editor 是由 Microsoft 开发的一款基于 Web 技术的开源代码编辑器&#xff0c;它是 Visual Studio Code 编辑器的核心。Monaco Editor 可以嵌入到网页中&#xff0c;提供类似于 Visual Studio Code 的编辑体验。 官方…

MySQL聚合统计

【数据库】MySQL聚合统计 王笃笃-CSDN博客https://blog.csdn.net/wangduduniubi?typeblog显示平均工资低于2000的部门和它的平均工资 mysql> select deptno,avg(sal) deptavg from emp group by deptno; --------------------- | deptno | deptavg | --------------…

信通院发布首个《大模型媒体生产与处理》标准,阿里云智能媒体服务作为业界首家“卓越级”通过

中国信通院近期正式发布《大模型驱动的媒体生产与处理》标准&#xff0c;阿里云智能媒体服务&#xff0c;以“首批首家”通过卓越级评估&#xff0c;并在9大模块50余项测评中表现为“满分”。 当下&#xff0c;AI大模型的快速发展带动了爆发式的海量AI运用&#xff0c;这其中&a…

职场女性的心灵救赎:数业智能心大陆照亮新曙光

近年来&#xff0c;职场女性抑郁问题愈发凸显。数业智能心大陆的AI心理疗愈平台数据显示&#xff0c;超八成职场女性曾感到抑郁。90 后职场女性的心理健康状况尤其令人担忧&#xff0c;随着年龄层的下降&#xff0c;职场女性中出现抑郁状态的比例呈现明显的上升趋势。 职场女性…

Jetpack Compose Side Effects in Details 副作用的详细信息

What is Side Effect’s? 副作用是什么&#xff1f; Side Effects is a change in the state of the application that occurs outside the scope of the composable function and is not related to the UI. In non-UI related state changes, our screen may recompose mor…

828华为云征文 | 使用华为云X实例部署图数据库Virtuoso并存储6500万条大数据的完整过程与性能测评

前言 在大数据时代&#xff0c;图数据库以其强大的关系处理能力在复杂网络、社交媒体分析、知识图谱等领域得到了广泛应用。而在云计算的蓬勃发展下&#xff0c;使用云服务器进行图数据库的部署与管理变得更加方便高效。本篇文章将详细介绍如何在华为云X实例上部署开源图数据…

中秋假期也能及时回应客户:微信聚合管理系统,自动回复

中秋佳节是阖家团圆的日子&#xff0c;很多人选择在此期间休息放松。但作为一名职场人士&#xff0c;如何在假期中不遗漏客户咨询&#xff1f; 不妨试试这个WeBot助手&#xff0c;你可以进行微信自动回复设置&#xff0c;轻松实现假期与工作两不误。 同一界面聚合多个账号 通…

HOT 100(七)栈、堆、贪心算法

一、栈 1、每日温度 使用单调递减栈来解决。主要思路是遍历temperatures数组&#xff0c;利用栈来存储还没有找到比当前温度高的天数的索引。当遇到比栈顶索引所对应温度更高的温度时&#xff0c;就可以确定当前这一天的温度比之前那一天高。索引的差值就是等待的天数。 求一…

使用c#制作一个小型桌面程序

封装dll 首先使用visual stdio 创建Dll新项目,然后属性管理器导入自己的工程属性表&#xff08;如果没有可以参考visual stdio 如何配置opencv等其他环境&#xff09; 创建完成后 系统会自动生成一些文件&#xff0c;其中 pch.cpp 先不要修改&#xff0c;pch.h中先导入自己需…

Unet改进31:添加Star_Block(2024最新改进方法)|紧凑的网络结构和高效的运算

本文内容:在不同位置添加Star_Block 目录 论文简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 论文简介 最近的研究引起了人们对网络设计中尚未开发的“星型操作”(元素智能乘法)潜力的关注。虽然有很多直观的解释,但其应用背后的基本原理在很大程度上仍未被探索。我们的研…

旅游网站设计与实现:SpringBoot技术手册

第三章 系统分析 开发一个系统首先要对系统进行分析&#xff0c;是开发者针对系统实际客户对软件应用的一个调查访问和研究&#xff0c;弄清用户对软件需求的具体要求&#xff0c;同时开发者还要对系统开发的经济和可技术上是否可行进行分析&#xff0c;并确定系统开发的成本和…

OZON电子产品大幅增长,OZON跨境PS5销量激增

Top1 存储卡 Карта памяти Canvas Select Plus 128 ГБ 商品id&#xff1a;1548303593 月销量&#xff1a;2131 欢迎各位卖家朋友点击这里&#xff1a; &#x1f449; D。DDqbt。COm/74rD 免费体验 随着智能手机和平板电脑的普及&#xff0c;用户对于存储空…

C++笔记---继承(上)

1. 继承的简单介绍 1.1 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许我们在保持原有类特性的基础上进行扩展&#xff0c;增加方法(成员函数)和属性(成员变量)&#xff0c;这样产生新的类&#xff0c;称派生类。 继承呈…

使用LDAP登录GitLab

使用LDAP登录GitLab gitlab.rb 配置如下 gitlab_rails[ldap_enabled] true #gitlab_rails[prevent_ldap_sign_in] false###! **remember to close this block with EOS below** gitlab_rails[ldap_servers] YAML.load <<-EOSmain:label: LDAPhost: 172.16.10.180port:…

python环境安装

一、下载开发IDE https://www.jetbrains.com/pycharm/download/?sectionwindows 下载:conda Download Now | Anaconda 重新打开PyCharm Community Edition 2024.2.1 新建项目&#xff1a;pythonProject1 编写python 文件时没有提示&#xff1a;错误:未选择 Python 解释器。请…

云轴科技ZStack 获鲲鹏应用创新大赛2024上海赛区决赛一等奖

9月13日&#xff0c;鲲鹏应用创新大赛2024上海赛区决赛成功举办。经评委专家从方案创新性、技术领先性、商业前景以及社会价值四个维度严格评审&#xff0c;云轴科技ZStack参赛作品《ZStack鲲鹏原生开发方案》荣获上海赛区企业赛——原生开发赛道&#xff08;互联网&#xff09…

线程 - 线程的由来、进程和线程的关系、进程创建_等待_退出详解

文章目录 一、线程概念1. 线程的出现2. linux 对线程的设计3. 线程二、进程和线程1. 进程和线程的关系2. 进程的调度3. 轻量级进程三、pthread库1. pthread 库的作用2. 手动链接 pthread库四、创建线程1. pthread_create()2. 函数的使用3. 线程和函数五、线程等待1. 新线程的运…

ROADM(可重构光分插复用器)-介绍

1. 引用 https://zhuanlan.zhihu.com/p/163369296 https://zhuanlan.zhihu.com/p/521352954 https://zhuanlan.zhihu.com/p/91103069 https://zhuanlan.zhihu.com/p/50610236 术语&#xff1a; 英文缩写描述灰光模块彩光模块CWDM&#xff1a;Coarse Wave-Length Division …