list的使用和模拟实现

目录

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 1namespace 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.mzph.cn/news/31789.shtml

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

相关文章

YOLOv5-7.0实例分割+TensorRT部署

一&#xff1a;介绍 将YOLOv5结合分割任务并进行TensorRT部署&#xff0c;是一项既具有挑战性又令人兴奋的任务。分割&#xff08;Segmentation&#xff09;任务要求模型不仅能够检测出目标的存在&#xff0c;还要精确地理解目标的边界和轮廓&#xff0c;为每个像素分配相应的…

Spring Boot配置文件与日志文件

1. Spring Boot 配置文件 我们知道, 当我们创建一个Spring Boot项目之后, 就已经有了配置文件存在于目录结构中. 1. 配置文件作用 整个项目中所有重要的数据都是在配置文件中配置的&#xff0c;比如: 数据库的连接信息 (包含用户名和密码的设置) ;项目的启动端口;第三方系统的调…

KMP字符串 (简单清晰/Java)

Kmp算法 解决问题&#xff1a; 字符串匹配问题 怎么解决&#xff1f; 前缀表next[]数组 #分析 先看暴力做法&#xff1a; 两层for循环&#xff0c;一层遍历文本串&#xff0c;一层遍历模式串&#xff08;子串&#xff09;对应的每个字符进行匹配&#xff0c;匹配成功就 i &a…

如何将jar包部署到宝塔

尝试多种方式上传&#xff0c;但启动一直失败&#xff0c;这种方式亲测是好使的 项目内修改位置 在pom.xml文件中将mysql的scope改成provided&#xff0c;如果是固定的版本号会出现问题 之后就可以打包啦&#xff0c;直接点击maven中的package 找到打包文件的位置&#xff…

免费插件-illustrator-Ai插件-印刷功能-二维码生成

文章目录 1.介绍2.安装3.通过窗口>扩展>知了插件4.功能解释5.示例5.1.QR常用二维码5.2.PDF4175.3.EAN13 6.总结 1.介绍 本文介绍一款免费插件&#xff0c;加强illustrator使用人员工作效率&#xff0c;进行二维码生成。首先从下载网址下载这款插件 https://download.csd…

MySQL之深入InnoDB存储引擎——redo日志

文章目录 一、为什么需要redo日志二、redo日志的类型1&#xff09;简单的redo日志类型2&#xff09;复杂的redo日志类型 三、Mini-Transaction四、redo日志的写入过程五、redo日志文件1、刷盘时机2、redo日志文件组 六、log sequence number1、lsn的引入2、flushed_to_disk_lsn…

java 文件/文件夹复制,添加压缩zip

复制文件夹,并压缩成zip 需求&#xff1a;创建A文件夹&#xff0c;把B文件夹复制到A文件夹。然后把A文件夹压缩成zip包 public static void main(String[] args) throws Exception {try {String A "D:\\dev\\program";String B "D:\\program";// 创建临…

Vue 插槽 slot

solt 插槽需要分为 2.6.0 版本以上和 2.6.0版本以下。 2.6.0 版本以下的 slot 插槽在&#xff0c;2.x版本将继续支持&#xff0c;但是在 Vue 3 中已被废弃&#xff0c;且不会出现在官方文档中。 作用 插槽 prop 允许我们将插槽转换为可复用的模板&#xff0c;这些模板可以基于…

Qt应用开发(基础篇)——LCD数值类 QLCDNumber

一、前言 QLCDNumber类继承于QFrame&#xff0c;QFrame继承于QWidget&#xff0c;是Qt的一个基础小部件。 框架类QFrame介绍 QLCDNumber用来显示一个带有类似lcd数字的数字&#xff0c;适用于信号灯、跑步机、体温计、时钟、电表、水表、血压计等仪器类产品的数值显示。 QLCDNu…

【CSS】文本效果

文本溢出、整字换行、换行规则以及书写模式 代码&#xff1a; <style> p.test1 {white-space: nowrap; width: 200px; border: 1px solid #000000;overflow: hidden;text-overflow: clip; }p.test2 {white-space: nowrap; width: 200px; border: 1px solid #000000;ove…

2023年Q2天猫洗衣机行业品牌销售排行榜(淘宝天猫数据)

洗衣机作为普及率极高的家电之一&#xff0c;如今已经成为我们生活中不可或缺的一部分。由于洗衣机的普及率较高&#xff0c;因此虽其市场规模庞大&#xff0c;但如今要使洗衣机呈现规模化增长的可能性还是比较小的。不过&#xff0c;随着用户需求及产品的升级&#xff0c;洗衣…

Apipost接口测试断言

常用断言直接点右边栏 断言list&#xff1a; // 断言json数组长度 apt.assert(response.json.data.data.length20); // 断言json数组中的某个对象 apt.assert(response.json.data.data[0].docid1482);

EvilBox One靶场笔记

EvilBox: One靶场笔记 信息收集 先fscan找主机192.168.1.102 namp扫端口 开放80,22端口 然后扫目录 └─$ gobuster dir -r -u http://192.168.1.102/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,bak,html在扫secret目录&#xff0c;找…

基于Kubeadm部署k8s集群:下篇

继续上篇内容 目录 7、安装flannel 8、节点管理命令 三、安装Dashboard UI 1、部署Dashboard 2、开放端口设置 3、权限配置 7、安装flannel Master 节点NotReady 的原因就是因为没有使用任何的网络插件&#xff0c;此时Node 和Master的连接还不正常。目前最流行的Kuber…

微服务04-elasticsearch

1、es概念 1.1 文档和字段 elasticsearch是面向**文档(Document)**存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中: 而Json文档中往往包含很多的字段(Field),类似于数据库中的列。 1.2 索引和映射 索引(…

Visual Studio 2019 详细安装教程(图文版)

前言 Visual Studio 2019 安装包的下载教程、安装教程 教程 博主博客链接&#xff1a;https://blog.csdn.net/m0_74014525 关注博主&#xff0c;后期持续更新系列文章 ********文章附有百度网盘安装包链接********* 系列文章 第一篇&#xff1a;Visual Studio 2019 详细安装教…

Spark(39):Streaming DataFrame 和 Streaming DataSet 输出

目录 0. 相关文章链接 1. 输出的选项 2. 输出模式(output mode) 2.1. Append 模式(默认) 2.2. Complete 模式 2.3. Update 模式 2.4. 输出模式总结 3. 输出接收器(output sink) 3.1. file sink 3.2. kafka sink 3.2.1. 以 Streaming 方式输出数据 3.2.2. 以 batch …

树状结构数据,筛选指定数据

问题描述&#xff1a; 应用场景和需求&#xff1a;对一个树状结构的数据&#xff0c;进行CRUD 时&#xff0c;想筛选出 树状结构数据中存在变动的部分。 操作步骤 准备需要的数据&#xff1a; 1.先拿到 你原来的树状结构数据 2.再筛选出 需要保留的数据集合id&#xff0c;也…

开源力量再现,国产操作系统商业化的全新探索

文章目录 1. 开源运动的兴起2. 开源力量的推动3. 国产操作系统的崭露头角3.1 国产操作系统有哪些 4.国产操作系统的商业化探索5.开源力量对国产操作系统商业化的推动 操作系统作为连接硬件、中间件、数据库、应用软件的纽带&#xff0c;被认为是软件技术体系中最核心的基础软件…

Tomcat的动静分离以及多实例部署

一、动静分离 Nginx实现负载均衡的原理&#xff1a; Nginx实现负载均衡是通过反向代理实现Nginx服务器作为前端&#xff0c;Tomcat服务器作为后端&#xff0c;web页面请求由Nginx服务来进行转发。 但不是把所有的web请求转发&#xff0c;而是将静态页面请求Ncinx服务器自己来处…