【C++】——list的介绍及使用 模拟实现

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

文章目录

前言

一、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

1.2.6 list的迭代器失效

二、 list的模拟实现

​编辑

三、 list与vector的对比

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、list的介绍及使用

1.1 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来说这可能是一个重要的因素)

1.2 list的使用

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

1.2.1 list的构造

构造函数(constructor)接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

list的构造使用代码演示

1.2.2 list iterator的使用

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

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置

【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  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 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

list的插入和删除使用代码的演示

list中还有一些操作,需要用到时大家可参阅list的文档说明。

1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值l.erase(it);++it;}
}// 改正
void TestListIterator()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++);// it = l.erase(it);}
}

二、 list的模拟实现

Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<list>
#include<algorithm>
using namespace std;void test_op1()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;vector<int> v;for (int i = 0; i < N; ++i){auto e = rand() + i;lt1.push_back(e);v.push_back(e);}int begin1 = clock();// vector用算法排序sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();// list用自己的排序方法lt1.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}void test_op2()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt1.push_back(e);lt2.push_back(e);}int begin1 = clock();// vectorvector<int> v(lt2.begin(), lt2.end());// 用迭代器区间进行初始化,相当于数据拷贝给vector// 让vector来排序sort(v.begin(), v.end());// lt2 再将数据拷贝回给listlt2.assign(v.begin(), v.end());int end1 = clock();int begin2 = clock();lt1.sort();// 直接排序int end2 = clock();printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}
//
//int main()
//{
//	test_op2();
//
//	return 0;
//}#include"list.h"int main()
{bit::test_list3();return 0;
}
list.h
#pragma once
#include<assert.h>// 原生指针是天然的迭代器,前提是物理空间是连续的namespace bit
{template<class T>struct ListNode{// 数据全部是公有的话,可以用structListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};// typedef ListIterator<T, T&, T*> iterator;// typedef ListIterator<T, const T&, const T*> const_iterator;// 期望:通过原生指针(Node*)来遍历链表,但是每个节点在物理空间上的地址不连续,没办法遍历;// 而且解引用也拿不到节点对象中对应的数据。// 原生指针(节点的指针)不满足我们的预期,所以我们用类将原生指针封装一下,自定义类型可以重载运算符,就可以掌控它的行为template<class T, class Ref, class Ptr>// Ref:Reference(引用)   Ptr:pointer(指针)struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;// 指针都是内置类型ListIterator(Node* node):_node(node){}// *it//T& operator*()Ref operator*(){return _node->_data;}// it->//T* operator->()Ptr operator->(){return &_node->_data;// 返回的是结构体A的地址}// ++itSelf& 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){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};//template<class T>//struct ListConstIterator//{//	typedef ListNode<T> Node;//	typedef ListConstIterator<T> Self;//	Node* _node;//	ListConstIterator(Node* node)//		:_node(node)//	{}//	// *it//	const T& operator*()//	{//		return _node->_data;//	}//	// it->//	const T* operator->()//	{//		return &_node->_data;//	}//	// ++it//	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)//	{//		return _node != it._node;//	}//	bool operator==(const Self& it)//	{//		return _node == it._node;//	}//};template<class T>class list{typedef ListNode<T> Node;public://typedef ListIterator<T> iterator;// 将迭代器的类型重命名为iterator,不管迭代器是什么类型,都不重要了// const的迭代器怎么搞呢?// 1、单独搞一个const迭代器的类模板;2、一个普通迭代器,一个const的迭代器,有点冗余了,可以用一个模板参数来控制//typedef ListConstIterator<T> const_iterator;typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;// 方法一://iterator begin()//{//	//return iterator(_head->_next);// return后面的代码就是一个匿名对象//	iterator it(_head->_next);// iterator的构造函数(有名对象)//	return it;//}// 方法二:单参数的构造函数具有隐式类型转换// 普通的迭代器会被修改iterator begin(){return _head->_next;}iterator end(){return _head;}// const迭代器,需要是迭代器(返回的是指针)不能修改,还是迭代器指向的内容?// 迭代器指向的内容不能修改!const iterator不是我们需要const迭代器,const修饰的是iterator(一个自定义类型)// T* const p1// const T* p2const_iterator begin() const{return _head->_next;}// 为什么const_iterator要加中间的下划线呢?因为const iterator是使迭代器不能被修改,不是我们需要的const迭代器。// 所以const的迭代器并不是在普通迭代器前面加上一个const,而是创建了一个新的const_iterator类型。const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}// 无参的构造函数list(){empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();// 先搞一个哨兵位的头节点,自己指向自己// 这里的e前面要加引用,因为T有可能是string类型,如果是string类型的话,不加引用,又是浅拷贝for (auto& e : lt){push_back(e);}}// 需要析构,一般就需要自己写深拷贝// 不需要析构,一般就不需要自己写深拷贝,默认浅拷贝就可以void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}// lt1 = lt3list<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_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;}*/void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;// prev newnode cur;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;}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 iterator(next);}size_t size() const{return _size;}bool empty(){return _size == 0;}private:Node* _head;size_t _size;};void test_list1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);// 不同的容器,它们内部的迭代器的类型都是不同的list<int>::iterator it = lt.begin();// 内嵌类型:1、类部类;2、typedef// iterator这个类型属于list<int>这个类域while (it != lt.end()){*it += 10;cout << *it << " ";++it;}cout << endl;lt.push_front(10);lt.push_front(20);lt.push_front(30);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_back();lt.pop_back();lt.pop_front();lt.pop_front();for (auto e : lt){cout << e << " ";}cout << endl;}struct A{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}};void test_list2(){list<A> lt;A aa1(1, 1);A aa2 = { 1, 1 };// 多参数的构造函数也可以支持隐式类型转换lt.push_back(aa1);lt.push_back(aa2);lt.push_back(A(2, 2));lt.push_back({ 3, 3 });// 隐式类型转换lt.push_back({ 4, 4 });A* ptr = &aa1;(*ptr)._a1;ptr->_a1;list<A>::iterator it = lt.begin();while (it != lt.end()){//*it += 10;// cout << *it << " ";// 流插入不支持自定义类型,如果想要流插入支持自定义类型:// 1、自己写一个流插入的运算符重载;2、数据是共有的// *it是调用operator*()函数,返回的是A的对象//cout << (*it)._a1 << ":" << (*it)._a2 << endl;cout << it->_a1 << ":" << it->_a2 << endl;cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;++it;}cout << endl;}void PrintList(const list<int>& clt){list<int>::const_iterator it = clt.begin();while (it != clt.end()){//*it += 10;cout << *it << " ";++it;}cout << endl;}void test_list3(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);PrintList(lt);list<int> lt1(lt);PrintList(lt1);}
}

三、 list与vector的对比

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

vectorlist
底 层 结 构动态顺序表,一段连续空间带头结点的双向循环链表
随 机 访 问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素 效率O(N)
插 入 和 删 除任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1)
空 间 利 用 率底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低
迭 代 器原生态指针对原生态指针(节点指针)进行封装
迭 代 器 失 效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使 用 场 景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随 机访问


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构基础 ——数组VS链表(二)

一、数组 数组对应的英文是array&#xff0c;是有限个相同类型的变量所组成的有序集合&#xff0c;数组中的每一个变量称为元素。数组是最简单、最常用的数据结构。 数组存储格式&#xff1a; 在Python语言中&#xff0c;并没有直接使用数组这个概念&#xff0c;而是使用列表(…

Transformer模型-encoder编码器,padding填充,source mask填充掩码的简明介绍

今天介绍transformer模型的encoder编码器&#xff0c;padding填充&#xff0c;source mask填充掩码 背景 encoder编码器层是对之前文章中提到的子层的封装。它接收位置嵌入的序列&#xff0c;并将其通过多头注意力机制和位置感知前馈网络。在每个子层之后&#xff0c;它执行残差…

SQLite数据库文件格式(十五)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite 4.9的虚拟表机制(十四) 下一篇&#xff1a;SQLite超详细的编译时选项&#xff08;十六&#xff09; ► 目录 本文档描述和定义磁盘上的数据库文件 自 SQLite 以来所有版本使用的格式 版本 3.0.0 &#xff08;2004-06-18…

PVE系统的安装

一.PVE系统的安装 前置准备环境:windows电脑已安装Oracle VM VirtualBox,电脑支持虚拟化,且已经开启,按住ctrl+shift+ESC打开任务管理器查看是否开启,如果被禁用,可进入BIOS开启虚拟化,重启电脑后再进行后续操作。本步骤选用windows10安装VirtualBox,版本为7.0.8。 …

web安全-SSH私钥泄露

发现主机 netdiscover -r 192.168.164.0 扫描端口 看到开放80和31337端口都为http服务 浏览器访问测试 查看80端口和31337端口网页和源代码并无发现有用信息 目录扫描 扫描出80端口并无有用信息 扫描31337端口 发现敏感文件robots.txt和目录.ssh 访问敏感文件和目录 /.ss…

Ansys Zemax | 如何将光栅数据从Lumerical导入至OpticStudio(下)

附件下载 联系工作人员获取附件 本文介绍了一种使用Ansys Zemax OpticStudio和Lumerical RCWA在整个光学系统中精确仿真1D/2D光栅的静态工作流程。将首先简要介绍方法。然后解释有关如何建立系统的详细信息。 本篇内容将分为上下两部分&#xff0c;上部将首先简要介绍方法工作…

一键修复所有DLL缺失解决步骤,使用dll修复工具详情

在使用电脑或安装软件时&#xff0c;我们有时会遭遇DLL文件丢失的情况&#xff0c;这会阻止软件正常启动或运行。为此&#xff0c;一个简易且有效的解决方案是使用一键修复所有DLL缺失问题的工具。 引言 DLL&#xff08;动态链接库&#xff09;是Windows操作系统的核心部分&am…

k8s_入门_kubelet安装

安装 在大致了解了一些k8s的基本概念之后&#xff0c;我们实际部署一个k8s集群&#xff0c;做进一步的了解 1. 裸机安装 采用三台机器&#xff0c;一台机器为Master&#xff08;控制面板组件&#xff09;两台机器为Node&#xff08;工作节点&#xff09; 机器的准备有两种方式…

文库配置异步转换(宝塔)| 魔众文库系统

执行以下操作前提前进入网站根目录&#xff0c;如 cd /www/wwwroot/example.com执行 artisan 命令前请参照 开发教程 → 开发使用常见问题 → 如何运行 /www/server/php/xxx/bin/php artisan xxx 命令 步骤1&#xff0c;生成数据库队列表迁移文件 在执行该步骤前&#xff0c;请…

橘子学JDK之JMH-02(BenchmarkModes)

一、案例二代码 这次我们来搞一下官网文档的第二个案例&#xff0c;我删除了一些没用的注释&#xff0c;然后对代码做了一下注释的翻译&#xff0c;可以看一下意思。 package com.levi;import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import …

【科技】2024最新微信机器人一键部署教程

外话 话说上次写文章好像又过了几个月了…… 其实还是因为马上小升初的各种密考&#xff0c;其它地方不知道&#xff0c;反正广东这块名校基本上都得密考考进去 笔者连考几次都惨不忍睹…… 不过5月份会有一个信息技术特长生招生&#xff0c;看看能不能吧~ 正文 先说&#xff…

基于SpringBoot+Vue的高校大学生心理咨询管理系统(源码+文档+部署+讲解)

一.系统概述 系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方法对高校大学生心理咨询管理的现状进行系统调查。采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计…

springboot相关报错解决

Caused by: java.lang.ClassNotFoundException: 目录 Caused by: java.lang.ClassNotFoundException: org.springframework.context.event.GenericApplicationListener spring-boot-dependencies:jar:2.1.9.RELEASE was not found org.springframework.context.event.Generi…

华为OD-C卷-攀登者1[100分]

攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。 地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如: [0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下图所示的地图 地图中有两个山脉位置分别为 1,2,3,4,5 和 8,9,1…

SV-7042V 40W网络有源音柱 智慧灯杆广播音柱

SV-7042V 40W网络有源音柱 一、描述 SV-7042V是深圳锐科达电子有限公司的一款壁挂式网络有源音柱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;其采用防水设计&#xff0c;功率40W。 SV-7042V作为网络广播播放系统的终…

LongVLM:让大模型解读长视频 SOTA 的方法

LongVLM&#xff1a;让大模型解读长视频 SOTA 的方法 使用LongVLM处理长视频的步骤LongVLM 方法3.1 总体架构3.2 局部特征聚合3.3 全局语义整合 效果4.1 实验设置4.2 主要结果4.3 消融研究4.4 定性结果 论文&#xff1a;https://arxiv.org/pdf/2404.03384.pdf 代码&#xff1a…

C语言进阶课程学习记录-main函数与命令行参数

C语言进阶课程学习记录-main函数与命令行参数 main函数验证以下4中定义是否正确实验-main的返回值cmd窗口 实验-main的输入参数cmd窗口 在main函数执其执行的函数实验-程序执行的第一个函数gcc编译器cmd窗口bcc编译器 小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&…

SpringCloud Alibaba Sentinel 规则持久化

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第十七篇&#xff0c;即使用 Sentinel 实现规则持久化。 二、概述 从前面我们做的实验可知&#xff0c;…

VsCode 安装Jupyter Notebook

VsCode 安装Jupyter Notebook 安装 1、打开 VSCode 编辑器&#xff0c;点击界面左端的【扩展】栏&#xff1b; 2、在【搜索框】中输入python&#xff0c;点击第一个Python&#xff0c;检查是否已经安装 python 插件&#xff0c;没安装的点击安装&#xff1b;已安装的继续第3步…

使用新版FLIR (FLIR_ADAS_v2) 数据集创建yolo格式数据集(目标检测)

FLIR在2022.1.19发布了新版的FLIR_ADAS_v2&#xff0c;有着更多的类别和数量更丰富的图像。数据集同步注释热图像和无注释RGB图像供参考。本文章主要介绍如何使用FLIR_ADAS_v2中的rgb图像和thermal图像来制作yolo格式数据集。 1.官方数据集下载&#xff1a;FLIR_ADAS_v2数据集…