【C++】list 模拟笔记

news2024/10/7 14:25:25

文章目录

  • list
  • 定义结点类(list_node)
  • 为什么封装迭代器为类 ?
  • 库里面模板多参数的由来 ?
  • 为什么普通迭代器不能隐式类型转换成const迭代器?
  • 迭代器位置指向及其返回值和整体代码

list

  list 和前面学习的 string 和 vector 稍有差别,list 存储空间是不连续的,不支持随机访问。list是一个双向带头循环链表,成员变量只有一个结点(Node)类型的指针 。在 C 语言部分,结点是个结构体,而在 C++ 中已经升级成为了类,可以实现自己的各种默认成员函数。这无疑成为了我们实现的难点。因为 list也是个类,其成员函数是 Node 类的指针,而且我们还要用模板来实现

定义结点类(list_node)

  这里注意用 struct 定义结点(Node)类,因为 list 类要频繁使用 Node 类,友元太麻烦,下方迭代器类也是用 struct 定义,方便 list 频繁使用。我们定义的时候,随便把构造函数写一下,用 T() 空类型作缺省值,T 如果是内置类型 int,就会用 0 初始化。类名不用 Node,因为库里别的容器也会用到结点类,且实现方式不一样,这里区分一下

库里定义的

自己实现的:

为什么封装迭代器为类 ?

  在 sting 和 vector 里面,迭代器可以实现成原生指针,解引用变得到了 val 值。而现在 list的存储空间不连续,迭代器++是到下一个结点位置,我们中间解引用得到的是一个结点,而我们现在需要的是解引用能到得结点里面的 val 值。对此我们不得不将迭代器封装成为一个类,通过重载 operator++ 和 operator* ,来实现迭代器的功能。
  迭代器的成员变量只有一个就是 Node 类型的指针, 由于我们迭代器类需要结点类型的指针,于是我们 typedef 重命名一下 list_node 为 Node

库里面模板多参数的由来 ?

  首先迭代器的类名不能直接是 iterator,别的容器也有迭代器,这里定义成 _list_iterator 。我们一开始是这么定义普通对象的 iterator ,那么思考我们如何定义 const 迭代器?

typedef _list_iterator iterator;

  假如我们向下面这样定义 const 迭代器,第一眼看好像没什么问题,但仔细一想,迭代器的成员变量是一个 Node 指针,这么定义是结点不能移动指向下一个位置,反而结点指向的内容可以被修改。我们需要的是 operator* 解引用后,结点的 val 不能被修改,但是结点可以依然可以移动,指向下一个位置

typedef const _list_iterator const_iterator;

  倘若我们像下面这样多定义一个 const 迭代器类,然后除了返回值不一样,其它照抄 _list_iterator 类来写的话,又会造成代码冗余

template
struct _list_const_iterator
{
……
}
typedef const _list_const_iterator const_iterator;

  对此我们多加一个模板参数,T& ,对于普通对象用实例化为普通迭代器,const 对象就实例化为 const 迭代器,这样就解决了 operator* 函数返回值的问题,而库里面又重载了 operator-> 函数,返回值是 T*,有分普通和 const 对象,因此我们再增加一个模板参数 T*。在这里注意一下,operator-> 返回的是指针,假设我们有 list 迭代器 it,且 T 类型为一个结构体。当我们要用迭代器访问结构体内的成员,我们就要这样写,it->->(结构体内的成员),第一个箭头调用 operator->,第二个指向结构体内的成员。但是编译器要求可读性,会优化为 it->(结构体内的成员)

typedef _list_iterator<T,T&> iterator;
typedef _list_iterator<T,const T&> const_iterator;
//普通迭代器
typedef _list_iterator<T,T&,T
*> iterator;
//const迭代器
typedef _list_iterator<T,const T&,const T
*> const_iterator



为什么普通迭代器不能隐式类型转换成const迭代器?

  首先确保自己知道了 list 的大致实现原理。那么当我们自己实现一个 iterator 时,如果不加以注意,可能会发生如下情况:我们明明没有对权限进行放大,为什么编译器还会报错呢?

  这是因为在泛型编程中,如果我们没有专门定义一个拷贝构造函数,那么默认的会以自己的类型为基准去考虑参数类型。不要用 int 可以隐式转化成 const int 的思维去考虑类模板。虽然 iterator和const_iterator 在底层调用的都是同一个类模板,但是在实例化的时候,编译器会认为这是两个不同的类型!所以这里原因很清楚了,当我们定义一个const_iterator时,其默认拷贝构造的参数也是const_iterator。
  所以这里原因很清楚了,当我们定义一个 const_iterator 时,其默认拷贝构造的参数也是 const_iterator。因此,编译器才会报错说 iterator<int, int&, int*> 无法转化为 iterator<int, const int&, const int*> 。
  解决方式也很简单,我们只需要在list_iterator 类中定义一个构造函数即可,参数为普通对象的迭代器类型,这样无论被拷贝对象时const迭代器还是普通迭代器都能调用这个函数,而且迭代器我们只需要完成浅拷贝,因为我们就是要指向同一块空间!!!

看看库里的:

迭代器位置指向及其返回值和整体代码

  稍微注意一下,begin() 指向的是 _head 的下一个结点,而 end() 指向的是 _head 结点。增删查改中,由于 erase 函数删除结点会导致迭代器失效,因此需要更新返回新的迭代器,新的迭代器指向删除结点的下一个。 insert 函数的话也和 erase 函数保持一致返回迭代器,不过返回的是新插入的结点

#pragma once
#include <iostream>
#include <assert.h>

namespace Me
{
	//定义结点
	template<class T>
	struct list_node
	{
		//定义为公有,让迭代器访问
		list_node<T>* _prev;
		list_node<T>* _next;
		T _val;
		//升级成为了类有自己的构造函数
		list_node(const T& val = T())
			: _prev(nullptr)
			,_next(nullptr)
			, _val(val)
		{}
	};

	//封装迭代器,定义为公有,让list访问
	template<class T,class Ref,class Ptr>
	struct _list_iterator
	{
		typedef list_node<T> Node;
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;
		typedef _list_iterator<T, Ref, Ptr> self;
		Node* _node;
		//升级成为了类有自己的构造函数
		_list_iterator(Node* node=nullptr)
			:_node(node)
		{}

		_list_iterator(const iterator&x)
			:_node(x._node)
		{}

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

		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) 
		{
			//这里不用单独写拷贝构造,浅拷贝就行,按需求来分析
			_list_iterator<T> tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

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

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

	//定义链表
	template<class T>
	class list//定义链表
	{
		typedef list_node<T> Node;

	public:
		//普通迭代器
		typedef _list_iterator<T,T&,T*> iterator;
		//const迭代器
		typedef _list_iterator<T,const T&,const T*> const_iterator;

		//typedef const _list_iterator<T> const_iterator;
		// 这样设计const迭代器是不行的,因为const迭代器期望指向内容不能修改
		// 这样设计是迭代器本身不能修改

		iterator begin()
		{
			//return _head->_next;
			return iterator(_head->_next);
		}
		iterator end()
		{
			//左闭又开 _head不存储有效数据
			//return iterator(_head);
			return _head;
		}
		const_iterator begin() const
		{
			//return _head->_next;
			return const_iterator(_head->_next);
		}
		const_iterator end() const
		{
			//左闭又开 _head不存储有效数据
			//return iterator(_head);
			return _head;
		}
		//哨兵卫头结点初始化
		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
		}

		list()
		{
			empty_init();
		}
		list(list<T>& lt)
		{
			empty_init();
			//迭代拷贝
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		void swap(list<T> lt)
		{
			std::swap(_head, lt._head);
		}
		//拷贝构造后交换
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		//清理部分
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		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;
			insert(end(), x);
		}
		void pop_front()
		{
			erase(begin());
		}
		void pop_back()
		{
			//assert(_head != _head->_next);
			//Node* tail = _head->_prev;
			//Node* tailP = tail->_prev;
			//tailP->_next = _head;
			//_head->_prev = tailP;
			//delete tail;
			erase(--end());
		}

		iterator insert(iterator pos, const T& x)
		{
			//调用结点的拷贝构造函数
			Node* newnode = new Node(x);

			//找插入的前一个结点
			Node* prev = pos._node->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;

			newnode->_next = pos._node;
			pos._node->_prev = newnode;
			return newnode;
		}
		iterator erase(iterator pos)
		{
			assert(pos._node != _head);
			assert(_head != _head->_next);
			Node* prev = pos._node->_prev;
			Node* next = pos._node->_next;
			prev->_next = next;
			next->_prev = prev;

			delete pos._node;
			pos._node = nullptr;
			return next;
		}

		size_t size()
		{
			size_t sz = 0;
			for (auto& e : *this)
			{
				sz++;
			}
			return sz;
		}
		void print()
		{
			iterator it = begin();
			while (it != end())
			{
				cout << *it << " ";
				++it;
			}
			cout << endl;
		}
	private:
		Node* _head;//带哨兵我的头节点
	};
}

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

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

相关文章

SAP HANA数据库学习

Systems介绍&#xff1a; Catalog下面存HANA的 Schema。 Content下面建package来 放view 。 Provisioning可以建虚拟表 数据源啥的。 Security下面 是用户角色和权限。 一个calculation view里面的scenario场景下都有啥呢&#xff1f; 首先是semantics下面的节点&#xff0…

跨网段耦合器的作用

你是否曾经遇到过需要跨网段访问设备的问题&#xff1f;比如在工业自动化领域&#xff0c;PLC和数控设备的连接。这时候&#xff0c;远创智控YC8000-NAT就能帮你轻松解决。 1, 远创智控YC8000-NAT是一款功能强大的设备&#xff0c;它可以将LAN1口所连接PLC的IP地址和端口号&a…

MySQL多版本并发控制原理(MVCC)

在数据库系统中&#xff0c;事务是指由一系列数据库操作组成的一个完整的逻辑过程&#xff0c;事务的基本特性是ACID&#xff1a; A : Atomicity (原子性) C: Consistency (一致性) I: Isolation (隔离性) D: 持久性&#xff08;Durability&#xff09; 由于大部分数据库都是高…

听GPT 讲K8s源代码--pkg(五)

在 Kubernetes 中&#xff0c;kubelet 是运行在每个节点上的主要组件之一&#xff0c;它负责管理节点上的容器&#xff0c;并与 Kubernetes 控制平面交互以确保容器在集群中按照期望的方式运行。kubelet 的代码位于 Kubernetes 代码库的 pkg/kubelet 目录下。 pkg/kubelet 目录…

档案数字化扫描完成标准有哪些内容?

档案数字化扫描完成标准是指在进行数字化扫描即将纸质文档或图片等非数字化文件转化为数字格式的文件的过程中&#xff0c;要满足一系列严格的要求。 1.扫描速度快。由于档案数量庞大&#xff0c;数字化扫描需要快速高效地进行&#xff0c;因此需要采用高效的扫描设备和软件&am…

函数-嵌入式C语言

函数-嵌入式C语言 值传递 地址传递

基于C语言的科学计算器

完整资料进入【数字空间】查看——baidu搜索"writebug" 一、产品概述 计算器&#xff1a; 它是一个拥有扁平化优雅用户界面的科学计算器&#xff0c;拥有科学计算与基础计算器功能&#xff0c;可以计算是是数学表达式&#xff1a;从一个简单的表达式&#xff0c;如…

postgresql源码学习(58)—— 删除or重命名WAL日志?这是一个问题

最近因为WAL日志重命名踩到大坑&#xff0c;一直很纠结WAL日志在什么情况下会被删除&#xff0c;什么情况下会被重命名&#xff0c;钻研一下这个部分。 一、 准备工作 1. 主要函数调用栈 首先无用WAL日志的清理发生检查点执行时&#xff0c;检查点执行核心函数为CreateCheckPo…

96、Kafka中Zookeeper的作用

Kafka中zk的作用 它是一个分布式协调框架。很好的将消息生产、消息存储、消息消费的过程结合在一起。在典型的Kafka集群中, Kafka通过Zookeeper管理集群配置&#xff0c;选举leader&#xff0c;以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到…

PyQt5:使用PyQtWebEngine

1. PyQt 5.13.0 1.1 安装PyQt pip install PyQt55.13.0 -i https://pypi.tuna.tsinghua.edu.cn/simple1.2 安装PyQtWebEngine pip install PyQtWebEngine5.13.0 -i https://pypi.tuna.tsinghua.edu.cn/simplepip list 1.3 测试 python文件 import sys from PyQt5.QtCore imp…

ARM——点灯实验

循环点灯 RCC寄存器使能GPIOE、GPIOF组寄存器 修改GPIOx组寄存器下的值 通过GPIOx_MODER寄存器设置为输出模式通过GPIOx_OTYOER寄存器设置为推挽输出类型通过GPIOx_OSPEEDR寄存器设置为低速输出通过GPIOx_PUPDR寄存器设置为禁止上下拉电阻点灯 通过GPIOx_ODR寄存器设置为高电…

day33哈希表

1.哈希表 常见的哈希表分为三类&#xff0c;数组&#xff0c;set&#xff0c;map&#xff0c;C语言的话是不是只能用数组和 2.例题 题目一&#xff1a; 分析&#xff1a;题目就是判断两个字符串出现的次数是否相同&#xff1b; 1&#xff09;哈希表26个小写字母次数初始化为0&…

K8S初级入门系列之一-概述

一、前言 K8S经过多年的发展&#xff0c;构建了云原生的基石&#xff0c;成为了云原生时代的统治者。我将用三个博客系列全面&#xff0c;循序渐进的介绍K8S相关知识。 初级入门系列&#xff0c;主要针对K8S初学者&#xff0c;以及希望对K8S有所了解的研发人员&#xff0c;重点…

【贪心算法part05】| 435.无重叠区间、763.划分字母区间、56.合并区间

目录 &#x1f388;LeetCode435. 无重叠区间 &#x1f388;LeetCode763.划分字母区间 &#x1f388;LeetCode 56.合并区间 &#x1f388;LeetCode435. 无重叠区间 链接&#xff1a;435.无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, …

【雕爷学编程】Arduino动手做(55)--DHT11温湿度传感器模块3

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

力扣C++|一题多解之数学题专场(2)

目录 50. Pow(x, n) 60. 排列序列 66. 加一 67. 二进制求和 69. x 的平方根 50. Pow(x, n) 实现 pow(x,n)&#xff0c;即计算 x 的 n 次幂函数&#xff08;即x^n&#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例…

【SQL应知应会】表分区(五)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…

day42-servlet下拉查询/单例模式

0目录 1.Servlet实现下拉查询&#xff08;两表&#xff09; 2.单例模式 1.实战 1.1 创建工程&#xff0c;准备环境... 1.2 接口 1.3 重写方法 1.4 servlet 1.5 list.jsp list.jsp详解 2.单例模式 2.1 饿汉模式&#xff1a;在程序加载时直接创建对象&#…

8.4 利用集成运放实现的信号转换电路

在控制、遥控、遥测、近代生物物理和医学等领域&#xff0c;常常需要将模拟信号进行转换&#xff0c;如将信号电压转换成电流&#xff0c;将信号电流转换成电压&#xff0c;将直流信号转换成交流信号&#xff0c;将模拟信号转换成数字信号&#xff0c;等等。 一、电压 - 电流转…

【网络】socket——TCP网络通信 | 日志功能 | 守护进程

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 上篇文章中本喵介绍了UDP网络通信的socket代码&#xff0c;今天介绍TCP网络通信的socket代码。 TCP &a…