STL_list文档使用介绍与底层代码实现简介

在这里插入图片描述

文章目录

    • list介绍
    • list的使用
      • 构造函数(constructor)
      • 迭代器
      • list capacity
      • list modify(修改)
      • 其他接口函数
      • list迭代器失效问题
    • list实现
      • 基础框架(节点类)
      • 基础框架(迭代器类)
      • 基础框架(链表主体类)
      • 链表主体函数

二次修改于date:2024:3:20

list介绍

在STL中list底层使用的是双向带头循环链表,这是一个很完善的结构,可以做到在O(1)时间内完成pos位置的插入删除。
唯一的缺点是不支持随机访问,如果要访问list中的第三个元素,只能通过遍历迭代器或者范围for来访问第三个元素。
所以list不支持算法库里面的sort(因为算法库中的sort底层是快速排序,快速排序为了防止最坏的情况也就是已排序好的数据,在这种情况下的效率就是O(N^2)因此引入了三数取中,但是如果不支持随机访问,三数取中就不可以使用了)
要想对sort进行排序只能使用list自带的sort来进行排序,但是list也很少排序,如果是需要排序的数据一般是用顺序表来存储。其次list内置sort效率很低,数据量大的时候甚至不如先将数据拷贝到vector中排序完再拷贝回来。

这是list的双向带头循环链表的一个逻辑结构图
image.png

常见的迭代器类型
单向---->只能++,例如:forword_list和unorder_map
双向---->可++和–,例如:list
随机访问---->可以++/–/+/-/+=/-=等任意方式访问,有vector和string
在文档中根据迭代器名称可以判断出,当前函数支持什么类型的迭代器,这几个迭代器从上到下满足继承关系,单向的迭代器可以调用的函数双向的迭代器一定也可以,但是双向可以调用的函数单向的不一定能调用。

list的使用

构造函数(constructor)

image.png
C++这里提供了四种构造函数
1.不传参数构造空链表(也就是只有一个头结点)alloc是空间配置器,也就是内存池
2.使用n个val来构造链表
3.使用了一个迭代器区间来构造(左闭右开),可以是其他容器的迭代器来构造链表
4.拷贝构造

#include<iostream>
#include<list>
#include<string>

using namespace std;

template<class T>
void Print(T& tmp)
{
	typename T::iterator it = tmp.begin();
	while (it != tmp.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	list<int> l1;
	list<int> l2(10, 6);
	string s("hello kisskernel");
	list<char> l3(s.begin(), s.end());
	
	Print(l1);
	Print(l2);
	Print(l3);
	return 0;
}

这里实现了一个可以打印任意类型容器的模板打印函数,在访问模板内的内嵌类型的时候,如果不加typename就会报错,因为模板在没有实例化之前是不能取模板的内嵌类型的,这时候编译器根本就不认识T,也不知道T是什么类型,取内嵌的iterator就会报错。所以前面加上了typename就是告诉编译器这个参数是个模板类型,等实例化之后再去里面取内嵌类型。

迭代器

list的迭代器和其他容器一样的不多赘述。
虽然迭代器的使用类似指针的操作,在前面的vector和string中迭代器也确实就是指针,但是list迭代器如果单单是指针的话,不能满足list这里的迭代器要求,比如it++如果时原生指针就时地址++,并不能跳到下一个节点。
所以list的迭代器封装了节点的指针,然后通过重载运算符来完成迭代器的操作,包括后面的map和set也是类似的。

迭代器的价值:

  1. 封装底层实现,不暴露底层细节
  2. 统一访问方式,减少学习成本

算法中的函数模板理论上都是通用的,传什么迭代器都是可以的,但是因为算法中可能用到了不同的迭代器操作,有一些迭代器可能不支持这些操作,比如sort需随机访问,list的iterator是不支持的。

注意:begin的位置就是第一个节点的位置,end的位置是最后一个节点的下一个位置,也就是头结点(哨兵位)的位置。
结构如下:

image.png
begin是正向迭代器的开始,++向着逆时针移动。
rbegin是反向迭代器的开始,++向着顺时针方向移动
反向迭代器:关于反向迭代器,这里为了让end和rbegin保持一致,所以他们都是指向头结点的,但是rbegin解引用拿到的是最后一个节点4,这是因为反向迭代器的实现重载*的时候是返回的前一个位置的值。所以rend解引用拿到的就是头结点(这里会报错,这也证明了拿到的确实就是头节点)。

list capacity

image.png
1.判断链表是否为空(用迭代器begin和end判断是不是相等的就可以了,相等就是空)
2.返回链表的元素个数(可以遍历链表来计数,也可在成员中加一个_size来记录,插入就++,删除就–)

list modify(修改)

image.png
修改函数只需要了解框选出来的即可。
1.push_front头插
2.popfront头删
3.push_back尾插
4.pop_back尾删
5.insert随机插入(在pos位置之前插入)
image.png
返回值是新插入的元素的第一个位置(如果只插入一个元素,就返回这个插入的元素的位置)。
6.erase删除
image.png
返回的迭代器是最后一个被删除的位置的下一个位置的迭代器。如果已将容器最后一个元素删除那么返回的就是end();
7.swap交换
8.clear清空链表

其他接口函数

image.png
assign就是将对象重新赋值,第一个就是将容器内的数据清空然后将这个迭代器区间内的数据插入进去,第二个就是清空然后插入n个val

image.png
splice的意思就是拼接结合的意思。将一个容器的元素接合到另一个容器中。
1.将x容器内的元素全部接合到调用容器的position位置。
2.将x容器内的i之后的元素接合到pos位置。
3.将x容器[ first,last)的这个区间接合到pos位置。

list迭代器失效问题

list底层是双向带头循环链表,使用迭代器插入一个节点并不会导致迭代器失效,因为插入的节点不会导致其他节点的物理地址的变换。使用erase删除一个节点同样也不会导致迭代器失效,只会导致删除的这个节点失效。

list实现

需要了解list容器总共有几部分组成,list内成员,就是一个指针,指向双向带头循环链表的头节点。然后将节点封装成一个类。vector和string的迭代器都可以使用原生指针来实现,因为他们的存储空间是连续的,++ 或者-- 后就能找到附近的元素,但是list的存储空间是不连续的。

所以list的原生指针行为不能满足list对于迭代器的定义,就需要通过类来封装原生指针,重载运算符来实现迭代器功能。这里一共需要三个类,list,_list_node , _list_iterator。
正因为使用类封装迭代器重载了运算符,所以在使用的时候可以不需要关注容器底层到底是数组,链表或者是树形结构,都可以使用简单统一的方法来访问修改容器,这其实就是迭代器的意义。

基础框架(节点类)

	//封装节点类
	template<class T>
	struct _list_node
	{
		typedef _list_node<T> node;
		
		_list_node(const T& val = T())
			:_val(val)
			,_next(nullptr)
			,_prev(nullptr)
		{}

		T _val;
		_list_node* _next;
		_list_node* _prev;
	};

基础框架(迭代器类)

//封装迭代器类
	template<class T,class Ref, class Ptr>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T,Ref,Ptr> self;

		_list_iterator(node* pnode)
			:_pnode(pnode)
		{}

		Ref operator*()
		{
			return _pnode->_val;
		}

		Ptr operator->()
		{
			return &_pnode->_val;
		}
		//前置++
		self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}
		//后置++
		//临时对象不能返回引用
		self operator++(int)
		{
			self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}

		//前置--
		self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}

		//后置--
		self operator--(int)
		{
			self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}

		bool operator==(const self& it)
		{
			return _pnode == it._pnode;
		}
		bool operator!=(const self& it)
		{
			return _pnode != it._pnode;
		}

		self& operator+(int x)
		{
			while (x--)
				_pnode = _pnode->_next;
			return *this;
		}

		self& operator-(int x)
		{
			while (x--)
				_pnode = _pnode->_prev;
			return *this;
		}

		node* _pnode;
	};

基础框架(链表主体类)

	//list主体类
	template<class T>
	class list
	{
		typedef _list_node<T> node;
	public:
		//迭代器
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;
        //.................
    private:
		node* _head;
	};

迭代器类使用了三个模板参数,主要为了适配const对象而加上了两个对象。因为如果是const对象,调用operator*()的时候返回的必须是const T&,使得这个对象只能读不能写。如果像前面那样直接写成函数重载,这里就会有问题了,因为迭代器并不是const类型的所以只有返回值类型不相同的两个函数是不能构成函数重载的。

还有方法,就是再写一个const_iterator类,但是这个类除了除了operator*()和operator->这两个函数的返回值不同以外,其他的函数都是相同的,所以代码会有很多的冗余。

这就要使用模板,将这两个函数的返回值都写成模板参数,const和非const只需要在模板实例化的时候控制就可以了,实际上还是生成了两个类,但是这是编译器替我们写的,并不需要我们自己动手。

链表主体函数

		//迭代器
		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() const
		{
			return const_iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}
		//
		//构造函数
		list()
		{
			empty_initialize();
		}

		list(size_t n, const T& val = T())
			:_head(nullptr)
		{
			file_initialize(n, val);
		}

		list(int n, const T& val = T())
			:_head(nullptr)
		{
			file_initialize(n, val);
		}

		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_initialize();
			InputIterator it = first;
			while (it != last)
			{
				push_back(*it);
				++it;
			}
		}
		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
		}
		//copy construct
		list(const list<T>& lt)
			:_head(nullptr)
		{
			empty_initialize();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}
		list<T>& operator=(list<T> tmp)
		{
			swap(tmp);
			return *this;
		}
		//析构
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
			//cout << _head << endl;
		}
		
		//自定义类型交换
		void push_back(const T& x)
		{
			node* newnode = new node(x);
			node* tail = _head->_prev;

			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		iterator insert(iterator pos, const T& x)
		{
			node* newnode = new node(x);
			node* cur = pos._pnode;
			node* prev = (pos._pnode)->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			return pos;
		}

		iterator erase(iterator pos)
		{
			node* cur = pos._pnode;
			node* next = cur->_next;
			node* prev = cur->_prev;

			prev->_next = next;
			next->_prev = prev;
			
			delete cur;
			return next;
		}

		iterator pop_front()
		{
			return erase(_head->_next);
		}
		iterator pop_back()
		{
			return erase(_head->_prev);
		}

		void clear()
		{
			node* cur = _head->_next;
			while (cur != _head)
			{
				node* next = cur->_next;
				delete cur;
				cur = next;
			}
			_head->_next = _head;
			_head->_prev = _head;
		}
		//
		size_t size() const
		{
			size_t count = 0;
			node* cur = _head->_next;
			while (cur != _head)
			{
				count++;
				cur = cur->_next;
			}
			return count;
		}
		
		bool empty() const
		{
			return _head->_next == _head;
		}
		//
		T& front()
		{
			return _head->_next->_val;
		}

		T& back()
		{
			return _head->_prev->_val;
		}

		void empty_initialize()
		{
			_head = new node;
			_head->_prev = _head;
			_head->_next = _head;
		}
		void file_initialize(size_t n, const T& val)
		{
			empty_initialize();
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

在编写成员函数的时候要注意重复冗余的代码可以抽离出来实现成为单独的函数,使用的地方进行调用,逻辑相近的代码可以复用,比如写完insert和erase,头插头删尾插尾删都可以复用。这样不仅代码简洁,而且可维护性更好。

下面着重说一下迭代器重载的operator->的作用。

比如这里有一个类date

class date
{
public:
	date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	int _year;
	int _month;
	int _day;
};

如果list里面存放的是date类对象,要输出的时候就会遇到问题,想要只输出year,只能先解引用然后再使用点来访问date里面的数据。

void test5()
{
	xzj::list<date> ld;
	ld.push_back({ 1900,2,28 });
	ld.push_back({ 1901,3,29 });
	ld.push_back({ 1902,4,20 });
	ld.push_back({ 1903,5,20 });

	xzj::list<date>::iterator it = ld.begin();
	while (it != ld.end())
	{
		cout << (*it)._year << endl;
		++it;
	}
	
}

指针访问结构体里面的数据都是使用->那么在这里我们也可以使用->只需要重载->操作符

		Ptr operator->()
		{
			return &_pnode->_val;
		}

有了上面的重载我们就可以直接使用->来访问date对象内的数据了

void test5()
{
	xzj::list<date> ld;
	ld.push_back({ 1900,2,28 });
	ld.push_back({ 1901,3,29 });
	ld.push_back({ 1902,4,20 });
	ld.push_back({ 1903,5,20 });

	xzj::list<date>::iterator it = ld.begin();
	while (it != ld.end())
	{
		cout << it->_year << endl;
		++it;
	}
}

但是这里it->实际调用方式是:it . operator->(),这个函数返回了date*这个类型的地址,但是仅有一个地址是不可以的,我们是不是还需要一个->才能访问数据呢?所以一共需要两个->
理论上来讲是这样的,但是这里编译器做了优化,所以这里只需要一个箭头就可以访问了。

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

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

相关文章

提供数字免疫力:采取整体方法来优化您的网络

采用数字技术已成为许多美国企业的关键竞争优势&#xff0c;导致其在与新部署的云解决方案的安全连接方面的投资不断增加。然而&#xff0c;随着越来越多的关键应用程序迁移到云端&#xff0c;公司保护其敏感数据和资源变得更具挑战性&#xff0c;因为这些资产现在超出了内部防…

uniapp——第3篇:自定义组件、组件间传数据

前提&#xff0c;建议先学会前端几大基础&#xff1a;HTML、CSS、JS、Ajax&#xff0c;还有一定要会Vue!&#xff08;Vue2\Vue3&#xff09;都要会&#xff01;&#xff01;&#xff01;不然不好懂 一、组件是啥玩意&#xff1f; 我之前讲vue2的文章讲过 Vue全家桶:vue2vue3全…

Python通过Ctypes调用C++类,实测有效

文章目录 前言创建vs dll工程添加外部库编辑代码编译测试参考 前言 在软件开发中&#xff0c;有时候需要Python与C相结合&#xff0c;以充分发挥两者的优势 。Python作为一种高级编程语言&#xff0c;具有简洁易读的特点&#xff0c;适用于快速开发和原型设计。而C则是一种性能…

小程序绕过 sign 签名

之前看到了一篇文章【小程序绕过sign签名思路】之前在做小程序渗透时也遇到了这种情况&#xff0c;但是直接放弃测试了&#xff0c;发现这种思路后&#xff0c;又遇到了这种情况&#xff0c;记录下过程。 并没有漏洞分享&#xff0c;仅仅是把小程序也分享出来&#xff0c;方便…

上位机图像处理和嵌入式模块部署(qmacvisual轮廓查找)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;图像的处理流程一般都是这样的&#xff0c;即灰度化-》降噪-》边缘检测-》二值化-》开闭运算-》轮廓检测。虽然前面的几个…

AI开源概览及工具使用

一、前言 随着ChatGPT热度的攀升&#xff0c;越来越多的公司也相继推出了自己的AI大模型&#xff0c;如文心一言、通义千问等。各大应用也开始内置AI玩法&#xff0c;如抖音的AI特效&#xff1b; 关联资源&#xff1a;代码 GitHub、相关论文、项目Demo、产品文档、Grok Ai、gr…

多数据源 - dynamic-datasource | 进阶 - 动态添加、移除数据源

文章目录 实现原理示例程序🗯️ 上节回顾:前节中,了解了 dynamic-datasource 的事务支持。 👉 本节目标:了解 dynamic-datasource 的进阶用法 - 动态添加/移除数据源。 动态添加/移除数据源:指在系统运行过程中动态的添加数据源,删除数据源,多使用于基于数据库的多租…

探讨苹果 Vision Pro 的空间视频(术语辨析、关键技术、思考)

背景:一位资深视频技术从业者在 Pixvana 工作,积累了丰富的捕获、处理、编码、流传和播放空间媒体经验。 一、术语 空间视频:传统的 3D 视频,呈矩形,包含左右眼视图,如 iPhone15 Pro 和 Vision Pro 可录制。沉浸式视频:非矩形的环绕式视频体验,通常由两个或多个传感器…

【C++】仿函数优先级队列反向迭代器

目录 一、优先级队列 1、priority_queue 的介绍 2、priority_queue 的使用 3、 priority_queue 的模拟实现 1&#xff09;priority_queue()/priority_queue(first, last) 2&#xff09;push&#xff08;x&#xff09; 3&#xff09;pop&#xff08;&#xff09; 4&#…

Datawhale 零基础入门数据挖掘-Task1 赛题理解

一、 赛题理解 Tip:此部分为零基础入门数据挖掘的 Task1 赛题理解 部分&#xff0c;为大家入门数据挖掘比赛提供一个基本的赛题入门讲解&#xff0c;欢迎后续大家多多交流。 赛题&#xff1a;零基础入门数据挖掘 - 二手车交易价格预测 地址&#xff1a;零基础入门数据挖掘 -…

【Vue】三、使用ElementUI实现图片上传

目录 一、前端代码实现 二、后端代码实现 三、调试效果实现 一、前端代码实现 废话不多说直接上代码 <el-form-item prop"image" label"上传图片" v-model"form.image"><el-upload:action"http://localhost:8…

关于vuex 的模块开发和使用

1、文件结构 2、modules 文件内容 例子&#xff1a; ccc.js 文件内容如下&#xff1a; // 基础配置项 const state {aa: [] }const mutations {setaa (state, data) {state.aa data} }const actions {} export default {namespaced: true, state,mutations,actions } **注…

【Linux】进程通信

目录 一、管道通信 二、共享内存 三、消息队列 一、管道通信 管道是由操作系统维护的一个文件&#xff0c;管道通信的本质就是将管道文件作为临界资源&#xff0c;实现不同进程之间的数据读写&#xff0c;但是管道只允许父子进程或者兄弟进程之间的通信。 管道文件本身是全…

Redis面试题以及答案

1. 什么是Redis&#xff1f;它主要用来什么的&#xff1f; Redis&#xff0c;英文全称是Remote Dictionary Server&#xff08;远程字典服务&#xff09;&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并…

虹科Pico汽车示波器 | 免拆诊断案例 | 2019 款东风悦达起亚K2车怠速起停系统工作异常

一、故障现象 一辆2019款东风悦达起亚K2车&#xff0c;搭载G4FG发动机&#xff0c;累计行驶里程约为9 400 km。车主反映&#xff0c;行驶至路口停车等红灯时&#xff0c;怠速起停&#xff08;ISG&#xff09;系统自动使发动机熄火&#xff0c;接着组合仪表提示“怠速起停已解除…

HarmonyOS/OpenHarmony应用开发-DevEco Studio 在MAC上启动报错

报错截图 报错详细内容 ------------------------------------- Translated Report (Full Report Below) -------------------------------------Process: devecostudio [8640] Path: /Applications/DevEco-Studio.app/Contents/MacOS/devecos…

【pip 安装pymssql报错】 Failed to build pymssql

在使用pip install pymssql安装pymssql时报如下图的错误&#xff1b; 报错截图 2&#xff09;查找资料说pip<3.0版本 &#xff0c;我也试了&#xff0c;不行。 你们也可以试一试&#xff1a;pip install"pymssql<3.0" 3&#xff09;我的成功方式&#xff1…

选择word中的表格VBA

打开开发工具 选择Visual Basic插入代码 Sub 选择word中的表格() Dim t As Table an MsgBox("即将选择选区内所有表格&#xff0c;若无选区&#xff0c;则选择全文表格。", vbYesNo, "提示") If an - 6 Then Exit Sub Set rg IIf(Selection.Type wdSel…

【C++】线程库

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;C 前言 C11标准引入了线程库&#xff0c;通过其可以在C程序中方便地创建和管理线程。以下是一些常用的线程库组件&#xff1a; std::thread&#xff1a;std…

【Linux第三课-基础开发工具的使用】yum、vim、gcc/g++编译器、gdb、Make/Makefile编写、进度条程序、git命令行简单操作

目录 yum - 软件包管理器快速认识yum快速使用yumyum搜索yum安装yum卸载 yum的周边 - yum的整个生态问题 vim快速介绍vimvim的模式命令模式插入模式低行模式 常见模式 -- 命令、低行命令模式 -- 光标的移动命令模式 -- 复制粘贴、剪贴、删除命令模式 -- 小写/大写替换模式命令模…