STL库 —— list 的编写

news2025/6/27 0:10:14

目录

一、成员变量

​编辑

二、push_back 函数

三、迭代器 iterator

3.1 iterator 结构体

3.2 begin() 与 end() 函数

3.3 iterator 运算符重载

3.4 -> 的重载

3.5 const_iterator

四、测试代码

五、修饰符成员

5.1 insert 函数

5.2 erase 函数

5.3 push 函数

5.4 pop 函数

六、容量成员

6.1 size 函数

6.2 empty 函数

七、模板优化iterator


一、成员变量

	template<class T>
	struct ListNode
	{
		T _data;
		ListNode<T>* _prev;
		ListNode<T>* _next;
		ListNode(const T& x = T())
			:_data(x)
			,_prev(nullptr)
			,_next(nullptr)
		{}
	};

	template<class T>
	class my_list
	{
		typedef ListNode<T> Node;
	private:
		Node* _head;//哨兵位头结点
	};

		my_list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

二、push_back 函数

为了测试方便,我们在这里先把 push_back 写出来。

		void push_back(T data)
		{
			Node* NewNode = new Node(data);

			NewNode->_next = _head;
			NewNode->_prev = _head->_prev;
			_head->_prev->_next = NewNode;
			_head->_prev = NewNode;
		}

三、迭代器 iterator

3.1 iterator 结构体

这里的迭代器和之前的迭代器有所不同,诸如 string 、 vector ,他们的内存空间是连续的,迭代器的底层都是基于指针的,而 list 中,由于存储单元的随机性,迭代器不可以再是单纯的指针,而是像上面那样的结构体对象:

    template<class T>
	struct ListIterator
	{
		typedef ListNode<T> Node;
        typedef ListIterator<T> iterator;
		
        Node* _node;
		ListIterator(Node* node)//构造函数
		{
			_node = node;
		}
	};

3.2 begin() 与 end() 函数

然后,需要在 my_list 类中新添 begin 和 end 函数,因为他们都是服务于迭代器,所以他们的返回值类型应该是迭代器,还要注意,根据 STL 通用设计模式, end() 指向的位置不用于存放有效数据元素,而是作为遍历结束的标记。所以 end() 函数可以直接返回通过 _head 构造的迭代器,以此表示链表的末尾。

    template<class T>
	class my_list
	{
		typedef ListNode<T> Node;
	public:
		typedef ListIterator<T> iterator;

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

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

	private:
		Node* _head;
	};

3.3 iterator 运算符重载

随后,为了使迭代器可以跑起来,需要再对 ++ 、 -- 、 != 等运算符进行重载,
其中,为了保持与内建类型行为一致,内建类型(如intfloat等)的前置自增和自减操作返回的是引用。自定义类型(如迭代器)模拟内建类型的行为,包括操作符的使用,有助于保持语言的一致性和直观性。
后置自增和自减不返回其引用的原因与前置自增自减的设计意图及使用方式有关。后置自增操作符的主要特点是先返回对象当前的值(在自增之前的状态),然后再对对象进行自增操作。这一行为决定了其返回类型应该是对象的值而不是引用
顺便,我们可以把 == 和 * 运算符都重载:

		typedef ListIterator<T> iterator;
        iterator& operator++()//前置++
		{
			_node = _node->_next;
			return *this;
		 }
		iterator operator++(int)//后置++
		{
			iterator tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		iterator& operator--()//前置--
		{
			_node = _node->_prev;
			return *this;
		}
		iterator operator--(int)//后置--
		{
			iterator tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		T& operator*()
		{
			return _node->_data;
		}
		bool operator!=(const iterator& it)
		{
			return  _node != it._node;
		}
		bool operator==(const iterator& it)
		{
			return _node == it._node;
		}

3.4 -> 的重载

当我们创建一个自定义类型,如 class A(此时在Flash命名空间内):

	class A
	{
	public:
		int a = 0;
		int b = 0;
	};

去测试 A 类型的 list 时,虽然 push_back 可以用,但是我们想遍历链表时,迭代器却不能用了:

	void test()
	{
		my_list<A> la;
		la.push_back({ 6, 12 });
		la.push_back({ 7, 18 });
		la.push_back({ 3, 22 });
		my_list<A>::iterator it = la.begin();
		while (it != la.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}


错误的原因在于,系统不支持自定义类型的流插入,这里有两种解放方案:
1.重新重载一个 << 运算符
2.改变输出的格式 cout << (*it).a << (*it).b << " ";

		while (it != la.end())
		{
			cout << (*it).a << (*it).b << " ";
			it++;
		}

 但是究其本源,迭代器模拟的还是指针,如果是指针的话,就可以通过 -> 读取指针指向空间的数据,所以这里可以重载 -> 来达到模拟指针目的。

		T* operator->()
		{
			return &_node->_data;//->的优先级更高
		}

3.5 const_iterator

为了保证迭代器的内容不被修改,我们还要为迭代器添加 const ,但是根据所学知识,仅在函数返回值前加 const 是不能满足需求的,这只能保证迭代器本身不能被修改,此时就不可能自增和自减了,所以要新建一个 const_iterator 类,来模拟实现 const T* 指针的行为。

其中只有 operator* 与 operator-> 返回值不同。

template<class T>
	struct ListConstIterator
	{
		typedef ListNode<T> Node;
		typedef ListConstIterator<T> const_iterator;

		Node* _node;

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

		// *it
		const T& operator*()
		{
			return _node->_data;
		}

		// it->
		const T* operator->()
		{
			return &_node->_data;
		}

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

		const_iterator operator++(int)
		{
			const_iterator tmp(*this);
			_node = _node->_next;

			return tmp;
		}

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

		const_iterator operator--(int)
		{
			const_iterator tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

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

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

此时在 my_list 类中在添加 cbegin 和 cend 函数:

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

四、测试代码

有了之前的代码,我们就可以进行统一的测试:

    void test_push_back()
	{
		my_list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(5);
		my_list<int>::iterator it = l1.begin();
		while (it != l1.end())
		{
			cout << *it << endl;
			it++;
		}
	}

五、修饰符成员

上面的代码就可以让我们基本理清 list 的行为,有助于写下面的函数。

5.1 insert 函数

库中的 insert 支持的是迭代器访问,此时 pos 是上面的迭代器结构体, pos._node 才是此时要访问的节点。pos 位置前的节点的下一个节点是新插入的节点,pos位置是新插入节点后面的节点。

		void insert(iterator pos, T data)
		{
			Node* NewNode = new Node(data);
			Node* prev = pos._node->_prev;
			Node* cur = pos._node;
			//节点位置: prev Newnode cur
			prev->_next = NewNode;
			NewNode->_prev = prev;
			NewNode->_next = cur;
			cur->_prev = NewNode;
		}

5.2 erase 函数

erase 函数更加简单,其不需要创建新节点,仅需控制指针的指向即可。

		void erase(iterator pos)
		{
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;
			//prev pos next
			prev->_next = next;
			next->_prev = prev;
			delete pos._node;
		}

5.3 push 函数

有了 insert 函数, push_back 函数就可以直接复用,因为是尾插,且传入的 pos 位置是插入位置的下一个节点, end 函数指向的是哨兵位节点,所以 pos 直接取到 end 即可。

		void push_back2(T data)
		{
			insert(end(), data);
		}

同 push_back 使用 end 作为 pos 一样, begin 返回的是哨兵位的下一个节点,所以传入位置直接取到 begin 即可。

		void push_front(T data)
		{
			insert(begin(), data);
		}

5.4 pop 函数

因为没有重载运算符 - ,所以 pop_back 传值时只能使用自减操作, end 指向的是哨兵位,所以需要使用 end 前面的位置,标准库中并没有实现减操作的重载,且效率很低,所以没有必要手搓。

		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}

六、容量成员

6.1 size 函数

`std::list`的`size()`成员函数返回容器中元素的数目。在C++11及之后的标准中,`std::list::size()`的时间复杂度为常数时间O(1)。这是通过内部维护一个表示元素数量的计数器来实现的。
每当添加或删除元素时,这个计数器就会相应地更新。
因此,`size()`函数的实现通常看起来像这样:

        size_t size() const 
        {
            return _size;  // _size是一个成员变量,用来追踪当前链表中元素的数量
        }

这里的`_size`是容器的一个私有成员变量,其在构造函数中初始化,在向容器添加元素或从容器中移除元素时被更新。

但是要注意,在使用复用函数时,如上的 "push_front" "pop_back" 时, _size 不需要进行自增或自减操作,这样可能会自增自减两次,导致错误。

6.2 empty 函数

		bool empty()
		{
			return _size == 0;
		}

七、模板优化iterator

上面的 iterator 与 const_iterator 重复度太高,此时,就可以使用模板进行优化,让编译器根据传入的参数进行实例化:

template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> iterator;
		Node* _node;
	};

 与之前学的模板不同的是,传入的参数增加了,传入的参数变成了引用和指针:

    template<class T>
	class my_list
	{
		typedef ListNode<T> Node;
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
	}

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

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

相关文章

【SQL Server】数据库死锁

在软件使用了SQL Server数据库的场合&#xff0c;运行软件时&#xff0c;出现&#xff1a; "事务(进程 ID **)与另一个进程被死锁在 锁 资源上&#xff0c;并且已被选作死锁牺牲品。请重新运行该事物"。 分析原因&#xff1a;软件多个进程在调用SQL语句访问数据库表的…

计算机网络——WEB服务器编程实验

实验目的 1. 处理一个 http 请求 2. 接收并解析 http 请求 3. 从服务器文件系统中获得被请求的文件 4. 创建一个包括被请求的文件的 http 响应信息 5. 直接发送该信息到客户端 具体内容 一、C 程序来实现 web 服务器功能。 二、用 HTML 语言编写两个 HTML文件&#xff0c;并…

使用阿里云试用Elasticsearch学习:4. 聚合——2

近似聚合 如果所有的数据都在一台机器上&#xff0c;那么生活会容易许多。 CS201 课上教的经典算法就足够应付这些问题。如果所有的数据都在一台机器上&#xff0c;那么也就不需要像 Elasticsearch 这样的分布式软件了。不过一旦我们开始分布式存储数据&#xff0c;就需要小心…

flutter中鼠标检测事件的应用---主要在于网页端使用

flutter中鼠标检测事件的应用—主要在于网页端使用 鼠标放上去 主要代码 import package:flutter/material.dart;class CustomStack extends StatefulWidget {override_CustomStack createState() > _CustomStack(); }class _CustomStack extends State<CustomStack>…

规则引擎之LiteFlow应用

官网地址&#xff1a;LiteFlow DEMO 整体结构 1.引入maven依赖 <dependency><groupId>com.yomahub</groupId><artifactId>liteflow-spring-boot-starter</artifactId><version>2.11.4.2</version> </dependency> 2. 配置yml …

TensorFlow学习之:深度学习基础

神经网络基础 神经网络是深度学习的核心&#xff0c;它们受人脑的结构和功能启发&#xff0c;能够通过学习大量数据来识别模式和解决复杂问题。神经网络的基本工作原理包括前向传播和反向传播两个阶段。 前向传播&#xff08;Forward Propagation&#xff09; 前向传播是神经…

微信小程序制作圆形进度条

微信小程序制作圆形进度条 1. 建立文件夹 选择一个目录建立一个文件夹&#xff0c;比如 mycircle 吧&#xff0c;另外把对应 page 的相关文件都建立出来&#xff0c;包括 js&#xff0c;json&#xff0c;wxml 和 wxcc。 2. 开启元件属性 在 mycircle.json中开启 component 属…

uniapp小程序中使用video视频播放卡顿

问题:在使用uniapp小程序的video视频播放,视频已经在播放了,但是进度条没走,还是卡顿的状态(测试ios能正常使用,安卓手机会出现此问题) 在网上找了很多方法,最多的说是用:custom-cache"false",试了并没有效果,看来和我问题不一样,后来用了个简单粗暴的方法,发现是有效…

docker 创建容器过程

结合下图&#xff0c;本文讨论docker 创建容器过程&#xff1a; START└── [用户通过Docker Client发出指令]└── (1) docker run 或 docker create 命令├── (2) Docker Client与Docker Daemon建立通信连接└── (3) Docker Daemon接收到创建容器请求├── (4) 检查…

密码学基础--搞清RFC和PKCS(1)

目录 1. CryptoDriver里KeyElement格式 2. 挖掘RFC标准 3.小结 昨天从生成密钥对开始逐步了解了公钥、证书等各种编码方式&#xff0c;今天继续趁热打&#xff0c;做一个理论知识汇总。 Ps:我只是标准的翻译搬运工。 1. CryptoDriver里KeyElement格式 在 CryptoKeyElemen…

实现鼠标在页面点击出现焦点及大十字星

近段时间&#xff0c;在完成项目进度情况显示时候&#xff0c;用户在操作鼠标时候&#xff0c;显示当鼠标所在位置对应时间如下图所示 代码实现步骤如下&#xff1a; 1.首先引用 jquery.1.7.js 2.再次引用raphael.js 3.然后引用graphics.js 4.最后引用mfocus.js 其中mfocu…

地理信息系统(ArcGIS)在水文水资源、水环境中的应用

刘老师&#xff08;副教授&#xff09;&#xff1a;来自北京重点高校资深专家&#xff0c;长期从事水资源与水环境、流域污染控制与管理、非点源模拟与控制、环境信息系统开发、环境遥感与GIS应用等领域的研究&#xff0c;发表多篇Sci论文、具有资深的技术底蕴和专业背景。 1、…

多张固定宽度元素,随着屏幕尺寸变化自动换行

背景&#xff1a;多张固定宽度元素&#xff0c;随着屏幕尺寸变化自动换行实现&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevic…

最好的 iPhone 解锁器解决方案功能、优点和缺点

现最受好评的 iPhone 解锁器&#xff0c;例如 奇客解锁、iPassGo 等。深入了解它们的优缺点和兼容性&#xff0c;以做出明智的决定。了解如何解锁 iPhone 并解决有关解锁方法、PIN 绕过和潜在风险的常见问题。 1、奇客手机解锁 奇客手机解锁是一款适用于 iOS 的多功能解锁工具…

Ubuntu-22.04安装VMware虚拟机并安装Windows10

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、VMware是什么&#xff1f;二、安装VMware1.注册VMware账号2.下载虚拟机3.编译vmmon&vmnet4.加载module5.安装bundle 三、安装Windows101.基础配置2.进阶…

RISC-V特权架构 - 中断注入

中断注入 1 中断注入的作用2 mip寄存器3 中断注入后的处理过程 本文属于《 RISC-V指令集基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 中断注入的作用 中断注入&#xff0c;就是在M模式下&#xff0c;手动向S模式去产生一个中断。 比如&#xff1a;向mip寄存器的bit5…

为什么需要SOCKS代理?

在数字化时代&#x1f310;&#xff0c;随着网络安全威胁的不断演进和增加&#xff0c;保护个人隐私和数据安全成为了互联网用户的一大挑战&#x1f6e1;️。在寻求增强在线安全和隐私的解决方案时&#xff0c;SOCKS代理成为了一个关键的技术工具&#x1f511;。本文旨在详细探…

华为数通配置旁挂二层组网直接转发实验

配置旁挂二层组网直接转发示例 组网图形 组网需求 AC组网方式&#xff1a;旁挂二层组网。DHCP部署方式&#xff1a; AC作为DHCP服务器为AP分配IP地址。汇聚交换机SwitchB作为DHCP服务器为STA分配IP地址。业务数据转发方式&#xff1a;直接转发。 数据规划 表1 AC数据规划表 …

Linux JDK修改不生效

原JDK8&#xff0c;现需要切换为JDK11&#xff0c;环境变量已经修改为11&#xff0c;但java -version还是显示8。 ln -s -f /home/jdk-11.0.19/bin/java

数据结构——线性表(链式存储结构)

语言&#xff1a;C语言软件&#xff1a;Visual Studio 2022笔记书籍&#xff1a;数据结构——用C语言描述如有错误&#xff0c;感谢指正。若有侵权请联系博主 一、线性表的逻辑结构 线性表是n个类型相同的数据元素的有限序列&#xff0c;对n>0&#xff0c;除第一元素无直接…