【C++】list的模拟实现及其应用

news/2024/5/3 1:24:29/文章来源:https://blog.csdn.net/m0_66599463/article/details/129972676

文章目录

  • list的相关介绍
  • list的使用
      • list构造
      • list iterator的使用
      • list capacity
      • list element access
      • list modifiers
      • list迭代器失效
  • sort问题
  • list模拟实现的完整代码
  • list与vector的对比

list的相关介绍

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

list的使用

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

构造函数的基本使用

#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt1;list<int> lt2(5, 100);list<int> lt3(lt2.begin(), lt2.end());list<int> lt4(lt2);for (auto ch : lt2){cout << ch << " ";}cout << endl;for (auto ch : lt3){cout << ch << " ";}cout << endl;for (auto ch : lt4){cout << ch << " ";}cout << endl;return 0;
}

在这里插入图片描述

构造函数的模拟实现
简单介绍一下下面的代码,empty_init是创造个头节点(哨兵位),因为每个构造函数都要注意头结点的问题,所以为了方便写个函数。

void empty_init()
{_head = new node;_head->_next = _head;_head->_prev = _head;
}list()//默认构造
{empty_init();
}
//传区间构造
template <class Iterator>
list(Iterator first, Iterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}// lt2(lt1)
/*list(const list<T>& lt)
{empty_init();for (auto e : lt){push_back(e);}
}*/
//现代的构造函数写法
void swap(list<T>& tmp)
{std::swap(_head, tmp._head);
}
//这里传引用并不会出现问题,因为下面的区间构造是值拷贝
list(const list<T>& lt)
{empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);
}

list iterator的使用

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

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

迭代器的相关使用

int main()
{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();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;list<int>::reverse_iterator rit = lt.rbegin();while (rit != lt.rend()){cout << *rit << " ";rit++;}cout << endl;return 0;
}

在这里插入图片描述

list迭代器的模拟实现
这里是将迭代器做了个封装,因为list的每个节点的地址并不一定是是连续的(大概率是不连续的)所以对list++,–等操作实现不了。对它进行封装就能实现了。
【注意】

  1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
//typedef __list_iterator<T, T&, T*> iterator;
//typedef __list_iterator<T, const T&, const T*> const_iterator;
//模板有三个参数,看着很麻烦,但是对代码简洁方便起了很大作用。
//T是list的储存类型,Ref是控制节点的值是否能修改,Ptr控制返回的地址是否能修改
template<class T, class Ref, class Ptr>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> iterator;node* _node;//成员变量//构造函数将节点初始化__list_iterator(node* n):_node(n){}Ref operator*(){return _node->_date;}//->最后返回的是date的地址Ptr operator->(){return &_node->_date;}//前置加加iterator& operator++(){_node = _node->_next;return *this;}iterator operator++(int)//后置加加标志{iterator tmp(*this);_node = _node->_next;return tmp;}//前置减减iterator& operator--(){_node = _node->_prev;return *this;}iterator operator--(int)//后置减减标志{iterator tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const iterator& s){return _node != s._node;}bool operator==(const iterator& s){return _node == s._node;}
};

下面是对操作符重载->,进行的应用

struct custom
{int a;int b;custom(int _a = 0, int _b = 0):a(_a), b(_b){}
};
void print_list(const list<custom>& lt)
{list<custom>::const_iterator it = lt.begin();while (it != lt.end()){//也可以写成(*it).a //这里本应该是it->->a,为了增强可读性,变成了一个。cout << it->a << ":" << it->b << endl;//本体是it.operator->()->a//it.operator->()是等于custom*//it.operator++(0)是一个意思++it;}cout << endl;
}

list capacity

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数
int main()
{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> lt1;cout << lt.size()<<endl;cout << lt1.size() << endl;cout << lt.empty() << endl;cout << lt1.empty() << endl;return 0;
}

在这里插入图片描述

list element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用
int main()
{std::list<int> mylist;mylist.push_back(77);mylist.push_back(22);// now front equals 77, and back 22mylist.front() -= mylist.back();std::cout << "mylist.front() is now " << mylist.front() << '\n';return 0;
}

在这里插入图片描述

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中的有效元素
int main()
{list<int> lt;lt.push_back(1);lt.push_back(1);lt.push_back(1);lt.push_back(1);lt.push_front(10);lt.push_back(100);for (auto ch : lt){cout << ch << " ";}cout << endl;lt.pop_back();lt.pop_front();for (auto ch : lt){cout << ch << " ";}cout << endl;lt.insert(++lt.begin(),100);lt.erase(--lt.end());for (auto ch : lt){cout << ch << " ";}cout << endl;list<int> lt1(3, 100);lt.swap(lt1);for (auto ch : lt){cout << ch << " ";}cout << endl;lt.clear();return 0;
}

在这里插入图片描述

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

sort问题

这里问题就是std中已经有了sort,为什么list中又写了一个sort。
理论而言,模板语法上可以传任意类型参数,但是内部使用迭代器对迭代器有要求
list的迭代器是双向迭代器,而std中的迭代器是随机迭代器,
在随机迭代器当中使用的是快排,源码中使用了减法,这是双向迭代器没有的,
所以也就导致list当中有自己的sort。

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

在这里举个例子,下面是双向迭代器,可以使用,双向迭代器或者随机迭代器,这里随机迭代器是一个特殊的双向迭代器,
如果它是个随即迭代器就只能使用随机迭代器。
单向迭代器,就是三种迭代器都可以使用。
在这里插入图片描述
但是list中的sort并不常用,原因是它的效率不高,想要追求效率高,就把list到中的数据拷贝到vector当中,排完序在拷贝回来。这样的效率会有很大的提升。

    for (auto e : lt1){v.push_back(e);}sort(v.begin(), v.end());size_t i = 0;for (auto& e : lt1){e = v[i++];}

list模拟实现的完整代码

#pragma once
#include<iostream>
using namespace std;
namespace zzm
{template<class T>struct list_node{list_node* _prev;list_node* _next;T _date;list_node(const T& x = T()):_prev(nullptr), _next(nullptr), _date(x){}};template<class T, class Ref,class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref,Ptr> iterator;node* _node;__list_iterator(node* n):_node(n){}Ref operator*(){return _node->_date;}Ptr operator->(){return &_node->_date;}iterator& operator++(){_node = _node->_next;return *this;}iterator operator++(int)//后置加加标志{iterator tmp(*this);_node = _node->_next;return tmp;}iterator& operator--(){_node = _node->_prev;return *this;}iterator operator--(int)//后置减减标志{iterator tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const iterator& s){return _node != s._node;}bool operator==(const iterator& s){return _node == s._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 const iterator const>iterator;//是行不通的,T*const  ×//应该是const T*       √/*list(){_head = new node;_head->_next = _head;_head->_prev = _head;}*/iterator begin(){//iterator it(_head->_next);//return it;return iterator(_head->_next);//匿名构造,生命周期只有这一行}iterator end(){return iterator(_head);}const_iterator begin() const{//iterator it(_head->_next);//return it;return const_iterator(_head->_next);//匿名构造,生命周期只有这一行}const_iterator end() const{return const_iterator(_head);}void empty_init(){_head = new node;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}template <class Iterator>list(Iterator first, Iterator last){empty_init();while (first != last){push_back(*first);++first;}}// lt2(lt1)/*list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}*/void swap(list<T>& tmp){std::swap(_head, tmp._head);}list(const list<T>& lt){empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}// lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}void push_back(const T& a){/*node* tail = _head->_prev;node* new_node = new node(a);_head->_prev = new_node;tail->_next = new_node;new_node->_next = _head;new_node->_prev = tail;*/insert(end(),a);}void push_front(const T& a){insert(begin(), a);}void insert(iterator pos,const T&x){node* cur = pos._node;node* drev = cur->_prev;node* new_node = new node(x);new_node->_next = cur;new_node->_prev = drev;drev->_next = new_node;cur->_prev = new_node;}void erase(iterator pos){node* next = pos._node->_next;node* prev = pos._node->_prev;next->_prev = prev;prev->_next = next;delete pos._node;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}~list(){clear();delete _head;_head = nullptr;}//头节点是不清的void clear(){iterator it = begin();while (it != end()){//it = erase(it);erase(it++);//删除的是临时拷贝}}private:node* _head;};void print_list(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){//(*it) *= 2;cout << *it << " ";++it;}cout << endl;}
}

list与vector的对比

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

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

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

相关文章

[linux kernel]slub内存管理分析(5) kfree

文章目录背景省流前情回顾描述方法约定kfree 操作总览简介逻辑图预览释放逻辑slab page各个状态转化调用栈详细分析kfreeslab_free__slab_freeput_cpu_partialunfreeze_partialsdiscard_slab->free_slab内存释放逻辑总结slab page状态转换关系图背景 省流 如果对代码细节不…

21.SSM框架-SpringMVC

目录 一、SpringMVC。 &#xff08;1&#xff09;SpringMVC快速入门。 &#xff08;2&#xff09;SpringMVC的数据响应方式。 &#xff08;1&#xff09;页面跳转。 &#xff08;2&#xff09;回写数据。 &#xff08;3&#xff09;获取请求参数。 &#xff08;4&#xf…

SpringSecurity实战解析

文章目录一、Security认证和原理1、认证基本流程1.1 表单认证概述1.2 基本流程分析1.3 权限访问流程2、请求间共享认证信息2.1 概述2.2 获取认证用户信息3、认证的几种方式4、注解权限4.1 概述4.2 Secured注解使用方式4.3 jsr250Enabled4.4 prePostEnabled 规范(重要)5、自定义…

【数据结构初阶】二叉树OJ题

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;数据结构初阶 ⭐代码仓库&#xff1a;Data Structure 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff…

Flume笔记

Flume 概念 高可用、高可靠&#xff0c;分布式海量日志采集、聚合和传输的系统。 主要作用&#xff1a;实时读取服务器本地磁盘的数据&#xff0c;将数据写入到HDFS 组成 Agent&#xff0c;JVM进程&#xff0c;以事件的形式将数据从源头送到目的地 Agent分为Source、Chann…

李宏毅2021春季机器学习课程视频笔记5-模型训练不起来问题(当梯度很小的时候问题)

求解最小Loss的失败&#xff0c;不能得到最优的值&#xff0c;找不到Loss足够小的值。 1.Loss关于参数的梯度为0&#xff0c;不能继续更新参数。&#xff08;local minima 或者 saddle point&#xff09;如何知道走到了哪个点&#xff1f; 利用泰勒展开&#xff1a; Critical P…

免费ChatGPT接入-国内怎么玩chatGPT

免费ChatGPT中文版 OpenAI 的 GPT 模型目前并不提供中文版的免费使用&#xff0c;但是有许多机器学习平台和第三方服务提供商也提供了基于 GPT 技术的中文版模型和 API。下面是一些常见的免费中文版 ChatGPT&#xff1a; Hugging Face&#xff1a;Hugging Face 是一个开源社区…

Mysql主备一致性保证

大家知道 bin log 既可以用来归档&#xff0c;又可以用来做主备同步。有人可能会问&#xff0c;为什么备库执行了 bin log 就可以跟主库保持一致了呢&#xff1f;bin log的内容是什么样的呢&#xff1f;今天我们就来聊聊它。 在最开始&#xff0c;Mysql 是以容易学习和方便的高…

JDK1.8下载与安装完整教程

目录 一、获取安装资源 1、百度网盘共享 2、官方网站下载(百度网盘文件下载下来有问题情况下) 2.1、搜索jdk官方网站 2.2、进到官网下拉找到Java8&#xff0c;选择Windows 2.3、下载安装程序(下载要登录&#xff0c;没有账号就注册就行) 二、正式安装 1、先在D盘(不在C…

【模型复现】Network in Network,将1*1卷积引入网络设计,运用全局平均池化替代全连接层。模块化设计网络

《Network In Network》是一篇比较老的文章了&#xff08;2014年ICLR的一篇paper&#xff09;&#xff0c;是当时比较厉害的一篇论文&#xff0c;同时在现在看来也是一篇非常经典并且影响深远的论文&#xff0c;后续很多创新都有这篇文章的影子。[1312.4400] Network In Networ…

蓝桥杯刷题冲刺 | 倒计时1天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;蓝桥杯加油&#xff0c;大家一定可以&#x1f43e; 文章目录我是菜菜&#xff0c;最近容易我犯的错误总结 一些tips 各位蓝桥杯加油加油 当输入输出数据不超过 1e6 时&#xff0c;scanf printf 和…

elasticsearch基础6——head插件安装和web页面查询操作使用、ik分词器

文章目录一、基本了解1.1 插件分类1.2 插件管理命令二、分析插件2.1 es中的分析插件2.1.1 官方核心分析插件2.1.2 社区提供分析插件2.2 API扩展插件三、Head 插件3.1 安装3.2 web页面使用3.2.1 概览页3.2.1.1 unassigned问题解决3.2.2 索引页3.2.3 数据浏览页3.2.4 基本查询页3…

微服务+springcloud+springcloud alibaba学习笔记(1/9)

1.微服务简介 什么是微服务呢&#xff1f; 就是将一个大的应用&#xff0c;拆分成多个小的模块&#xff0c;每个模块都有自己的功能和职责&#xff0c;每个模块可以 进行交互&#xff0c;这就是微服务 简而言之&#xff0c;微服务架构的风格&#xff0c;就是将单一程序开发成…

项目管理案例分析有哪些?

项目管控中遇到的问题有哪些&#xff1f;这些问题是如何解决的&#xff1f; 在项目管理领域&#xff0c;案例分析是一种常见的方法来学习和理解项目管理实践&#xff0c;下面就来介绍几个成功案例&#xff0c;希望能给大家带来一些参考。 1、第六空间&#xff1a;快速响应个性…

1669_MIT 6.828 xv6代码的获取以及编译启动

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 6.828的学习的资料从开始基本信息的讲解&#xff0c;逐步往unix的一个特殊版本xv6过度了。这样&#xff0c;先得熟悉一下这个OS的基本代码以及环境。 在课程中其实…

最短路径算法及Python实现

最短路径问题 在图论中&#xff0c;最短路径问题是指在一个有向或无向的加权图中找到从一个起点到一个终点的最短路径。这个问题是计算机科学中的一个经典问题&#xff0c;也是许多实际问题的基础&#xff0c;例如路线规划、通信网络设计和交通流量优化等。在这个问题中&#…

Downloader工具配置参数并烧录到flash中

1 Downloader工具介绍 Downloader工具可以用来烧录固件到设备中&#xff0c;固件格式默认为*dcf。该工具还可以用来在线调试EQ或者进行系统设置。 2 配置参数 2.1 作用 当有一个dcf文件时&#xff0c;配合不同的配置文件*.setting&#xff0c;在不进行编译的情况下&#xff…

【毕业设计】ESP32通过MQTT协议连接服务器(二)

文章目录0 前期教程1 前言2 配置SSL证书3 配置用户名和密码4 配置客户端id&#xff08;client_id&#xff09;5 conf文件理解6 websocket配置7 其他资料0 前期教程 【毕业设计】ESP32通过MQTT协议连接服务器&#xff08;一&#xff09; 1 前言 上一篇教程简单讲述了怎么在虚拟…

【调试】ftrace(三)trace-cmd和kernelshark

之前使用ftrace的时候需要一系列的配置&#xff0c;使用起来有点繁琐&#xff0c;这里推荐一个ftrace的一个前端工具&#xff0c;它就是trace-cmd trace-cmd安装教程 安装trace-cmd及其依赖库 git clone https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git/ c…

【Ruby学习笔记】19.Ruby 连接 Mysql - MySql2

Ruby 连接 Mysql - MySql2 前面一章节我们介绍了 Ruby DBI 的使用。这章节我们技术 Ruby 连接 Mysql 更高效的驱动 mysql2&#xff0c;目前也推荐使用这种方式连接 MySql。 安装 mysql2 驱动&#xff1a; gem install mysql2你需要使用 –with-mysql-config 配置 mysql_conf…