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 = 2
	while(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.mfbz.cn/a/874859.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【HarmonyOS NEXT】实现网络图片保存到手机相册

【问题描述】 给定一个网络图片的地址&#xff0c;实现将图片保存到手机相册 【API】 phAccessHelper.showAssetsCreationDialog【官方文档】 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-photoaccesshelper-V5#showassetscreationdialog…

ISO27001信息安全管理体系认证怎么做?

ISO27001认证是关于信息安全管理体系认证&#xff0c;ISO27001将有效保证企业在信息安全领域的可靠性&#xff0c;降低企业泄密风险&#xff0c;更好的保存核心数据。下面给大家详细讲讲ISO27001信息安全管理体系认证详细办理流程。 一、ISO27001信息安全管理体系认证的办理条…

【Centos】Centos系统换yum源

【Centos】Linux&#xff0c;Centos系统换yum源 1、备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak/etc/yum.repos.d/CentOS-Base.repo 是yum的配置文件 /etc/yum.repos.d/CentOS-Base.repo.bak 是我们备份的配置文件 2、下载yum源 这里…

【EI稳定,马来亚大学主办】2024年计算机与信息安全国际会议(WCCIS 2024,9月27-29)

2024年计算机与信息安全国际会议 (WCCIS 2024) 将于2024年9月27-29日召开。 会议旨在为从事计算机与信息安全的专家学者、工程技术人员、技术研发人员提供一个共享科研成果和前沿技术&#xff0c;了解学术发展趋势&#xff0c;拓宽研究思路&#xff0c;加强学术研究和探讨&…

【编译原理】编译器概述、编译器结构、编译器实例

编译器概述、编译器结构、编译器实例 编译器概述 1.编译器是一个程序 核心功能是把源代码翻译成目标代码 比如源代码&#xff1a;C/C&#xff0c;Java&#xff0c;C#&#xff0c;html 目标代码&#xff1a;X86&#xff0c;IA64,ARM,… 把一种源程序翻译成另外一种源程序&…

/bin/bash的作用

1、为啥使用不了很多命令&#xff1f; 我刚进入一个新系统&#xff1a; 我当时蒙蔽了&#xff0c;这是啥意思&#xff0c;为啥没命令? 原因是&#xff1a;当时进入的shell并没有初始化这些路径环境&#xff0c;所以正确的方法是&#xff1a; 2、/bin/bash运行的过程中执行…

Mac清理其他文件:释放存储空间的高效指南

每个Mac用户都可能遇到存储空间不足的问题&#xff0c;尤其是当“其他”文件积累到一定体积时。在Mac上&#xff0c;“其他”文件通常包括各种系统文件、缓存、文档以及不被归类为应用程序、照片、电影或音乐的其他类型的文件。这些文件往往不易被注意&#xff0c;但逐渐占用了…

C语言代码练习(第十八天)

今日练习&#xff1a; 48、猴子吃桃问题。猴子第1天摘下若干个桃子&#xff0c;当即吃了一半&#xff0c;还不过瘾&#xff0c;又多吃了一个。第2天早上又将剩下的桃子吃掉一半&#xff0c;又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时&…

TMGM:欧元区储蓄率处于结构性增高

法国的家庭储蓄率进一步上升&#xff0c;从2024年第一季度的家庭总可支配收入(GDI)的17.6%上升到2024年第二季度的17.9%&#xff0c;这信息来自于法国国家统计和经济研究所&#xff08;INSEE&#xff09;。这也是欧元区正在进行的上升趋势的早期迹象。虽然第二季度数字还没出来…

【C++】C++ STL探索:容器适配器 Stack 与 Queue 的使用及模拟实现

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现 本文将详细介绍如何使用容器适…

探索最佳 Shell 工具:全面测评 Bash、Zsh、Fish、Tcsh 和 Ksh

感谢浪浪云支持发布 浪浪云活动链接 &#xff1a;https://langlangy.cn/?i8afa52 文章目录 1. 简介2. 测评工具3. 测评标准4. Bash 测评4.1 易用性4.2 功能特性4.3 性能4.4 可定制性4.5 社区和支持 5. Zsh 测评5.1 易用性5.2 功能特性5.3 性能5.4 可定制性5.5 社区和支持 6. F…

3款数据恢复免费版软件评测:帮你轻松解决数据丢失问题

如今数字化时代&#xff0c;数据至关重要&#xff0c;珍贵照片、重要文档、成长记录的视频音频&#xff0c;承载回忆与努力。但数据丢失风险常伴&#xff0c;误删、格式化、病毒攻击等意外频发&#xff0c;令人陷入绝望&#xff0c;如坠数据黑洞。所幸科技发展&#xff0c;数据…

精益生产现场管理和改善的“蜕变”之旅:从理念到落地的全方位指南

精益生产现场管理和改善&#xff0c;正逐步成为众多企业转型升级的必经之路。今天&#xff0c;就让我们&#xff08;深圳天行健企业管理咨询公司&#xff09;带大家一起踏上这场从理念到落地的“蜕变”之旅&#xff0c;探索精益生产现场管理与改善的方方面面&#xff0c;为企业…

API安全 | 发现API的5个小tips

在安全测试目标时&#xff0c;最有趣的测试部分是它的 API。API 是动态的&#xff0c;它们比应用程序的其他部分更新得更频繁&#xff0c;并且负责许多后端繁重的工作。在现代应用程序中&#xff0c;我们通常会看到 REST API&#xff0c;但也会看到其他形式&#xff0c;例如 Gr…

游戏论坛网站|基于Springboot+vue的游戏论坛网站系统游戏分享网站(源码+数据库+文档)

游戏论坛|游戏论坛系统|游戏分享网站 目录 基于Springbootvue的游戏论坛网站系统游戏分享网站 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大…

JAVA同城服务系统大集结活动报名通道已开启构建你的同城圈子系统小程序源码

同城服务系统大集结&#xff0c;活动报名通道已开启&#xff01; &#x1f389; 【开篇号角】同城服务大狂欢&#xff0c;集结号已吹响&#xff01; 嘿&#xff0c;小伙伴们&#xff01;你们期待已久的同城服务系统大集结活动终于来啦&#xff01;&#x1f38a; 是的&#xff…

kubernetes微服务基础及类型

目录 1 什么是微服务 2 微服务的类型 3 ipvs模式 ipvs模式配置方式 4 微服务类型详解 4.1 ClusterIP 4.2 ClusterIP中的特殊模式headless 4.3 nodeport 4.4 metalLB配合loadbalance实现发布IP 1 什么是微服务 用控制器来完成集群的工作负载&#xff0c;那么应用如何暴漏出去&…

【卷起来】VUE3.0教程-08-路由管理

在Vue中&#xff0c;我们可以通过vue-router路由管理页面之间的关系。 Vue Router是Vue.js的官方路由&#xff0c;它与Vue.js核心深度集成&#xff0c;让用Vue.js构建单页应用变得轻而易举。 &#x1f332; 在Vue中引入路由 安装路由 npm install --save vue-router 建立三个…

详聊LLaMa技术细节:LLaMA大模型是如何炼成的?

本文介绍来自 Meta AI 的 LLaMa 模型&#xff0c;类似于 OPT&#xff0c;也是一种完全开源的大语言模型。LLaMa 的参数量级从 7B 到 65B 大小不等&#xff0c;是在数万亿个 token 上面训练得到。值得一提的是&#xff0c;LLaMa 虽然只使用公共的数据集&#xff0c;依然取得了强…

读论文-《基于计算机视觉的工业金属表面缺陷检测综述》

文章目录 1. 背景1.1 工业需求1.2 传统方法的局限1.3 计算机视觉技术的优势 2. 技术流程2.1 光学成像2.1.1照明方式2.1.2 缺陷和背景特性 2.2 图像预处理2.3 缺陷检测2.4 结果分析和决策 3. 关键算法3.1 光学成像技术相关算法3.2 图像预处理相关算法3.2.1 图像增强3.2.2特征提取…