STL_list文档使用介绍与底层代码实现简介

在这里插入图片描述

文章目录

    • list介绍
    • list的使用
      • 构造函数(constructor)
      • 迭代器
      • list capacity
      • list modify(修改)
      • 其他接口函数
      • list迭代器失效问题
    • list实现
      • 基础框架(节点类)
      • 基础框架(迭代器类)
      • 基础框架(链表主体类)
      • 链表主体函数

二次修改于date:2024:3:20

list介绍

在STL中list底层使用的是双向带头循环链表,这是一个很完善的结构,可以做到在O(1)时间内完成pos位置的插入删除。
唯一的缺点是不支持随机访问,如果要访问list中的第三个元素,只能通过遍历迭代器或者范围for来访问第三个元素。
所以list不支持算法库里面的sort(因为算法库中的sort底层是快速排序,快速排序为了防止最坏的情况也就是已排序好的数据,在这种情况下的效率就是O(N^2)因此引入了三数取中,但是如果不支持随机访问,三数取中就不可以使用了)
要想对sort进行排序只能使用list自带的sort来进行排序,但是list也很少排序,如果是需要排序的数据一般是用顺序表来存储。其次list内置sort效率很低,数据量大的时候甚至不如先将数据拷贝到vector中排序完再拷贝回来。

这是list的双向带头循环链表的一个逻辑结构图
image.png

常见的迭代器类型
单向---->只能++,例如:forword_list和unorder_map
双向---->可++和–,例如:list
随机访问---->可以++/–/+/-/+=/-=等任意方式访问,有vector和string
在文档中根据迭代器名称可以判断出,当前函数支持什么类型的迭代器,这几个迭代器从上到下满足继承关系,单向的迭代器可以调用的函数双向的迭代器一定也可以,但是双向可以调用的函数单向的不一定能调用。

list的使用

构造函数(constructor)

image.png
C++这里提供了四种构造函数
1.不传参数构造空链表(也就是只有一个头结点)alloc是空间配置器,也就是内存池
2.使用n个val来构造链表
3.使用了一个迭代器区间来构造(左闭右开),可以是其他容器的迭代器来构造链表
4.拷贝构造

#include<iostream>
#include<list>
#include<string>using namespace std;template<class T>
void Print(T& tmp)
{typename T::iterator it = tmp.begin();while (it != tmp.end()){cout << *it << " ";it++;}cout << endl;
}
int main()
{list<int> l1;list<int> l2(10, 6);string s("hello kisskernel");list<char> l3(s.begin(), s.end());Print(l1);Print(l2);Print(l3);return 0;
}

这里实现了一个可以打印任意类型容器的模板打印函数,在访问模板内的内嵌类型的时候,如果不加typename就会报错,因为模板在没有实例化之前是不能取模板的内嵌类型的,这时候编译器根本就不认识T,也不知道T是什么类型,取内嵌的iterator就会报错。所以前面加上了typename就是告诉编译器这个参数是个模板类型,等实例化之后再去里面取内嵌类型。

迭代器

list的迭代器和其他容器一样的不多赘述。
虽然迭代器的使用类似指针的操作,在前面的vector和string中迭代器也确实就是指针,但是list迭代器如果单单是指针的话,不能满足list这里的迭代器要求,比如it++如果时原生指针就时地址++,并不能跳到下一个节点。
所以list的迭代器封装了节点的指针,然后通过重载运算符来完成迭代器的操作,包括后面的map和set也是类似的。

迭代器的价值:

  1. 封装底层实现,不暴露底层细节
  2. 统一访问方式,减少学习成本

算法中的函数模板理论上都是通用的,传什么迭代器都是可以的,但是因为算法中可能用到了不同的迭代器操作,有一些迭代器可能不支持这些操作,比如sort需随机访问,list的iterator是不支持的。

注意:begin的位置就是第一个节点的位置,end的位置是最后一个节点的下一个位置,也就是头结点(哨兵位)的位置。
结构如下:

image.png
begin是正向迭代器的开始,++向着逆时针移动。
rbegin是反向迭代器的开始,++向着顺时针方向移动
反向迭代器:关于反向迭代器,这里为了让end和rbegin保持一致,所以他们都是指向头结点的,但是rbegin解引用拿到的是最后一个节点4,这是因为反向迭代器的实现重载*的时候是返回的前一个位置的值。所以rend解引用拿到的就是头结点(这里会报错,这也证明了拿到的确实就是头节点)。

list capacity

image.png
1.判断链表是否为空(用迭代器begin和end判断是不是相等的就可以了,相等就是空)
2.返回链表的元素个数(可以遍历链表来计数,也可在成员中加一个_size来记录,插入就++,删除就–)

list modify(修改)

image.png
修改函数只需要了解框选出来的即可。
1.push_front头插
2.popfront头删
3.push_back尾插
4.pop_back尾删
5.insert随机插入(在pos位置之前插入)
image.png
返回值是新插入的元素的第一个位置(如果只插入一个元素,就返回这个插入的元素的位置)。
6.erase删除
image.png
返回的迭代器是最后一个被删除的位置的下一个位置的迭代器。如果已将容器最后一个元素删除那么返回的就是end();
7.swap交换
8.clear清空链表

其他接口函数

image.png
assign就是将对象重新赋值,第一个就是将容器内的数据清空然后将这个迭代器区间内的数据插入进去,第二个就是清空然后插入n个val

image.png
splice的意思就是拼接结合的意思。将一个容器的元素接合到另一个容器中。
1.将x容器内的元素全部接合到调用容器的position位置。
2.将x容器内的i之后的元素接合到pos位置。
3.将x容器[ first,last)的这个区间接合到pos位置。

list迭代器失效问题

list底层是双向带头循环链表,使用迭代器插入一个节点并不会导致迭代器失效,因为插入的节点不会导致其他节点的物理地址的变换。使用erase删除一个节点同样也不会导致迭代器失效,只会导致删除的这个节点失效。

list实现

需要了解list容器总共有几部分组成,list内成员,就是一个指针,指向双向带头循环链表的头节点。然后将节点封装成一个类。vector和string的迭代器都可以使用原生指针来实现,因为他们的存储空间是连续的,++ 或者-- 后就能找到附近的元素,但是list的存储空间是不连续的。

所以list的原生指针行为不能满足list对于迭代器的定义,就需要通过类来封装原生指针,重载运算符来实现迭代器功能。这里一共需要三个类,list,_list_node , _list_iterator。
正因为使用类封装迭代器重载了运算符,所以在使用的时候可以不需要关注容器底层到底是数组,链表或者是树形结构,都可以使用简单统一的方法来访问修改容器,这其实就是迭代器的意义。

基础框架(节点类)

	//封装节点类template<class T>struct _list_node{typedef _list_node<T> node;_list_node(const T& val = T()):_val(val),_next(nullptr),_prev(nullptr){}T _val;_list_node* _next;_list_node* _prev;};

基础框架(迭代器类)

//封装迭代器类template<class T,class Ref, class Ptr>struct _list_iterator{typedef _list_node<T> node;typedef _list_iterator<T,Ref,Ptr> self;_list_iterator(node* pnode):_pnode(pnode){}Ref operator*(){return _pnode->_val;}Ptr operator->(){return &_pnode->_val;}//前置++self& operator++(){_pnode = _pnode->_next;return *this;}//后置++//临时对象不能返回引用self operator++(int){self tmp(*this);_pnode = _pnode->_next;return tmp;}//前置--self& operator--(){_pnode = _pnode->_prev;return *this;}//后置--self operator--(int){self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator==(const self& it){return _pnode == it._pnode;}bool operator!=(const self& it){return _pnode != it._pnode;}self& operator+(int x){while (x--)_pnode = _pnode->_next;return *this;}self& operator-(int x){while (x--)_pnode = _pnode->_prev;return *this;}node* _pnode;};

基础框架(链表主体类)

	//list主体类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;//.................private:node* _head;};

迭代器类使用了三个模板参数,主要为了适配const对象而加上了两个对象。因为如果是const对象,调用operator*()的时候返回的必须是const T&,使得这个对象只能读不能写。如果像前面那样直接写成函数重载,这里就会有问题了,因为迭代器并不是const类型的所以只有返回值类型不相同的两个函数是不能构成函数重载的。

还有方法,就是再写一个const_iterator类,但是这个类除了除了operator*()和operator->这两个函数的返回值不同以外,其他的函数都是相同的,所以代码会有很多的冗余。

这就要使用模板,将这两个函数的返回值都写成模板参数,const和非const只需要在模板实例化的时候控制就可以了,实际上还是生成了两个类,但是这是编译器替我们写的,并不需要我们自己动手。

链表主体函数

		//迭代器typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;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);}////构造函数list(){empty_initialize();}list(size_t n, const T& val = T()):_head(nullptr){file_initialize(n, val);}list(int n, const T& val = T()):_head(nullptr){file_initialize(n, val);}template<class InputIterator>list(InputIterator first, InputIterator last){empty_initialize();InputIterator it = first;while (it != last){push_back(*it);++it;}}void swap(list<T>& lt){std::swap(_head, lt._head);}//copy constructlist(const list<T>& lt):_head(nullptr){empty_initialize();list<T> tmp(lt.begin(), lt.end());swap(tmp);}list<T>& operator=(list<T> tmp){swap(tmp);return *this;}//析构~list(){clear();delete _head;_head = nullptr;//cout << _head << endl;}//自定义类型交换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_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){node* newnode = new node(x);node* cur = pos._pnode;node* prev = (pos._pnode)->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return pos;}iterator erase(iterator pos){node* cur = pos._pnode;node* next = cur->_next;node* prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;return next;}iterator pop_front(){return erase(_head->_next);}iterator pop_back(){return erase(_head->_prev);}void clear(){node* cur = _head->_next;while (cur != _head){node* next = cur->_next;delete cur;cur = next;}_head->_next = _head;_head->_prev = _head;}//size_t size() const{size_t count = 0;node* cur = _head->_next;while (cur != _head){count++;cur = cur->_next;}return count;}bool empty() const{return _head->_next == _head;}//T& front(){return _head->_next->_val;}T& back(){return _head->_prev->_val;}void empty_initialize(){_head = new node;_head->_prev = _head;_head->_next = _head;}void file_initialize(size_t n, const T& val){empty_initialize();for (size_t i = 0; i < n; ++i){push_back(val);}}

在编写成员函数的时候要注意重复冗余的代码可以抽离出来实现成为单独的函数,使用的地方进行调用,逻辑相近的代码可以复用,比如写完insert和erase,头插头删尾插尾删都可以复用。这样不仅代码简洁,而且可维护性更好。

下面着重说一下迭代器重载的operator->的作用。

比如这里有一个类date

class date
{
public:date(int year = 0, int month = 1, int day = 1):_year(year),_month(month),_day(day){}int _year;int _month;int _day;
};

如果list里面存放的是date类对象,要输出的时候就会遇到问题,想要只输出year,只能先解引用然后再使用点来访问date里面的数据。

void test5()
{xzj::list<date> ld;ld.push_back({ 1900,2,28 });ld.push_back({ 1901,3,29 });ld.push_back({ 1902,4,20 });ld.push_back({ 1903,5,20 });xzj::list<date>::iterator it = ld.begin();while (it != ld.end()){cout << (*it)._year << endl;++it;}}

指针访问结构体里面的数据都是使用->那么在这里我们也可以使用->只需要重载->操作符

		Ptr operator->(){return &_pnode->_val;}

有了上面的重载我们就可以直接使用->来访问date对象内的数据了

void test5()
{xzj::list<date> ld;ld.push_back({ 1900,2,28 });ld.push_back({ 1901,3,29 });ld.push_back({ 1902,4,20 });ld.push_back({ 1903,5,20 });xzj::list<date>::iterator it = ld.begin();while (it != ld.end()){cout << it->_year << endl;++it;}
}

但是这里it->实际调用方式是:it . operator->(),这个函数返回了date*这个类型的地址,但是仅有一个地址是不可以的,我们是不是还需要一个->才能访问数据呢?所以一共需要两个->
理论上来讲是这样的,但是这里编译器做了优化,所以这里只需要一个箭头就可以访问了。

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

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

相关文章

实现安卓连接阿里云物联网平台(2)

完整工程链接 链接&#xff1a;https://pan.baidu.com/s/1ykcJHPBSKBXVMaMWKoVRvA?pwd8888 提取码&#xff1a;8888 &#xff08;1&#xff09;创建一个新工程 &#xff08;2&#xff09;添加mqtt包的依赖 implementation org.eclipse.paho:org.eclipse.paho.client.mqttv…

C++学习基础版(一)

目录 一、C入门 1、C和C的区别 2、解读C程序 3、命名空间 4、输入输出 &#xff08;1&#xff09;cout输出流 &#xff08;2&#xff09;endl操纵符 &#xff08;3&#xff09;cin输入流 二、C表达式和控制语句 1、数据机构 特别&#xff1a;布尔类型bool 2、算数运…

C#对于文件中的文件名判断问题

C#中对于文件名的判断问题&#xff0c;我们使用bool值进行值的传递&#xff0c;首先我们使用内置方法进行文件字符串匹配的bool值回传&#xff0c;我们打印出文件名以及相对应的bool&#xff0c;即可知道文件名是否真正生效 bool isHave fileName.Contains("Hello"…

Langchain-chatchat+ChatGlm3-6b部署

我的环境 升级了下配置&#xff0c;加载知识库成功 内存&#xff1a;16GB 32B 显卡&#xff1a;GTX1060-6G RTX4080 Laptop-12G 1. 基础环境准备 1.1. 安装anaconda&#xff0c;创建环境python版本3.11 conda create -n chatglm3 python3.11 conda activate chatglm3 1.…

我国高纯电子级过氧化氢产量逐渐增长 未来有望实现完全国产替代

我国高纯电子级过氧化氢产量逐渐增长 未来有望实现完全国产替代 高纯电子级过氧化氢是氧化氢产品中技术含量最高的细分品类&#xff0c;多用于印刷电路板蚀刻、硅片清洗、光刻胶剥离等方面。经过多年发展&#xff0c;高纯电子级过氧化氢制备工艺已经成熟&#xff0c;大致可分为…

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 并训练自己的数据集

大模型主流微调训练方法总结 LoRA、Adapter、Prefix-tuning、P-tuning、Prompt-tuning 概述 大模型微调(finetuning)以适应特定任务是一个复杂且计算密集型的过程。本文训练测试主要是基于主流的的微调方法:LoRA、Adapter、Prefix-tuning、P-tuning和Prompt-tuning,并对…

开篇介绍——蓝桥赛前冲刺(JavaB组)

开篇介绍 蓝桥杯赛事时间安排 专栏内容介绍 在接下来的几天时间内&#xff0c;老汉会不间断的更新该专栏&#xff0c;主要针对蓝桥杯B组赛事高频考点的复习巩固&#xff0c;其中包括老汉认为较优质的算法讲解&#xff08;文章、视频&#xff09;&#xff0c;以及对应的真题、…

关系型数据库mysql(2)SQL语句

目录 一.SQL语句简介 1.1SQL语言 1.2SQL语句分类 1.3SQL分类 1.4SQL 语言规范 二.数据库基本操作 2.1查看数据库中的库信息 2.2查看数据库中的表信息 数据库内查看 数据库外查看 2.3显示数据库的结构&#xff08;字段&#xff09; ​编辑 2.4 字段属性 2.5常见的数…

【Linux】文件属性信息、文件目录权限修改

Linux文件属性信息 在 Linux 中&#xff0c;ls命令用于列出目录内容&#xff0c;并提供了许多参数以定制输出和显示不同类型的信息。以下是一些常用的ls命令参数 -a显示所有文件和目录&#xff0c;包括以.开头的隐藏文件。-l使用长格式列出文件和目录的详细信息&#xff0c;包…

Blender 3D建模要点

3d模型可以为场景的仿真模拟带来真实感&#xff0c;它还有助于更轻松地识别场景中的所有内容。 例如&#xff0c;如果场景中的所有对象都是简单的形状&#xff0c;如立方体和圆形&#xff0c;则很难在仿真中区分对象。 1、碰撞形状与视觉形状 像立方体和球体这样的简单形状&a…

【JAVA笔记】IDEA配置本地Maven

文章目录 1 配置本地Maven1.1 Maven下载1.2 Maven安装与配置1.2.1 安装1.2.2 配置1.2.2.1 环境配置1.2.2.2 本地仓库配置 2 IDEA设置本地Maven 1 配置本地Maven 1.1 Maven下载 官网&#xff1a;http://maven.apache.org/下载地址&#xff1a;http://maven.apache.org/downloa…

机器学习-05-回归算法

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中回归算法&#xff0c;包括线性回归&#xff0c;岭回归&#xff0c;逻辑回归等部分。 参考 fit_transform,fit,transform区别和作用详解&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…

HarmonyOS/OpenHarmony应用开发-HDC环境变量设置

hdc&#xff08;HarmonyOS Device Connector&#xff09;是 HarmonyOS 为开发人员提供的用于调试的命令行工具&#xff0c;通过该工具可以在 windows/linux/mac 系统上与真实设备或者模拟器进行交互。 hdc 工具通过 HarmonyOS SDK 获取&#xff0c;存放于 /Huawei/Sdk/openhar…

python统计分析——正态分布

参考资料&#xff1a;python统计分析【托马斯】 正态分布或高斯分布是所有分布函数中最重要的。这是由于当样本数足够大的时候&#xff0c;所有分布函数的平均值都趋近于正态分布。数学上正态分布的特征有平均数μ和标准差σ确定。 其中&#xff0c;-∞<x<∞&#xff0c;…

【STL基础】vector、stack、queue、list、pair、map、unordered_map、set、unordered_set(详细讲解)

vector、list、pair、unordered_map、unordered_set、stack、queue 参考文章&#xff1a; &#xff08;1&#xff09;【apollo】泛型编程 与 STL &#xff08;2&#xff09;c stack用法 入门必看 超详细 &#xff08;3&#xff09;C中queue的用法&#xff08;超详细&#xff0c…

7年产品老兵自述:无代码“导演”数字孪生!

文章导读&#xff1a; 有人说自己天生不适合体制内的工作&#xff0c;当不了金丝雀&#xff0c;只能做野飞的麻雀。但逃离体制&#xff0c;就真的能过上自己想要的生活吗&#xff1f;睿睿的回答是&#xff1a;可以&#xff01;且看内向天蝎男&#xff0c;如何离别体制、一路生…

前端学习笔记 | JS进阶

一、作用域 1、局部作用域 &#xff08;1&#xff09;函数作用域 &#xff08;2&#xff09;块作用域 let和const会产生块作用域 &#xff0c;而var不会产生块作用域 2、全局作用域 script标签和js文件的【最外层】变量 3、作用域链 本质&#xff1a;底层的变量查找机制 4、JS…

Linux系统如何使用tcpdump实时监控网络速度:方法与技巧解析

在网络管理和故障排查中&#xff0c;了解网络速度是一个重要的环节。而tcpdump&#xff0c;作为一个强大的网络数据包分析工具&#xff0c;不仅可以用于分析数据包的内容&#xff0c;还能用于实时监控网络速度。本文将介绍Linux系统如何使用tcpdump来实时监控网络速度。 首先&…

什么是 Transformer 机器学习模型?

此为视频What are Transformers (Machine Learning Model)?的笔记。 其实标题里已经揭示了最重要的一点&#xff1a;Transformer&#xff0c;也就是GPT中的T&#xff0c;是一种机器学习模型&#xff0c;或者更准确的说&#xff0c;是一种深度学习模型。基于翻译为中文可能会导…

jmeter的函数助手使用方法

如某个上传文件接口&#xff0c;一个文件只能同时被一个接口调用&#xff0c;如果被并发同时调用就会报错 创建多个测试文件 比如50并发&#xff0c;创建更多的文件防止并发多时随机数生成重复 生成随机数函数 工具–函数助手-选择random-输入范围&#xff08;1-696&#…