[STL-list]介绍、与vector的对比、模拟实现的迭代器问题

一、list使用介绍

  1.  list的底层是带头双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  2. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
  3. list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;
常用接口:

构造函数:

构造函数接口说明
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<int> l1;                         // 构造空的l1list<int> l2(4, 100);                 // l2中放4个值为100的元素list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3list<int> l4(l3);                    // 用l3拷贝构造l4// 以数组为迭代器区间构造l5int array[] = { 16,2,77,29 };list<int> l5(array, array + sizeof(array) / sizeof(int));// 列表格式初始化C++11list<int> l6{ 1,2,3,4,5 };

list iterator的使用

对于迭代器的使用我们可以把它理解为一个指针,指向ist的某个节点

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

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

代码演示:

    list<int> lt(4, 100);  list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}       cout << endl;// C++11范围for的方式遍历for (auto& e : lt)cout << e << " ";cout << endl;

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中的有效元素

代码演示:

void TestList1()
{int array[] = { 1, 2, 3 };list<int> L(array, array + sizeof(array) / sizeof(array[0]));// 在list的尾部插入4,头部插入0L.push_back(4);L.push_front(0);// 删除list尾部节点和头部节点L.pop_back();L.pop_front();
}// insert /erase 
void TestList2()
{int array1[] = { 1, 2, 3 };list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));// 获取链表中第二个节点auto pos = ++L.begin();cout << *pos << endl;// 在pos前插入值为4的元素L.insert(pos, 4);// 在pos前插入5个值为5的元素L.insert(pos, 5, 5);// 在pos前插入[v.begin(), v.end)区间中的元素vector<int> v{ 7, 8, 9 };L.insert(pos, v.begin(), v.end());// 删除pos位置上的元素L.erase(pos);// 删除list中[begin, end)区间中的元素,即删除list中的所有元素L.erase(L.begin(), L.end());
}// resize/swap/clear
void TestList3()
{// 用数组来构造listint array1[] = { 1, 2, 3 };list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));// 交换l1和l2中的元素list<int> l2;l1.swap(l2);// 将l2中的元素清空l2.clear();cout << l2.size() << endl;
}

其他接口

二、与vector对比

vectorlist



动态顺序表,一段连续空间带头结点的双向循环链表


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




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




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


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




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


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

vector与list排序效率对比:

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();// sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();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());// sort(v.begin(), v.end());// lt2lt2.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);
}

可见list的排序效率是非常低的,甚至将list的数据导入vector中排完序在导回来的效率都比直接在list中排序的效率快,这是因为list不支持下标随机访问,只能依靠迭代器迭代到指定位置访问,而排序过程中避免不了需要访问大量中间元素,所以list并不适合对数据进行排序

三、list迭代器问题

链表节点与链表结构

节点包含三部分:前驱指针、后驱指针、数据。list封装了头节点的指针,可以根据该指针对后续节点进行遍历

    template<class T>struct ListNode{ListNode* _next;ListNode* _prev;T _data;//节点的构造函数ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};template<class T>class list{void Empty_Init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}//构造函数list(){Empty_Init();}private:Node* _head;size_t _size;};
如何设定list迭代器

        在string与vector的模拟实现中,迭代器使用的都是原生指针T*,这是因为原生指针可以满足迭代器的要求,++可以指向下一个元素,解引用可以访问该元素,他们可以使用原生指针的根本原因是他们储存数据的结构都是连续的物理地址。

        在list中原生指针Node*不能满足我们的要求,因为list的节点都是依靠指针连接起来的,其物理地址并不是连续的,++指向的并不是下一个元素,而是指向了跳过了一个Node的大小的的地址,并且迭代器希望解引用直接可以访问节点中的数据,而*(Node*)却是一个节点类型,所以在list中使用原生指针并不符合迭代器的要求。

         所以我们可以自己新建一个类,作为迭代器的类型,在其中封装了头节点,就可以访问该链表了,并且我们在该类中可以通过运算符重载改变++与解引用的行为,这样就可以使用迭代器访问链表数据了

    template<class T>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T> Self;Node* _node;ListIterator(Node* _node):_node(_node){}T& operator*(){return _node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}//后置++Self operator++(int){Self tmp(_node);_node = _node->_next;return tmp;}};template<class T>class list{void Empty_Init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}//构造函数list(){Empty_Init();}iterator begin(){//隐式类型转换return _head->_next;}iterator end(){//隐式类型转换return _head;}private:Node* _head;size_t _size;};
完善迭代器功能  
operator->的重载

        但是上述代码具有一定的局限性,例如当T为一个结构体A时,*iterator返回的是结构体A,想要访问结构体中的数据还需要用 例如:*(it).a1,但是这样写有点多次一举,因为迭代器it本身就是指向节点的指针,访问数据可以直接使用 ->,例如:it->a1,所以我们还需要将->重载一下

  T* operator->(){//返回数据的地址return &_node->_data;}

结构体A存储在节点的_data中,这里返回了_data的地址,如果按照正常的思路进行访问,应该按照如下的方式:it.operator->()->_a1 应该是两个箭头,第一个箭头代表运算符的重载,第二个代表指针解引用访问数据。
但是编译器为了方便查看会进行优化,将两个箭头变成了一个箭头 it->_a1 ,这样直接可以访问

const迭代器

const的本质就是为了禁止对成员进行修改,所以我们只需要const迭代器只需要对非const迭代器稍加修改即可

    template<class T>struct ListConstIterator{typedef ListNode<T> Node;typedef ListConstIterator<T> Self;Node* _node;ListConstIterator(Node* _node):_node(_node){}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}};

但是这样const迭代器与非const迭代器这两个类的重合度非常高,仅仅是函数返回值前是否加用const修饰的区别,所以我们可以利用模板

    typedef ListIterator<T,T&,T*> iterator;typedef ListIterator<T,const T&,const T*> const_iterator;---------------------------------template<class T,class Ref,class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* _node):_node(_node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(_node);_node = _node->_next;return tmp;}};
完整代码:
#include<iostream>
using namespace std;
namespace zyq
{template<class T>struct ListNode{ListNode* _next;ListNode* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};//template<class T>//struct ListIterator//{//	typedef ListNode<T> Node;//	typedef ListIterator<T> Self;//	Node* _node;//	ListIterator(Node* _node)//		:_node(_node)//	{}//	T& operator*()//	{//		return _node->_data;//	}//     T* operator->()//	{//		return &_node->_data;//	}//	//前置++//	Self& operator++()//	{//		_node = _node->_next;//		return *this;//	}//	Self operator++(int)//	{//		Self tmp(_node);//		_node = _node->_next;//		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)//	{}//	const T& operator*()//	{//		return _node->_data;//	}//	const T* operator->()//	{//		return &_node->_data;//	}//	//前置++//	 Self& operator++()//	{//		_node = _node->_next;//		return *this;//	}//	Self operator++(int)//	{//		Self tmp(_node);//		_node = _node->_next;//		return tmp;//	}//	bool operator!=(const Self& it)//	{//		return !(_node == it._node);//	}//	bool operator==(const Self& it)//	{//		return _node == it._node;//	}//};template<class T,class Ref,class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* _node):_node(_node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(_node);_node = _node->_next;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{public:typedef ListNode<T> Node;//typedef ListIterator<T> iterator;//typedef ListConstIterator<T> const_iterator;typedef ListIterator<T,T&,T*> iterator;typedef ListIterator<T,const T&,const T*> const_iterator;iterator begin(){//隐式类型转换return _head->_next;}iterator end(){//隐式类型转换return _head;}const_iterator begin() const{//隐式类型转换return _head->_next;}const_iterator end() const{//隐式类型转换return _head;}void Empty_Init(){_head = new Node;_head->_next = _head;_head->_prev = _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(){iterator it = begin();while (it != end()){it=erase(it);}delete _head;_head = nullptr;}size_t size(){return _size;}/*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;_size++;}*/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& x){Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;}iterator erase(iterator pos){Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;_size--;return next;}private:Node* _head;size_t _size;};template<class T>void PrintList(const list<T>& clt){typename list<T>::const_iterator it = clt.begin();while (it != clt.end()){cout << *it << " ";it++;}cout << endl;}void testlist1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;/*lt.push_back(9);lt.pop_front();*/PrintList(lt);list<int> lt1(lt);PrintList(lt1);list<int> lt2;lt2 = lt;PrintList(lt2);}struct A{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}};void testlist2(){list<A> lt;lt.push_back({ 1,2 });lt.push_back(A(1,2));list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << " " << (*it)._a2 << " ";cout << it->_a1 << " " << it->_a2 << " ";it++;}cout << endl;}
}

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

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

相关文章

01.MySQL基础知识回顾

MySQL基础知识回顾 1.为什么要使用数据库 问题 如果把数据存储到内存中&#xff0c;那么重启后就消失了&#xff0c;我们希望一个数据被保存后永久存在于一个地方&#xff0c;也就是需要把数据持久化 什么是持久化 把数据保存到可掉电式存储设备中以供之后使用。大多数情况…

NIO与BIO

当谈到 Java 网络编程时&#xff0c;经常会听到两个重要的概念&#xff1a;BIO&#xff08;Blocking I/O&#xff0c;阻塞 I/O&#xff09;和 NIO&#xff08;Non-blocking I/O&#xff0c;非阻塞 I/O&#xff09;。它们都是 Java 中用于处理 I/O 操作的不同编程模型。 一、介…

vue-router v4.x命名路由,编程式跳转

命名路由&#xff0c;编程式跳转 const routes:RouteRecordRaw[] [{path:/,name:A,component: ()> import(../A.vue)},{path:/B,name:B,component: ()> import(../B.vue)}, ]通过name进行跳转 <template><div><router-link :to"{name:A}">…

【postgresql 基础入门】聚合函数,通用型,统计分析型,多种多样的聚合函数满足数据的大数据的统计分析

聚合函数 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 聚合函数一、前…

Linux安装最新版Docker完整教程

参考官网地址&#xff1a;Install Docker Engine on CentOS | Docker Docs 一、安装前准备工作 1.1 查看服务器系统版本以及内核版本 cat /etc/redhat-release1.2 查看服务器内核版本 uname -r这里我们使用的是CentOS 7.6 系统&#xff0c;内核版本为3.10 1.3 安装依赖包 …

MySQL如何创建存储过程

工作中有时候需要自己去创建存储过程&#xff0c;然后调用存储去获得一些数据等&#xff0c;接下来就给大家介绍下MySQL如何创建存储过程。 语法&#xff1a; CREATE PROCEDURE 存储程名([[IN|OUT|INOUT] 参数名 数据类型[,[IN|OUT|INOUT] 参数名 数据类型…]]) [特性 …] 过…

区块链技术与数字身份:解析Web3的身份验证系统

在数字化时代&#xff0c;随着个人数据的日益增多和网络安全的日益关注&#xff0c;传统的身份验证系统面临着越来越多的挑战和限制。在这种背景下&#xff0c;区块链技术的出现为解决这一问题提供了全新的思路和解决方案。Web3作为一个去中心化的互联网模式&#xff0c;其身份…

多卡环境 设置某张卡跑某程序

如果要在使用screen命令时指定CUDA设备&#xff0c;正确的方法是在screen命令之前设置CUDA_VISIBLE_DEVICES环境变量。由于screen会启动一个新的shell会话&#xff0c;直接在命令中设置环境变量可能不会按预期工作。因此&#xff0c;你需要先导出环境变量&#xff0c;然后再启动…

在线JSON工具

功能支持 ctrls json格式化游览器本地保存ctrla ctrlc 自动检测选中范围是否是全选&#xff0c;然后按照格式化方式添加到粘贴板中json 粘贴JSON自动格式化json可视化修改json压缩复制json层级折叠json关键key 搜索(自动提示高亮)满足某些近视的可以自行调整字体大小, 并且会游…

【Spring】SpringBoot整合MybatisPlusGernerator,MybatisPlus逆向工程

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 在我们写项目的时候&#xff0c;我们时常会因为需要创建很多的项目结构而头疼。项目中的表很多的时候&#xff0c;我们连实体类都创建不完&#xff0c;这时候就需要我们的逆向工程来帮助我们生成我们的框架结构。这些结构…

centos安装使用elasticsearch

1.首先可以在 Elasticsearch 官网 Download Elasticsearch | Elastic 下载安装包 2. 在指定的位置(我的是/opt/zhong/)解压安装包 tar -zxvf elasticsearch-7.12.1-linux-x86_64.tar.gz 3.启动es-这种方式启动会将日志全部打印在当前页面&#xff0c;一旦使用 ctrlc退出就会导…

【华为OD机试C++】删除字符串中出现次数最少的字符

《最新华为OD机试题目带答案解析》:最新华为OD机试题目带答案解析,语言包括C、C++、Python、Java、JavaScript等。订阅专栏,获取专栏内所有文章阅读权限,持续同步更新! 文章目录 描述输入描述输出描述示例代码描述 实现删除字符串中出现次数最少的字符,若出现次数最少的字…

自动驾驶硬件系统- Inertial Measurement Unit (IMU)

自动驾驶硬件系统- Inertial Measurement Unit (IMU) 附赠自动驾驶学习资料和量产经验&#xff1a;链接 惯性测量单元(Inertial measurement unit&#xff0c;简称 IMU)&#xff0c;是测量物体三轴姿态角及加速度的装置。一般IMU包括三轴陀螺仪及三轴加速度计&#xff0c;部分…

小米汽车:搅动市场的鲶鱼or价格战砧板上的鱼肉?

3月28日晚&#xff0c;备受关注的小米汽车上市发布会召开&#xff0c;小米集团董事长雷军宣布小米SU7正式发布。小米汽车在带飞股价的同时&#xff0c;二轮订购迅速售尽。 图一&#xff1a;小米集团股价 雷军口中“小米汽车迈出的第一步&#xff0c;也是人生最后一战的开篇”&a…

STC8H8K64U 库函数学习笔记 —— 流水灯

STC8H8K64U 库函数学习笔记 —— 流水灯 环境说明&#xff1a; 芯片&#xff1a;STC8H8K64U 软件&#xff1a; KeilC51 μVersion V5.38.00STCAI-ISP (V6.94) 库文件说明&#xff1a;我将依赖的库文件统一放置到工程下的 lib 目录中&#xff0c;所以&#xff0c;代码中的包含指…

蓝桥杯 经验技巧篇

1. 注意事项 &#x1f468;‍&#x1f3eb; 官方通知 &#x1f468;‍&#x1f3eb; 资料文档 时间&#xff1a;4月13日 9:00~13:00 &#xff08;时长 4小时&#xff09;物品 准考证&#xff08;赛前一周开放下载&#xff0c;自行打印&#xff09;学生证身份证笔、水、外套&a…

知识推理技术解析与实战

目录 一、引言二、知识推理基础知识表示方法本体论语义网络图形数据库 推理机制概述演绎推理归纳推理类比推理 实践代码示例 三、知识推理的核心技术自动推理系统规则引擎推理算法 知识图谱的运用构建知识图谱知识推理与查询 推理算法深度分析转导推理逻辑推理概率推理 实践代码…

关键节点组挖掘(附Python实现)

一、实验内容简介 该实验主要利用基于度的排序和基于投票策略的排序分别挖掘出一组重要节点作为种子节点&#xff0c;然后在给定的网络中传播&#xff0c;一直到稳态&#xff0c;然后统计感染的规模NR。最后通过比较两种方法的感染规模给出相应的评价&#xff0c;给出不同感染…

合资红利耗尽,海外喜忧参半:上汽2023有点“遗憾”

作者 |老缅 编辑 |德新 3月29日&#xff0c;上汽集团发布2023年全年财报。 数据显示&#xff0c;2023年上汽集团实现总营收7447.05亿元&#xff0c;同比增加0.09%&#xff1b;归属于上市公司股东的净利润141.06亿元&#xff0c;同比减少12.48%&#xff1b;基本每股收益1.226元…

【架构艺术】变更元信息分析框架设计

在变更风险防控领域&#xff0c;对于线上变更元信息的分析是非常重要的一部分&#xff0c;这是因为&#xff0c;只有理解了变更元信息&#xff0c;结合自主定制的变更规范&#xff0c;才能够知道具体的变更风险在哪里。不同的变更风险防御能力&#xff0c;实现的思路可能是不同…