list的使用和模拟实现

news2024/12/24 20:50:44

目录

1.list的介绍及使用

1.1 list的介绍

1.2 list的使用

1.2.1 list的构造

1.2.2 list iterator的使用

1.2.3 list capacity

1.2.4 list element access

1.2.5 list modifiers

2.为什么使用迭代器?

3.list的模拟实现

3.1完整代码

3.2代码解析

4.list与vector的对比


1.list的介绍及使用

1.1 list的介绍

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

1.2 list的使用

list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口

1.2.1 list的构造

构造函数

接口说明

list (size_type n, const value_type& val = value_type())

构造的list中包含n个值为val的元素

list ()

构造空的list

list (const list& x)

拷贝构造函数

list (InputIterator first, InputIterator last)

用两个迭代器[firs, last)区间中的元素构造list

1.2.2 list iterator的使用

此处,可暂时将迭代器理解成一个指针,该指针指向list中的某个节点

函数声明

接口说明

begin+end

返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器

rbegin+rend

返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin的前一个位置

1. beginend为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

1.2.3 list capacity

函数声明

接口说明

empty

检查list是否为空,是返回true,否则返回false

size

返回list中有效节点的个数

1.2.4 list element access

函数声明

接口声明

front

返回list的第一个节点中值的引用

back

返回list的最后一个节点中值的引用

1.2.5 list modifiers

函数声明

接口说明

push_front

在list首元素前插入值为val的元素

pop_front

删除list中第一个元素

push_back

在list尾部插入值为val的元素

pop_back

删除list中最后一个元素

insert

在list position位置的元素

erase

删除list position位置的元素

swap

交换两个list中的元素

clear

清空list中的有效元素

2.为什么使用迭代器?

容器类使用迭代器进行访问和遍历的主要原因包括以下几点:

  1. 抽象数据结构访问接口:通过迭代器,容器类可以提供一种统一的、抽象的方法来访问和操作容器中的元素。这样,无论容器内部的数据结构是什么,用户都可以使用相同的方式来访问和操作元素,提高了代码的可复用性和可维护性。
  2. 封装容器的内部实现细节:容器的内部实现可能采用各种不同的数据结构,例如数组、链表、树等。通过迭代器,容器可以隐藏内部实现细节,只提供迭代器的接口给用户使用,从而保护容器内部数据的完整性和安全性。
  3. 支持灵活的遍历方式:迭代器提供了多种灵活的遍历方式,例如正向遍历、反向遍历、随机遍历等。这使得用户可以根据实际需求选择最适合的遍历方式,提高了代码的灵活性和效率。
  4. 方便的算法和函数库使用:许多算法和函数库都是基于迭代器的,例如STL中的算法库、boost库等。通过使用迭代器,可以方便地在容器上应用这些算法和函数,提高了开发效率和代码的重用性。

综上所述,使用迭代器可以提供一种统一的、抽象的方法来操作容器中的元素,封装容器的内部实现细节,提供灵活的遍历方式,并支持方便的算法和函数库的使用。这使得容器的访问和遍历更加方便、灵活和高效

3.list的模拟实现

3.1完整代码

//ReverseIterator.h
#define _CRT_SECURE_NO_WARNINGS 1

namespace bit
{
	template<class Iterator, class Ref, class Ptr>//Ref表示引用类型,Ptr表示指针类型
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;//重命名
		Iterator _it;//成员

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

		Ref operator*()
		{
			Iterator tmp = _it;
			return *(--tmp);
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		Self& operator++()
		{
			--_it;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp = *this;
			--_it;
			return tmp;
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}

		Self& operator--(int)
		{
			Self tmp = *this;
			++_it;
			return tmp;
		}

		bool operator!=(const Self& s) const
		{
			return _it != s._it;
		}
	};
}
//list.h
#define _CRT_SECURE_NO_WARNINGS 1

#pragma once

#include <assert.h>
#include "ReverseIterator.h"

namespace bit
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val;

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

	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)
		{
			self 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;
		}
	};

	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;

		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		iterator begin()
		{
			//return _head->_next;//C++单参数类型支持隐式转换
			return iterator(_head->_next);//这两种写法是一样的
		}
		iterator end()
		{
			//return _head;//C++单参数类型支持隐式转换
			//虽然两种写法都是一样的,不过这种写法能更加明确的告诉我们返回的是迭代器类型的对象
			return iterator(_head);//匿名对象
		}

		const_iterator begin() const
		{
			//return _head->_next;//C++单参数类型支持隐式转换
			return const_iterator(_head->_next);//这两种写法是一样的
		}
		const_iterator end() const
		{
			return _head;
		}

		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;

			_size = 0;
		}

		list()
		{
			empty_init();
		}

		list(const list<T>& 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)
		{
			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* tail = _head->_prev;
			Node* newnode = new Node(x);

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

			newnode->_next = _head;
			_head->_prev = newnode;*/

			//第二种写 -- 复用insert函数
			insert(end(), x);
		}

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

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

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

		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;

			--_size;

			delete cur;

			return next;//因为迭代器失效问题所以要返回下一个迭代器
			//为什么失效?因为删除之后迭代器原来的指向失效,需要返回下一个节点作为新的迭代器
		}

		size_t size()
		{
			return _size;
		}

	private:
		Node* _head;
		size_t _size;
	};

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

		int _a1;
		int _a2;
	};
}
//main.cpp
#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
#include <list>
using namespace std;
#include "list.h"

void Print(const bit::list<int>& lt)
{
	bit::list<int>::const_iterator it = lt.begin();
	while (it != lt.end())
	{
		//(*it)++;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

void test_list1()
{
	bit::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	//因为链表的底层不一样,所以我们需要对其iterator进行封装然后重载运算符
	bit::list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	Print(lt);
}

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

	bit::list<bit::A>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << it->_a1 << " " << it->_a2 << endl;

		++it;
	}
	cout << endl;
}

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

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

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

void test_list4()
{
	bit::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	bit::list<int> lt1(lt);
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;

	bit::list<int> lt2;
	lt2.push_back(10);
	lt2.push_back(20);
	lt2.push_back(30);
	lt2.push_back(40);

	lt1 = lt2;
	for (auto e : lt1)
	{
		cout << e << " ";
	}
	cout << endl;
}

void test_list5()
{
	bit::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	//bit::list<int> lt1(lt);
	//for (auto e : lt1)
	//{
	//	cout << e << " ";
	//}
	//cout << endl;

	bit::list<int>::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

}

void test_list6()
{
	bit::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	bit::list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		if (*it == 2)
		{
			it = lt.erase(it);
		}
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	test_list6();
	
	return 0;
}

3.2代码解析

4.list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

vector

list

底层结构

动态顺序表,一段连续空间

带头节点的双向循环链表

随机访问

支持随机访问,访问某个元素效率O(1)

不支持随机访问,访问某个元素效率O(N)

插入和删除

任意位置插入和删除效率低,需要搬移元素,时间复杂度O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低

任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)

空间利用率

底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高

底层节点动态开辟,小节点容易造成内存碎片,空间利用率低缓存利用率低

迭代器

原生态指针

对原生态指针(节点指针)进行封装

迭代器失效

在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效

插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响

使用场景

需要高效存储,支持随机访问,不关心插入删除效率

需要大量插入和删除操作,不关心随机访问

 

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

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

相关文章

MySQL索引优化分析

MySQL索引优化分析 为什么你写的sql查询慢&#xff1f;为什么你建的索引常失效&#xff1f;通过本章内容&#xff0c;你将学会MySQL性能下降的原因&#xff0c;索引的简介&#xff0c;索引创建的原则&#xff0c;explain命令的使用&#xff0c;以及explain输出字段的意义。助你…

Servlet介绍

1.简介 Servlet是JavaWeb最为核心的内容&#xff0c;它是Java提供的一门动态web资源开发技术。 使用Servlet就可以实现&#xff0c;根据不同的登录用户在页面上动态显示不同内容。 Servlet是JavaEE规范之一&#xff0c;其实就是一个接口&#xff0c;将来我们需要定义Servlet类…

C++小游戏贪吃蛇源码

graphics.h是针对DOS下的一个C语言图形库 (c也可以) 目前支持下载此头文件的常用的有两种: 1. EGE (Easy Graphics Engine)2. EasyX Graphics LibraryEGE, 全名Easy Graphics Engine, 是windows下的简易绘图库&#xff0c;是一个类似BGI(graphics.h)的面向C/C语言新手的图形库…

win10+Vmware+ubuntu18 mosquitto调试记录

记录一下在建立mqtt调试环境上遇到的问题及对策。 我的PC环境为&#xff0c;win10为办公环境&#xff0c;Vmware虚拟机安装ubuntu18&#xff0c;虚拟机主要用来进行代码编译&#xff0c;建立mosquitto server测试环境。 1. ubuntu 安装mosquitto 安装mosquitto网上很多教程&…

CDN(内容分发网络)

CDN的全称是 Content Delivery Network, 即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络&#xff0c;依靠部署在各地的边缘服务器&#xff0c;通过中心平台的负载均衡、内容分发、调度等功能模块&#xff0c;使用户就近获取所需内容&#xff0c;降低网络拥塞&a…

音乐播放器应用——Android开发组件化实例分析

组件化技术在Android开发中扮演着重要的角色&#xff0c;它能够将一个庞大的应用拆分成多个独立的组件&#xff0c;每个组件都可以独立开发、测试和维护。本文将深入探讨组件化技术的认知、优势以及搭建过程&#xff0c;并通过代码示例解析组件化实例分析。最后&#xff0c;我们…

C++:类与对象(下)- this指针、(拷贝)构造函数、析构函数、复制运算符重载

目录 一、 this指针 1.1 引入 1.2 问题 1.3 特性 二、 构造函数 2.1 概念 2.2 特性 2.3 语法 2.4 注意点 三、 析构函数 3.1 概念 3.2 特性 3.3 示例 四、拷贝构造函数 4.1 概念 4.2 特性 4.3 示例 4.4 深浅拷贝 五、 赋值运算符重载 5.1 概念 5.2 语法 5…

10-数据结构-队列(C语言)

队列 目录 目录 队列 一、队列基础知识 二、队列的基本操作 1.顺序存储 ​编辑 &#xff08;1&#xff09;顺序存储 &#xff08;2&#xff09;初始化及队空队满 &#xff08;3&#xff09;入队 &#xff08;4&#xff09;出队 &#xff08;5&#xff09;打印队列 &…

编写一个指令(v-focus2end)使输入框文本在聚焦时焦点在文本最后一个位置

项目反馈输入框内容比较多时候&#xff0c;让鼠标光标在最后一个位置&#xff0c;心想什么奇葩需求&#xff0c;后面试了一下&#xff0c;是有点影响体验&#xff0c;于是就有了下面的效果&#xff0c;我目前的项目都是若依的架子&#xff0c;用的是vue2版本。vue3的朋友想要使…

什么是POP3协议?

POP3&#xff08;Post Office Protocol Version 3&#xff09;是一个用于从电子邮件服务器获取邮件的应用层协议。以下是关于POP3的详细解释&#xff1a; 基本操作&#xff1a;使用POP3&#xff0c;电子邮件客户端可以从邮件服务器上下载电子邮件&#xff0c;并将其保存在本地。…

Unity制作护盾——3、蜂窝晶体护盾

Unity制作晶格护盾 大家好&#xff0c;我是阿赵。 继续来做护盾&#xff0c;这一期做一个蜂窝晶体护盾的效果。 一、效果展示 这个晶体护盾的特点是&#xff0c;整个护盾是由很多五边形和六边形的晶体构成&#xff0c;每一块晶体的颜色都在不停的变化&#xff0c;然后每一块晶…

使用 Spring Boot 发送电子邮件(SMTP 集成)

本文探讨了 Spring Boot 与 SMTP 的集成以及如何从您自己的 Spring Boot 应用程序发送电子邮件。 本文探讨如何从您自己的Spring Boot应用程序发送电子邮件。 是的&#xff0c;您可以拥有专用的 REST API&#xff0c;它接受电子邮件发送者和接收者的电子邮件地址、主题…

【雕爷学编程】Arduino动手做(05)---热敏电阻模块之的基本参数、模块特色、电原理与使用说明

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

SpringBoot 整合Druid

集成Druid Druid简介 Java程序很大一部分要操作数据库&#xff0c;为了提高性能操作数据库的时候&#xff0c;又不得不使用数据库连接池。 Druid 是阿里巴巴开源平台上一个数据库连接池实现&#xff0c;结合了 C3P0、DBCP 等 DB 池的优点&#xff0c;同时加入了日志监控。 D…

Chrome DevTools 与 WebSocket 数据查看失焦的问题

Chrome DevTools 在与 WebSocket 连接交互时可能会出现失焦的问题&#xff0c;这似乎是一个已知的 bug。当 DevTools 选中 WebSocket 消息时&#xff0c;如果有新的消息到达&#xff0c;DevTools 将会自动失焦&#xff0c;导致无法查看完整的消息内容。 虽然这个问题很令人困扰…

C++友元函数和友元类的使用

1.友元介绍 在C中&#xff0c;友元&#xff08;friend&#xff09;是一种机制&#xff0c;允许某个类或函数访问其他类的私有成员。通过友元&#xff0c;可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用&#xff0c;例如在类之间共享数据或…

高斯模糊与图像处理(Gaussian Blur)

高斯模糊在图像处理中的用途及其广泛&#xff0c;除了常规的模糊效果外&#xff0c;还可用于图像金字塔分解、反走样、高低频分解、噪声压制、发光效果等等等等。正因为高斯模糊太基础&#xff0c;应用太广泛&#xff0c;所以需要尽可能深入认识这个能力&#xff0c;避免在实际…

【css】css中使用变量var

CSS 变量可以有全局或局部作用域。 全局变量可以在整个文档中进行访问/使用&#xff0c;而局部变量只能在声明它的选择器内部使用。 如需创建具有全局作用域的变量&#xff0c;请在 :root 选择器中声明它。 :root 选择器匹配文档的根元素。 如需创建具有局部作用域的变量&am…

无脑——010 复现yolov8 训练自己的数据集 基于yolov8框架 使用rt detr

背景&#xff1a; 2023.08.09导师让调研transformer的相关论文&#xff0c;做CV的都知道transformer多么难跑&#xff0c;需要用8张GPU跑100多个小时&#xff0c;我这个小小实验室放不下这尊大佛&#xff0c;所以就找点小模型跑一跑&#xff0c;调研论文发现最新的是CO-DETR&am…

【BMC】OpenBMC开发基础3:引入新的开源配方

引入新的开源配方 前面介绍了如何在OpenBMC中通过新建配方引入自己的程序&#xff0c;也介绍了如何修改原有的程序&#xff0c;下面要介绍的是如何引入开源的新程序&#xff0c;这在OE系统上是很方便的&#xff0c;重点就在于引入新的配方。 OE为了方便开发者使用&#xff0c…