【C++】STL——list的模拟实现、构造函数、迭代器类的实现、运算符重载、增删查改

文章目录

  • 1.模拟实现list
    • 1.1构造函数
    • 1.2迭代器类的实现
    • 1.3运算符重载
    • 1.4增删查改

1.模拟实现list

list使用文章

在这里插入图片描述

1.1构造函数

在这里插入图片描述

析构函数

在这里插入图片描述

  在定义了一个类模板list时。我们让该类模板包含了一个内部结构体_list_node,用于表示链表的节点。该结构体包含了指向前一个节点和后一个节点的指针以及节点的值。在list中保存了链表的头节点指针和链表长度大小。

namespace my_list
{	
	//用类模板定义一个list结点
	template<class T>
	struct _list_node
	{
		//list结点的构造函数,T()作为缺省值,当未转参时调用
		_list_node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}

		//list_node的成员函数,list为带头双向循环链表
		_list_node* _prev;
		_list_node* _next;
		T _val;
	};
	
	//用类模拟实现list
	template<class T>
	class list
	{
	private:
		typedef _list_node<T> Node;
		
	public:
		//list的构造函数,初始化头节点指针
		list()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;

			_size = 0;
		}

		//析构函数
		~list()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}

			_size = 0;

			delete _head;
			_head = nullptr;
		}

	private:
		//成员函数为list的头节点指针,和链表长度
		Node* _head;
		size_t _size;
	};
}

1.2迭代器类的实现

在这里插入图片描述
在这里插入图片描述

  因为list的底层实现和之前的vector和string不同,vector和string是数组和顺序表,而list底层是链表。而对于数组和顺序表,我们可以直接使用指针来实现其迭代器,但是对于list类型就无法使用指针来实现它的迭代器,因为迭代器++无法访问到他的下一个节点。所以我们这里建立一个迭代器的结构体,用来封装node指针,来实现list的专属迭代器

  这段代码定义了一个名为__list_iterator的迭代器结构体,用于遍历链表。该结构体模板有三个模板参数:T表示链表中节点的数据类型,Ref表示引用类型T&,Ptr表示指针类型 T* _node:指向链表节点的指针。构造函数__list_iterator(Node* node):用于初始化迭代器对象,将指针node赋值给_node。通过_node就可以实现各种操作符重载。

  定义了一个迭代器结构体,用于在链表中进行遍历和操作。通过重载运算符,可以使用迭代器对象像指针一样访问链表中的元素。

//使用类进行封装Node*,来实现list的专属迭代器
//list迭代器和一些内置类型迭代器不一样,++无法准确访问到下一个位置
//typedef __list_iterator<T, T&> iterator;
//typedef __list_iterator<T, const T&> 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)
	{}

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

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

	//重载前置++
	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}

	//重载后置++
	self operator++(int)
	{
		__list_iterator<T> 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;
	}

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

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

  通过建立一个新的结构体来重载迭代器,我们即可实现list专属迭代器对其链表进行++、- -等操作。

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

//list迭代器实现
iterator begin()
{
	//隐式类型转换,内置类型转换为自定义类型,实现迭代器功能
	//return _head->_next;
	return iterator(_head->_next);
}

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

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

const_iterator end() const
{
	//return _head;
	return const_iterator(_head);
}

1.3运算符重载

  迭代器的运算符重载在上面的代码中,已经都基本实现了。

在这里插入图片描述

  赋值运算符重载函数operator=首先创建了一个临时的链表对象lt,并将传入的链表对象的副本赋值给lt。然后,调用swap函数,将当前链表对象和临时链表对象进行交换。这样做的原因是为了实现异常安全的赋值操作。通过将传入的链表对象的副本赋值给临时链表对象,可以确保在交换过程中不会影响到传入的链表对象。 最后,返回当前链表对象的引用。

  通过这样的实现逻辑,可以实现链表对象之间的赋值操作,并且保证在异常发生时不会破坏数据的完整性。

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

//赋值运算符重载
list<T>& operator=(list<T> lt)
	//list& operator=(list lt)
{
	swap(lt);

	return *this;
}

1.4增删查改

push_back

在这里插入图片描述
  和带头双向循环链表的尾插一样。首先,创建一个新的链表节点newnode,节点的值为传入的参数x。然后,获取到链表的尾指针tail,即头节点的前一个节点。

  接着,将新节点newnode插入到链表的尾部。首先,将新节点的前驱指针指向尾节点tail,将尾节点的后继指针指向新节点。这样就将新节点连接到了链表的尾部。

  然后,将新节点的后继指针指向头节点,将头节点的前驱指针指向新节点。这样就将新节点连接到了链表的头部。

//尾插
void push_back(const T& x)
{
	//创建一个新的链表结点,且获取到链表的尾指针
	Node* newnode = new Node(x);
	Node* tail = _head->_prev;

	//连接尾结点
	newnode->_prev = tail;
	tail->_next = newnode;

	//连接头节点
	_head->_prev = newnode;
	newnode->_next = _head;

	++_size;
}

insert

在这里插入图片描述

  和上面的逻辑类似。

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

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

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

	++_size;

	return newnode;
}

erase

在这里插入图片描述
  首先,通过断言判断传入的迭代器pos不等于链表的末尾迭代器。如果等于末尾迭代器,表示要删除的节点不存在,会导致错误。然后,根据传入的迭代器pos获取到要删除的节点cur,以及其前驱节点prev和后继节点next。

  接着,将前驱节点的后继指针指向后继节点,将后继节点的前驱指针指向前驱节点。这样就将要删除的节点从链表中断开。然后,释放要删除的节点的内存。还要将链表的大小减1。

  为了避免迭代器失效,返回下一个节点的指针作为新的迭代器。这样可以保证在删除节点后,仍然可以使用返回的迭代器进行遍历和操作。

//删除
iterator erase(iterator pos)
{
	assert(pos != end());

	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	prev->_next = next;
	next->_prev = prev;

	delete cur;

	--_size;

	//为了避免迭代器失效,返回下一个结点指针
	return next;
}


完整实现

#pragma once

#include<assert.h>

namespace my_list
{	
	//用类模板定义一个list结点
	template<class T>
	struct _list_node
	{
		//list结点的构造函数,T()作为缺省值,当未转参时调用
		_list_node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}

		//list_node的成员函数,list为带头双向循环链表
		_list_node* _prev;
		_list_node* _next;
		T _val;
	};

	//使用类进行封装Node*,来实现list的专属迭代器
	//list迭代器和一些内置类型迭代器不一样,++无法准确访问到下一个位置
	//typedef __list_iterator<T, T&> iterator;
	//typedef __list_iterator<T, const T&> 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)
		{}

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

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

		//重载前置++
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		//重载后置++
		self operator++(int)
		{
			__list_iterator<T> 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;
		}

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

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

	//实现const类型迭代器
	/*template<class T>
	struct __list_const_iterator
	{
		typedef list_node<T> Node;
		Node* _node;

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

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

		__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;
		}

		bool operator!=(const __list_const_iterator<T>& it)
		{
			return _node != it._node;
		}

		bool operator==(const __list_const_iterator<T>& it)
		{
			return _node == it._node;
		}
	};*/

	//用类模拟实现list
	template<class T>
	class list
	{
	private:
		typedef _list_node<T> Node;
		
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		//list迭代器实现
		iterator begin()
		{
			//隐式类型转换,内置类型转换为自定义类型,实现迭代器功能
			//return _head->_next;
			return iterator(_head->_next);
		}

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

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

		const_iterator end() const
		{
			//return _head;
			return const_iterator(_head);
		}

		//初始化
		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;

			_size = 0;
		}

		//list的构造函数,初始化头节点指针
		list()
		{
			empty_init();
		}

		//拷贝构造
		list(const list<T>& lt)
			//list(const list& lt)
		{
			empty_init();

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

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

		//赋值运算符重载
		list<T>& operator=(list<T> lt)
			//list& operator=(list lt)
		{
			swap(lt);

			return *this;
		}

		//析构函数
		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		//清理
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}

			_size = 0;
		}

		//尾插
		void push_back(const T& x)
		{
			//创建一个新的链表结点,且获取到链表的尾指针
			Node* newnode = new Node(x);
			Node* tail = _head->_prev;

			//连接尾结点
			newnode->_prev = tail;
			tail->_next = newnode;

			//连接头节点
			_head->_prev = newnode;
			newnode->_next = _head;

			++_size;

			//insert(end(), x);
		}

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

		//尾删
		void pop_back()
		{
			erase(--end());
		}

		//头删
		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);

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

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

			++_size;

			return newnode;
		}

		//删除
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			--_size;

			//为了避免迭代器失效,返回下一个结点指针
			return next;
		}

		//返回size
		size_t size()
		{
			/*size_t sz = 0;
			iterator it = begin();
			while (it != end())
			{
				++sz;
				++it;
			}

			return sz;*/

			return _size;
		}

	private:
		//成员函数为list的头节点指针,和链表长度
		Node* _head;
		size_t _size;
	};

	//打印函数
	void print(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			// (*it) += 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}


测试代码

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<list>
using namespace std;

#include"list.h"

void test_list1()
{
	//list<int> lt;
	//lt.push_back(1);
	//lt.push_back(2);
	//lt.push_back(3);
	//lt.push_back(4);
	//for (auto e : lt)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;

	my_list::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	
	my_list::list<int>::iterator it = lt.begin();

	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	auto itt = lt.begin();

	while (itt != lt.end())
	{
		++(*itt);
		++itt;
	}

	for (auto e: lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

struct A
{
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}

	int _a1;
	int _a2;
};

void test_list2()
{
	my_list::list<A> lt;
	lt.push_back(A(1, 1));
	lt.push_back(A(2, 2));
	lt.push_back(A(3, 3));
	lt.push_back(A(4, 4));

	auto it = lt.begin();
	while (it != lt.end())
	{
		//cout << *(it)._a1 << " " << *(it)._a2 <<endl;
		cout << it->_a1 << " " << it->_a2 << endl;
		++it;
	}
	cout << endl;
}

void test_list3()
{
	my_list::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.push_front(5);
	lt.push_front(6);

	print(lt);

	lt.pop_front();
	lt.pop_back();

	print(lt);

	lt.clear();
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(30);
	lt.push_back(40);

	print(lt);

	cout << lt.size() << endl;
	
}


int main()
{
	//test_list1();
	//test_list2();
	test_list3();
	return 0;
}

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

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

相关文章

Pytorch个人学习记录总结 09

目录 损失函数与反向传播 L1Loss MSELOSS CrossEntropyLoss 损失函数与反向传播 所需的Loss计算函数都在torch.nn的LossFunctions中&#xff0c;官方网址是&#xff1a;torch.nn — PyTorch 2.0 documentation。举例了L1Loss、MSELoss、CrossEntropyLoss。 在这些Loss函数…

jenkins通过sshPut传输文件的时候,报错Permission denied的SftpException

一、背景 使用jenkins的ssh插件传输文件至远程机器的指定目录&#xff0c;php程序打包后&#xff0c;经过zip压缩为oms.zip zip -rq oms.zip ./ -x .git/* -x .env然后我们求md5值 md5sum oms.zip最后执行传输。 09:03:02 Executing command on ssh[116.61.10.149]: mkdir…

C#生成dll给c++调用 方法二COM方式 vs2022 NO Make Assembly COM-Visible选错了 不需要clr

有些C项目中也用了C语言.c,用方法一就无法使用【不能使用 /clr 选项编译 C 文件】。就用方法2。 方法二:COM方式 参考&#xff1a; https://www.5axxw.com/questions/content/2ozion 1.C# 生成dll using System; using System.Collections.Generic; using System.Linq; usin…

Jvm的一些技巧

反编译字节码文件 找到对应的class文件所在的目录&#xff0c;使用javap -v -p 命令 查询运行中某个Java进程的Jvm参数 【案例】查询 MethodAreaDemo 这个类运行过程中&#xff0c;初始的元空间大小 MetaspaceSize jps 查询 Java 进程的进程ID 使用jinfo查看具体的参数&…

阿里云 MSE + ZadigX ,无门槛实现云原生全链路灰度发布

作者&#xff1a;ZadigX 企业发布现状痛点 目前企业在选择和实施发布策略时面临以下困境&#xff1a; 1. 缺乏云原生能力&#xff1a; 由于从传统部署转变为云原生模式后&#xff0c;技术架构改造需要具备相关能力的人才。这使得企业在发布策略方面难以入手。 2. 缺乏自动化…

手写自定义的spring-boot-start

需求&#xff1a;手写一个加密的spring-boot-start&#xff0c;按着用户定义的加密算法&#xff08;可选&#xff1a;MD5、SHA&#xff09;去加密内容 新建一个maven项目 新建好的项目结构和pom.xml如图 添加pom.xml 完整的pom.xml文件 <?xml version"1.0" …

VS中使用QT的插件:QT VS Tools

1、插件下载 &#xff08;1&#xff09;可以在VS中的管理扩展中直接搜索安装&#xff0c;但是我下载太慢&#xff0c;甚至是根本就无法安装。 &#xff08;2&#xff09;qt插件下载地址&#xff1a;Index of /official_releases/vsaddin 这个地址下载就很快&#xff0c;下载…

web集群学习

目录 简述静态网页和动态网页的区别 Webl.0 和 Web2.0 的区别 安装tomcat8&#xff0c;配置服务启动脚本&#xff0c;部署jpress应用 1、安装jdk文件&#xff1a; 2、下载tomcat的二进制包&#xff1a; 3、创建用户组和用户 创建Tomcat的登录服务脚本 此处创建的登录服务…

MyBatis查询数据库(3)

前言&#x1f36d; ❤️❤️❤️SSM专栏更新中&#xff0c;各位大佬觉得写得不错&#xff0c;支持一下&#xff0c;感谢了&#xff01;❤️❤️❤️ Spring Spring MVC MyBatis_冷兮雪的博客-CSDN博客 前面我们讲解了MyBatis增删改查基本操作&#xff0c;下面我们来深入了解M…

Mac上命令

1. block端口&#xff1a; sudo cp /etc/pf.conf /etc/pf443.conf 编辑pf443.conf&#xff0c;vim /etc/pf443.conf&#xff0c;如 block on en0 proto udp from any to any port 9000 # block UDP port 9000 block on en0 proto tcp from any to any port 5004 # bloc…

【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换读写分离】—— 案例实战

&#x1f4a7; S p r i n g A O P 主从数据源切换 读写分离 自定义注解案例实战&#xff01; \color{#FF1493}{Spring AOP 主从数据源切换 读写分离 自定义注解 案例实战&#xff01;} SpringAOP主从数据源切换读写分离自定义注解案例实战&#xff01;&#x1f4a7; …

Docker Sybase修改中文编码

镜像&#xff1a;datagrip/sybase 镜像默认用户名sa&#xff0c;密码myPassword&#xff0c;服务名MYSYBASE 1.进入容器 docker exec -it <container_name> /bin/bash2.加载Sybase环境变量 source /opt/sybase/SYBASE.sh3.查看是否安装了中文字符集 isql -Usa -PmyP…

【Unity学习笔记】对象池

文章目录 设计思路总体设计从生命周期考虑 一些代码 对象池这个东西老生常谈了&#xff0c;使用它的好处在于&#xff1a;当我们需要重复创建或者销毁一些物体&#xff0c;例如限制子弹数量上限为10发&#xff0c;当射出第11发就需要使第10发消失&#xff0c;第11出现。销毁10号…

NullPointerException导致手机重启案例分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、 Framework 层对象 空指针导致手机重启。二、解决方案&#xff0c;规避空指针三、Telecom APK 控制导致的重启举例 一、 Framework 层对象 空指针导…

XGBoost的基础思想与实现

目录 1. XGBoost VS 梯度提升树 1.1 XGBoost实现精确性与复杂度之间的平衡 1.2 XGBoost极大程度地降低模型复杂度、提升模型运行效率 1.3 保留了部分与梯度提升树类似的属性 2. XGBoost的sklearnAPI实现 2.1 sklearn API 实现回归 2.2 sklearn API 实现分类 3. XGBoost回…

MySQL 极速安装使用与卸载

目录 mysql-5.6.51 极速安装使用与卸载 sqlyog工具 mysql简化 mysql-8.1.0下载配置 再完善 mysql-5.6.51 极速安装使用与卸载 mysql-8.1.0下载安装在后 mysql中国官网 MySQLhttps://www.mysql.com/cn/ 点击MySQL社区服务器 点击历史档案 下载完 解压 用管理员运行cmd&a…

ODIN_1靶机详解

ODIN_1靶机复盘 下载地址&#xff1a;https: //download.vulnhub.com/odin/odin.ova 靶场很简单&#xff0c;一会儿就打完了。 靶场说明里提醒说加一个dns解析。 我们在/etc/hosts加一条解析 就能正常打开网站了&#xff0c;要么网站打开css是乱的。 这里看到结尾就猜测肯定…

uniapp自定义消息语音

需求是后端推送的消息APP要响自定义语音&#xff0c;利用官方插件&#xff0c;总结下整体流程 uniapp后台配置 因为2.0只支持uniapp自己的后台发送消息&#xff0c;所以要自己的后台发送消息只能用1.0 插件地址和代码 插件地址: link let isIos (plus.os.name "iOS&qu…

<C++> 三、内存管理

1.C/C内存分布 我们先来看下面的一段代码和相关问题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";const char *pChar3 "abcd";int *ptr1…

小乌龟(TortoiseGit)连接GitLab

目录 &#x1f35f;写在前面 &#x1f35f;实验目标 &#x1f35f;安装gitlab &#x1f37f;1、安装依赖 &#x1f37f;2、下载清华gitlab包 &#x1f37f;3、安装gitlab &#x1f37f;4、修改配置文件 &#x1f37f;5、管理命令 &#x1f35f;访问gitlab &#x1f35f;界面设置…