【C++】学习STL中的list

news2025/1/22 7:56:43

❤️前言

        大家好!,今天为大家带来的一篇博客是关于STL中的list,内容主要包括list的介绍使用、list的模拟实现。以及list与vector的对比。

正文

list的介绍和使用

        首先,让我们看看list的文档介绍:

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

        官方网站list的文档介绍如下:

list - C++ Reference (cplusplus.com)icon-default.png?t=N7T8https://legacy.cplusplus.com/reference/list/list/?kw=list        list的简单使用在我们学会了vector的使用之后只要多看看list的文档并多加使用就可以很熟悉,因此在这里就不多讲了。

        需要提一提的是,由于存储在其中的元素的内存并不是连续的,list并不支持随机访问,也没有提供重载方括号。并且因此它并不适合算法库中的sort()和reverse(),于是在我们想要对list进行排序或者逆置的时候,应该使用list自带的接口,它们的名字与算法库中的一样。

        除此之外,使用list时也可能遇到迭代器失效的问题,这时候我们需要重新为迭代器赋值来解决这个问题。

list的模拟实现

        list的模拟实现分为三个部分,分别为:list的节点、list的迭代器、list本体。

list_node的模拟实现

        首先我们模拟实现list的节点,双向链表需要节点具有前后两个指针:

template<typename T>
struct list_node
{
    // 我们初学类模板时常常会忘记写<T>
    // 这里需要注意,模板名带上尖括号之后才会成为类型名
	list_node<T>* _prev;
	list_node<T>* _next;
	T _val;

	list_node<T>(const T& x = T())
		:_prev(nullptr)
		,_next(nullptr)
		,_val(x)
	{}
};

list迭代器的模拟实现

        然后我们要接着模拟实现list的迭代器,这里我们有一个很重要的设计,就是设置多个模板参数,对应数据类型,数据的引用类型,数据的指针类型。

        设计的思路:当我们设计迭代器类的时候,不仅要考虑普通的迭代器,还要考虑const迭代器,它并不是简单的在迭代器类声明对象前加上const关键字就可以。因为我们的const迭代器并不意味着我们不能改动这个迭代器对象本身,而是我们不能改动此迭代器所指向的数据。因此我们需要对迭代器类的一些接口的返回值类型做改动,也就是 * 号和 -> ,原因是这两个重载运算符会提供操作数据的方式。这种设计很好的减少了代码的冗余,使我们不用再另写一份const迭代器。

        除此之外,迭代器实现中比较独特的一点就是->运算符的重载,我们可以看到这里的运算符重载返回值为节点值的地址,那么我们获得了节点数据的地址之后继续要访问节点值的成员,应该要再使用一次->运算符,那么在具体应用中我们就需要使用两个箭头,也就是 iterator->->member ,但是这不符合我们使用->运算符的习惯,于是编译器就为我们简化了一下,只要使用一次箭头,编译器自己会正确识别这样的写法。

	template<typename T, typename Ref, typename Ptr>
	struct _list_iterator
	{
		typedef list_node<T> Node;
		typedef _list_iterator<T, Ref, Ptr> self;

		Node* _node;

		_list_iterator(Node* node)
			:_node(node)
		{}

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

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

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self operator++(int)
		{
			_node = _node->_next;
			return _node->_prev;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self operator--(int)
		{
			_node = _node->_prev;
			return _node->_next;
		}

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

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

               除了正向迭代器之外,我们还有一个反向迭代器,反向迭代器的使用规则与正向迭代器完全相反,如果我们直接重新定义一个反向迭代器的类模板,那么我们就需要为每一种迭代器都再写一份反向迭代器,这样代码会非常的冗余。于是这里就引出了一个新的概念——适配器模式。

        由于反向迭代器的规则与迭代器完全相反,反向迭代器的许多地方其实是可以复用对应正向迭代器本身的接口。那么我们就将迭代器类型作为一个模板参数传入,然后在反向迭代器内部创建一个正向迭代器,之后就可以直接对其进行相反的操作,这样我们就得到了一个可以复用的反向迭代器模板。代码如下:

template<class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
	typedef Reverse_iterator<Iterator, Ref, Ptr> self;

	Reverse_iterator(Iterator it)
		:_it(it)
	{}

	Ref operator*()
	{
		return *_it;
	}
		
	Ptr operator->()
	{
		return &_it->_node->_val;
	}

	self& operator++()
	{
		return --_it;
	}

	self operator++(int)
	{
		return _it--;
	}

	self& operator--()
	{
		return ++_it;
	}

	self operator--(int)
	{
		return _it++;
	}

	bool operator!=(const self& rit) const
	{
		return _it != rit._it;
	}
		
	bool operator==(const self& rit) const
	{
		return _it == rit._it;
	}

private:
	Iterator _it;
};

list本身的实现

        list本身的实现非常简单,我们之前在数据结构时期已经学过了链表的相关知识,写个双向循环链表应该是不在话下。简单代码如下:

template<typename 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;
	typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;

	reverse_iterator rbegin()
	{
		return (iterator)_head->_prev;
	}

	reverse_iterator rend()
	{
		return (iterator)_head;
	}

	iterator begin()
	{
		return _head->_next;
	}

	iterator end()
	{
		return _head;
	}

	const_iterator begin() const
	{
		return _head->_next;
	}

	const_iterator end() const
	{
		return _head;
	}

	list<T>()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}

	list<T>(const list<T>& lt)
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;

		for (auto& x : lt)
		{
			push_back(x);
		}
	}

	void swap(list<T>& lt)
	{
		std::swap(_head, lt._head);
		std::swap(_size, lt._size);
	}

	list<T>& operator=(list<T> lt)
	{
		swap(lt);

		return *this;
	}

    // 复用insert的代码
	void push_back(const T& x)
	{
		//Node* tail = _head->_prev;
		//Node* newnode = new Node(x);

		//newnode->_next = tail->_next;
		//newnode->_prev = tail;

		//tail->_next = newnode;
		//_head->_prev = newnode;
		//_size++;

		insert(end(), x);
	}

    // 复用erase的代码
	void pop_back()
	{
		//Node* tail = _head->_prev;
		//Node* nexttail = tail->_prev;

		//_head->_prev = nexttail;
		//nexttail->_next = _head;
		//delete tail;
		//_size--;

		erase(--end());
	}

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

	void pop_front()
	{
		erase(begin());
	}

	// pos之前位置插入
	iterator insert(iterator pos, const T& x)
	{
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* newnode = new Node(x);

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

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

		_size++;

		return newnode;
	}

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

		prev->_next = next;
		next->_prev = prev;
			
		delete cur;
		_size--;

		return next;
	}

	size_t size()
	{
		return _size;
	}

	void clear()
	{
		auto it = begin();
		while (it != end())
		{
			it = erase(it);
		}
	}

	~list<T>()
	{
		clear();

		delete _head;
		_head = nullptr;
	}

private:
	Node* _head;
	size_t _size = 0;
};

🍀结语

        ok,那么这篇拖了许久的博客也终于是写完了,临近开学,希望大家开学快乐呀!

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

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

相关文章

【C++深入浅出】类和对象上篇(类的基础、类的模型以及this指针)

目录 一. 前言 二. 面向对象与面向过程 2.1 面向过程 2.2 面向对象 三. 类的基础知识 3.1 类的引入 3.2 类的定义 3.3 成员变量的命名规则 3.4 封装 3.5 类的访问限定符 3.6 类的作用域 3.7 类的实例化 四. 类的对象模型 4.1 类对象的大小 4.2 类对象的存储方式 …

红队打靶:Narak打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现与端口扫描 第二步&#xff1a;Web渗透 第三步&#xff1a;tftp渗透 第四步&#xff1a;webdav利用 第五步&#xff1a;寻找敏感文件初步提权 第六步&#xff1a;motd利用提权 总结与思考 写在开头 本篇博客在自己的理解之上…

Windows下Redis的安装和配置

文章目录 一,Redis介绍二,Redis下载三,Redis安装-解压四,Redis配置五,Redis启动和关闭(通过terminal操作)六,Redis连接七,Redis使用 一,Redis介绍 远程字典服务,一个开源的,键值对形式的在线服务框架,值支持多数据结构,本文介绍windows下Redis的安装,配置相关,官网默认下载的是…

yo!这里是c++中的继承

目录 前言 概念定义 基类与派生类对象转换 作用域 派生类的默认成员函数 与友元&&与静态成员 菱形继承及菱形虚拟继承 多继承 菱形继承 虚拟继承 1.介绍 2.原理 继承总结 后记 前言 封装、继承、多态作为c的三大特性&#xff0c;在学完封装的有关内容之后…

OAuth2.0二 JWT以及Oauth2实现SSO

一 JWT 1.1 什么是JWT JSON Web Token&#xff08;JWT&#xff09;是一个开放的行业标准&#xff08;RFC 7519&#xff09;&#xff0c;它定义了一种简介的、自包含的协议格式&#xff0c;用于在通信双方传递json对象&#xff0c;传递的信息经过数字签名可以被验证和信任。JW…

python借助isinstance(item, (int, float))提取列表中的数字

如下一个列表[1,2,3,23, ,123] 借助isinstance(item, (int, float)) List [1,2,3,23, ,123] numbers [] # 遍历原始列表 for item in List:# 检查每个元素是否为数字&#xff08;整数或浮点数&#xff09;if isinstance(item, (int, float)):# 如果是数字&#xff0c;则添加…

Eclipse的安装(NEW~)

艾米&#xff0c;我擅长做很多事&#xff0c;但忘记你你并非其中一件。 随着IntelliJ IDEA在Java开发领域越来越广泛的被使用&#xff0c;Eclipse似乎快要退出舞台了。不过作为一款开源免费并拥有悠久历史的Java 开发IDE&#xff0c;总会有一批铁粉支持它&#xff0c;惦记着它。…

Ubuntu 20.04 LTS 安装Kubernetes 1.26

1、环境配置 (1)添加主机名称解析记录 cat > /etc/hosts << EOF 192.168.44.200 master01 master01.bypass.cn 192.168.44.201 node01 node01.bypass.cn 192.168.44.202 node02 node02.bypass.cn EOF(2)禁止K8s使用虚拟内存 swapoff -a sed -ri s(.*swap.*)#\1…

新手入门C语言安装IDE教程(以CLion,CodeBlocks,小熊猫)

前言 当时自己入门c语言时候老师让使用codeblocks&#xff0c;但是这玩意过于离谱了。 所以如果不是强求的话还是不建议codeblocks 个人推荐&#xff1a; 新手期刚学c语言: 可以先用用小熊猫c&#xff08;汉化版的devcpp&#xff09;然后下载个CLion&#xff08;Vscode你要是…

分享码云上8个宝藏又有价值的开源图片编辑器

如果你需要高效地处理图片&#xff0c;那么这8款实用工具是可以尝试的&#xff01; 它们能够进行一键抠图、放大、拼接、转矢量图、图标自动生成以及等操作&#xff0c;让你的工作效率飞升&#xff01; 在Gitee这个最有价值的开源项目计划是Gitee综合评定出的优秀开源项目的展示…

ModaHub魔搭社区——未来向量数据库会不像传统数据库那样,在国内涌现 200 多家出来?

I. 引言:数据库市场的持续扩张与向量数据库的崛起 随着技术的迭代速度越来越快,技术门槛也在逐渐降低,数据库市场的持续扩张是不可避免的。当前存在着大量的需求,这将吸引越来越多的数据库甚至向量数据库加入竞争。然而,从业界角度看,这种市场扩张是有利的。它可以促使更…

【人工智能】—_维度灾难、降维、主成分分析PCA、获取旧数据、非线性主成分分析

文章目录 高维数据与维度灾难维度灾难降维为什么需要降维&#xff1f;PRINCIPLE COMPONENT ANALYSIS主成分的几何图像最小化到直线距离的平方和举例主成分的代数推导优化问题计算主成分&#xff08;Principal Components, PCs&#xff09;的主要步骤获取旧数据的方法&#xff1…

计算一组数据中的低中位数即如果一组数据中有两个中位数则较小的那个为低中位数statistics.median_low()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 计算一组数据中的低中位数 即如果一组数据中有两个中位数 则较小的那个为低中位数 statistics.median_low() 选择题 以下程序的运行结果是? import statistics data_1[1,2,3,4,5] data_2[1,2,…

IIR滤波器

IIR滤波器原理 IIR的特点是&#xff1a;非线性相位、消耗资源少。 IIR滤波器的系统函数与差分方程如下所示&#xff1a; 由差分方程可知IIR滤波器存在反馈&#xff0c;因此在FPGA设计时要考虑到有限字长效应带来的影响。差分方程中包括两个部分&#xff1a;输入信号x(n)的M节…

LLM学习笔记(1)

学习链接 ChatGPT Prompt Engineering for Developers - DeepLearning.AI 一、prompt engineering for developer 1、原则 prompting principles and iterative pattern 2、用于summarize 环境与helper functions import openai import osfrom dotenv import load_dotenv…

C语言深入理解指针(非常详细)(二)

目录 指针运算指针-整数指针-指针指针的关系运算 野指针野指针成因指针未初始化指针越界访问指针指向的空间释放 如何规避野指针指针初始化注意指针越界指针不使用时就用NULL避免返回局部变量的地址 assert断言指针的使用和传址调用传址调用例子&#xff08;strlen函数的实现&a…

MySQL 8.0.34安装教程

一、下载MySQL 1.官网下载 MySQL官网下载地址&#xff1a; MySQL :: MySQL Downloads &#xff0c;选择下载社区版&#xff08;平时项目开发足够了&#xff09; 2.点击下载MySQL Installer for Windows 3.选择版本8.0.34&#xff0c;并根据自己需求&#xff0c;选择下载全社区安…

2023开学礼新疆石河子大学图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工

2023开学礼新疆石河子大学图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工

监控服务器与Zabbix服务器时间同步配置

一、简介 zabbix server数据采集对时间同步的要求比较高&#xff0c;因为被监控的主机时间快了&#xff0c;会导致数据读取失败等问题&#xff0c;时间慢了&#xff0c;会有一堆的因为数据写入延时产生的误告警&#xff0c;会发生告警恢复时间比告警产生时间早的情况。此案列以…

CSS中如何实现文字渐变色效果(Text Gradient Color)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 文字渐变色效果&#xff08;Text Gradient Color&#xff09;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这…