【C++】STL之list深度剖析及模拟实现

目录

前言

一、list 的使用

 1、构造函数

2、迭代器

3、增删查改

4、其他函数使用

二、list 的模拟实现

 1、节点的创建

 2、push_back 和 push_front

 3、普通迭代器

 4、const 迭代器

 5、增删查改(insert、erase、pop_back、pop_front)

 6、构造函数和析构函数

  6.1、默认构造

  6.2、构造 n 个 val 的对象

  6.3、拷贝构造

  6.4、迭代器区间构造

  6.5、 赋值运算符重载

  6.6、析构函数

三、list 模拟实现源代码

四、list 的迭代器失效

五、list 和 vector的对比


前言

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  3. list 与 forward_list 非常相似:最主要的不同在于 forward_list 是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list 通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问.

一、list 的使用

 1、构造函数

构造函数接口说明
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
int main()
{// 默认构造list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// 拷贝构造list<int> lt2(lt);// 构造 n 个节点list<int> lt3(5, 1);// 迭代器区间构造list<int> lt4(lt.begin(), lt.end());return 0;
}

2、迭代器

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置
int main()
{int a[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt(a, a + 9);auto it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;return 0;
}

迭代器一般是用来遍历和查找的; 

而反向迭代器的使用是类似的,只不过调用的函数换成了 rbegin 和 rend 。

注意:反向迭代器的迭代使用的也是++。但迭代器区间一样是[rbegin, rend);

3、增删查改

函数声明接口说明
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()
{vector<int> v = { 1,2,3,4,5,6,7,8,9 };list<int> lt(v.begin(), v.end());for (auto e : lt) cout << e << " ";cout << endl;lt.push_front(10);lt.push_back(20);for (auto e : lt) cout << e << " ";cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt) cout << e << " ";cout << endl;auto pos = find(lt.begin(), lt.end(), 5);lt.insert(pos, 50);for (auto e : lt) cout << e << " ";cout << endl;pos = find(lt.begin(), lt.end(), 8);lt.erase(pos);for (auto e : lt) cout << e << " ";cout << endl;return 0;
}

4、其他函数使用

函数声明接口说明
empty检测 list 是否为空,是返回 true ,否则返回 false
size返回 list 中有效节点的个数
front返回 list 的第一个节点中值的引用
back返回 list 的最后一个节点中值的引用

二、list 的模拟实现

 1、节点的创建

template<class T>
struct list_node//节点
{list_node<T>* _next;list_node<T>* _prev;T _data;// 构造函数list_node(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}
};

   由于节点存储的数据可能是任意类型,所以我们需要将将节点定义为模板类。这里我们需要写一个给缺省值的默认构造函数,便于之后在主类中new一个新节点时直接初始化,同时将两个指针置为空,将数据写入数据域中。

 2、push_back 和 push_front

class list 
{
public:typedef list_node<T> node;private:node* _head;
}
//尾插
void push_back(const T& x) const
{node* new_node = new node(x);node* tail = _head->_prev;//链接节点之间的关系tail->_next = new_node;new_node->_prev = tail;new_node->_next = _head;_head->_prev = new_node;
}
//头插
void push_front(const T& x)
{node* head = _head->_next;node* new_node = new node(x);_head->_next = new_node;new_node->_prev = _head;new_node->_next = head;head->_prev = new_node;
}

 这里模拟的头插和尾插也很简单,因为和我们之前在数据结构时候的双向循环链表是一样的,只需要找到头或者尾,然后链接四个节点间的关系即可。

 3、普通迭代器

注意:list 的迭代器是自定义类型,不是原生指针node*。

迭代器为自定义类型,其中*,++等都是通过运算符重载来完成的。

所以我们需要重载的符号:*,->,前置++,后置++,前置--,后置--,!=,==

template<class T>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T> self;node* _node;//构造函数__list_iterator(node* n):_node(n){}//重载*运算符T& operator*(){return _node->_val;}T* operator->(){return &_node->_data;}//重载前置++运算符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& s){return _node != s._node;}//重载==运算符bool operator==(const self& s){return _node == s._node;}
};

 此处我实现了一个简单的正向迭代器,使用一个模板参数T表示类型。

 当普通迭代器封装好了之后,我们需要在list类中来实现它的 begin() 和 end() 方法。由于迭代器的名字一般都是 iterator,而且对于范围for来说,也只能通过 iterator 来转换为迭代器进行遍历。所以这里我们将其typedef为iterator。

template<class T>
class list//链表
{typedef list_node<T> node;
public:typedef __list_iterator<T> iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}
private:node* _head;
};

 4、const 迭代器

  const迭代器与普通迭代器的区别在于const迭代器指向的内容是不能修改的,但是它的指向是可以修改的。

template<class T>
class list//链表
{typedef list_node<T> node;
public:typedef __list_const_iterator<T> const_iterator;const_iterator begin(){return const_iterator(_head->_next);}const_iterator end(){return const_iterator(_head);}
private:node* _head;
};

  我们最好的做法就是在__list_iterator 的类模板中的添加两个模板参数,然后再 list 类中 typedef 两份分别将第二个参数分别改成 T& 和 const T& 的类型,本质上就是让编译器根据传入的 Ref 的不同来自动示例化出 const 迭代器类,而我们还需要重载一个->运算符,因为list中可能存储的是自定义类型,这个自定义类型如果要是有多个成员变量的话,我们就需要使用->来解引用访问成员变量,同样还是要区分普通迭代器和const 迭代器,所以就增加了另一个模版参数 Ptr。具体的解决做法如下:

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* n):_node(n){}Ref operator*()//解引用{return _node->_data;}Ptr operator->(){return &_node->_data;}...
};

然后,最终在链表类中使用如下:

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;//const迭代器iterator begin(){return iterator(_head->_next);//匿名对象的返回}const_iterator begin() const{return const_iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator end() const{return const_iterator(_head);}
private:node* _head;
};

 5、增删查改(insert、erase、pop_back、pop_front)

// 指定位置插入
void insert(iterator pos, const T& x)
{node* cur = pos._node;node* prev = cur->_prev;node* new_node = new node(x);prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;
}
// 指定位置删除
iterator erase(iterator pos)
{assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);
}
// 尾删
void pop_back()
{erase(--end());
}
// 头删
void pop_front()
{erase(begin());
}

 6、构造函数和析构函数

  6.1、默认构造

  由于后面会频繁对空进行初始化,所以在这里对它进行了封装,方便后面的调用。

void empty_init()//空初始化
{_head = new node;_head->_next = _head;_head->_prev = _head;
}
list()
{empty_init();
}

  6.2、构造 n 个 val 的对象

//用n个val构造对象
list(int n, const T& val = T())
{empty_init();for (int i = 0; i < n; i++){push_back(val);}
}

  6.3、拷贝构造

//拷贝构造传统写法
list(const list<T>& lt)
{empty_init();for (auto e : lt){push_back(e);}
}
//拷贝构造现代写法
list(const list<T>& lt)
{empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);
}

  6.4、迭代器区间构造

template <class Iterator>
list(Iterator first, Iterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}

  6.5、 赋值运算符重载

//赋值运算符重载
list<T>& operator=(list<T> lt)//注意这里不能用引用
{swap(lt);return *this;
}

  6.6、析构函数

//要全部清理掉
~list()
{clear();delete _head;_head = nullptr;
}
//不释放头结点
void clear()
{iterator it = begin();while (it != end()){it = erase(it);//这样也可以//erase(it++);}
}

三、list 模拟实现源代码

template<class T>
struct list_node//节点
{list_node<T>* _next;list_node<T>* _prev;T _data;list_node(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}
};
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* n):_node(n){}Ref operator*()//解引用{return _node->_data;}Ptr operator->(){return &_node->_data;}//前置++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& s){return _node != s._node;}bool operator==(const self& 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;//const迭代器iterator begin(){return iterator(_head->_next);//匿名对象的返回}const_iterator begin() const{return const_iterator(_head->_next);}iterator end(){return iterator(_head);}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);//push_back使用的前提是要有哨兵位的头结点++first;}}// 交换函数void swap(list<T>& tmp){std::swap(_head, tmp._head);}//现代拷贝构造list(const list<T>& lt){list<T> tmp(lt.begin(), lt.end());swap(tmp);}//现代赋值写法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);//这样也可以//erase(it++);}}void insert(iterator pos, const T& x){node* cur = pos._node;node* prev = cur->_prev;node* new_node = new node(x);prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;}iterator erase(iterator pos){assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);}//尾插void push_back(const T& x) const{//node* new_node = new node(x);//node* tail = _head->_prev;链接节点之间的关系//tail->_next = new_node;//new_node->_prev = tail;//new_node->_next = _head;//_head->_prev = new_node;insert(end(), x);}//头插void push_front(const T& x){//node* head = _head->_next;//node* new_node = new node(x);//_head->_next = new_node;//new_node->_prev = _head;//new_node->_next = head;//head->_prev = new_node;insert(begin(), x);}//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}
private:node* _head;
};

四、list 的迭代器失效

  当我们使用 erase 进行删除后,此时指向删除位置的迭代器就失效了,再次使用就会令程序崩溃。

  因此若要多次删除,则需要在使用后利用 erase 的返回值更新迭代器,这样使用才不会出现错误。

int main()
{vector<int> v = { 1, 2,3,5,4,6 };list<int> lt(v.begin(), v.end());list<int>::iterator pos = find(lt.begin(), lt.end(), 3);for (int i = 0; i < 3; i++){pos = lt.erase(pos);   //利用erase的返回值更新迭代器}for (auto e : lt) cout << e << " ";cout << endl;return 0;
}

五、list 和 vector的对比

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


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

 

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

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

相关文章

CSS box-shadow阴影

1、语法 box-shadow: h-shadow v-shadow blur spread color inset; 值描述h-shadow必需的。水平阴影的位置。允许负值v-shadow必需的。垂直阴影的位置。允许负值blur可选。模糊距离spread可选。阴影的大小color可选。阴影的颜色。在CSS颜色值寻找颜色值的完整列表inset可选。…

【Linux】Linux远程访问Windows下的MySQL数据库

1.建立Windows防火墙规则 首先需要开放windows防火墙&#xff0c;针对3306端口单独创建一条规则&#xff0c;允许访问。 打开windows安全中心防火墙与保护&#xff0c;点击高级设置 进入之后&#xff0c;点击入站规则&#xff0c;新建一条规则 新建端口入站规则 端口填写330…

使用vue-cli搭建SPA项目

目录 引言 什么是SPA&#xff1f; Vue CLI 是什么&#xff1f; 步骤1&#xff1a;安装 Vue CLI 为什么选择 Vue CLI 作为项目搭建工具 安装vue-cli 步骤2&#xff1a;创建新的 Vue 项目 创建成功后的项目结构 步骤3&#xff1a;项目结构概述 vue项目结构说明 步骤4&a…

Vue中前端导出word文件

很多时候在工作中会碰到完全由前端导出word文件的需求&#xff0c;因此特地记录一下比较常用的几种方式。 一、提供一个word模板 该方法提供一个word模板文件&#xff0c;数据通过参数替换的方式传入word文件中&#xff0c;灵活性较差&#xff0c;适用于简单的文件导出。需要…

论文笔记(整理):轨迹相似度顶会论文中使用的数据集

0 汇总 数据类型数据名称数据处理出租车数据波尔图 原始数据&#xff1a;2013年7月到2014年6月&#xff0c;170万条数据 ICDE 2023 Contrastive Trajectory Similarity Learning with Dual-Feature Attention 过滤位于城市&#xff08;或国家&#xff09;区域之外的轨迹 过…

Idea引入thymeleaf失败解决方法

报错 Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.Fri Sep 29 09:42:00 CST 2023 There was an unexpected error (typeNot Found, status404). 原因&#xff1a;html没有使用thymeleaf 首先要引入…

Linux Day18 TCP_UDP协议及相关知识

一、网络基础概念 1.1 网络 网络是由若干结点和连接这些结点的链路组成&#xff0c;网络中的结点可以是计算机&#xff0c;交换机、 路由器等设备。 1.2 互联网 把多个网络连接起来就构成了互联网。目前最大的互联网就是因特网。 网络设备有&#xff1a;交换机、路由器、…

【MATLAB源码-第38期】基于OFDM的块状导频和梳状导频误码率性能对比,不同信道估计方法以及不同调制方式对比。

1、算法描述 块状导频和梳状导频都是用于无线通信系统中信道估计的方法。 块状导频&#xff1a; 定义&#xff1a; 在频域上&#xff0c;块状导频是连续放置的一组导频符号。这意味着所有的导频符号都集中在一个短的时间段内发送。 优点&#xff1a; 对于时间选择性信道&#…

Python 打印素数

"""打印素数介绍&#xff1a;素数是指只有两个正因数&#xff08;1和它本身&#xff09;的自然数&#xff0c;而且必须大于1。例如&#xff1a;2、3、5、7、11、13、17、19、23、29等等都是素数。小于2的数不是素数&#xff0c;因为它没有两个正因数。例如&…

基于JAVA+SpringBoot的新闻发布平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着科技的飞速发展和…

IO流 之 缓冲流(字节缓冲流和字符缓冲流)

缓冲流对原始流进行了包装&#xff0c;以提高原始流读写数据的性能。 字节缓冲流&#xff08;BufferedInputStream和BufferedOutputStream&#xff09; 字节缓冲流在内存中提供了一个默认为8kb的区域&#xff0c;用于缓冲&#xff0c;当流开始时&#xff0c;先读取一个8kb的内…

skywalking入门

参考&#xff1a; https://www.jianshu.com/p/ffa7ddcda4ab 参考&#xff1a; https://developer.aliyun.com/article/1201085 skywalking&#xff08;APM&#xff09; 调用链路分析以及应用监控分析工具 Skywalking主要由三大部分组成&#xff1a;agent、collector、webapp-…

十六.镜头知识之工业镜头的质量判断因素

十六.镜头知识之工业镜头的质量判断因素 文章目录 十六.镜头知识之工业镜头的质量判断因素1.分辨率(Resolution)2.明锐度(Acutance)3.景深(DOF)&#xff1a;4. 最大相对孔径与光圈系数5.工业镜头各参数间的相互影响关系5.1.焦距大小的影响情况5.2.光圈大小的影响情况5.3.像场中…

CISSP学习笔记:人员安全和风险管理概念

第二章 人员安全和风险管理概念 2.1 促进人员安全策略 职责分离: 把关键的、重要的和敏感工作任务分配给若干不同的管理员或高级执行者&#xff0c;防止共谋工作职责:最小特权原则岗位轮换:提供知识冗余&#xff0c;减少伪造、数据更改、偷窃、阴谋破坏和信息滥用的风险&…

快速幂矩阵-python

看了大神讲解&#xff0c;理论在这里&#xff1a;快速幂算法&#xff08;全网最详细地带你从零开始一步一步优化&#xff09;-CSDN博客 例题&#xff1a;求整数 base 的 整数 power 次方&#xff0c;对整数 num_mod 取幂。 python 代码如下&#xff1a; import timedef norm…

LabVIEW在运行时调整表控件列宽

LabVIEW在运行时调整表控件列宽 如何在LabIEW中运行时调整表控件的列宽大小&#xff1f; 在VI运行时&#xff0c;有两种不同的方法可以更改表中列的宽度。首先&#xff0c;可以使用鼠标手动更改它们;其次&#xff0c;可以从框图中以编程方式更改它们。 手动更改列宽 只有在…

IPsec_SSL VPN身份鉴别过程简要

一、IPsec VPN身份鉴别&#xff08;参考国密标准《GMT 0022-2014 IPsec VPN技术规范》&#xff09; IKE第一阶段&#xff08;主模式&#xff09; “消息2”由响应方发出&#xff0c;消息中具体包含一个SA载荷&#xff08;确认所接受的SA提议&#xff09;、响应方的签名证书和…

基于AI图像识别的智能缺陷检测系统,在钢铁行业的应用-技术方案

目录 概述 废钢智能检判方案简介 废钢智能检判系统优势及价值 废钢人工检判过程 废钢等级检判标准 废钢检判结果 智能检判方案-废钢智能检判算法 算法一&#xff1a;废钢等级识别算法 算法二&#xff1a;不合格料的位置识别算法 算法三&#xff1a;不合格料的类型识别…

【再识C进阶3(下)】详细地认识字符分类函数,字符转换函数和内存函数

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

全网最全面最精华的设计模式讲解,从程序员转变为工程师的第一步

前言 现代社会&#xff0c;技术日新月异&#xff0c;要想跟上技术的更新就必须不断学习&#xff0c;而学习技术最有效方式就是阅读优秀的源码&#xff0c;而优秀的源码都不是简单的逻辑堆积&#xff0c;而是有很灵活的设计模式应用其中&#xff0c;如果我们不懂设计模式&#…