【C++】- 掌握STL List类:带你探索双向链表的魅力
文章目录
- 前言:
- 一.list的介绍及使用
- 1. list的介绍
- 2. list的使用
- 2.1 list的构造
- 2.2 list iterator的使用
- 2.3 list capacity
- 2.4 list element access
- 2.5 list modifiers
- 2.6 list的迭代器失效
- 二.list的模拟实现
- 1. list的节点
- 2. list的成员变量
- 3.list迭代器相关问题
- 3.1 普通迭代器
- 3.2 const迭代器
- 4. list的成员函数
- 4.1 list的空初始化
- 4.2 push_back
- 4.3 构造函数
- 4.4 insert
- 4.4 erase
- 4.5 push_front
- 4.6 pop_front
- 4.7 pop_back
- 4.8 clear
- 4.8 析构函数
- 4.9 swap
- 4.10 赋值运算符重载
- 最后想说:
前言:
C++中的List容器是标准模板库(STL)中的一种序列容器,它实现了双向链表的功能。与数组(如vector)和单向链表相比,List容器提供了更加灵活的元素插入和删除操作,特别是在容器中间位置进行这些操作时。
一.list的介绍及使用
1. list的介绍
- 双向链表结构: list容器使用双向链表来存储元素,每个元素(节点)都包含数据部分和两个指针,分别指向前一个元素和后一个元素。这种结构使得在链表的任何位置进行插入和删除操作都非常高效,时间复杂度为
O(1)
。 - 动态大小: list容器的大小可以在运行时动态改变,即可以在程序运行过程中添加或移除元素。
- 不支持随机访问: 与
vector
和array
等连续内存的容器不同,list
不支持随机访问迭代器,不能直接通过索引获取元素,而需要通过迭代器遍历。 - 迭代器稳定性: 在list中插入或删除元素不会导致其他迭代器失效(除了指向被删除元素的迭代器)。这是因为它通过调整相邻节点的指针来维护链表结构,而不需要移动元素或重新分配内存。
2. list的使用
list的使用参考文档:list的文档介绍
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) | 用[first, last)区间中的元素构造list |
代码演示:
#include<list>
int main()
{list<int> l1;//构造空的l1;list<int> l2(4,100);//l2中存放4个值为100的元素list<int> l3(l2.begin(),l2.end());//用l2的[begin,end)左开右闭区间构造l3;list<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 };// 用迭代器方式打印l5中的元素list<int>::iterator it = l5.begin();while (it != l5.end()){cout << *it << " ";++it;}cout << endl;// C++11范围for的方式遍历for (auto& e : l5)cout << e << " ";cout << endl;return 0;
}
2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
函数声明 | 接口说明 |
---|---|
begin | 返回第一个元素的迭代器 |
end | 返回最后一个元素下一个位置的迭代器 |
rbegin | 返回一个指向容器中最后一个元素的反向迭代器(即容器的反向起始) |
rend | 返回一个反向迭代器,该迭代器指向列表容器中第一个元素之前的理论元素(该元素被认为是其反向结束)。 |
注意:
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
代码演示:
int main()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));// 使用正向迭代器正向list中的元素// list<int>::iterator it = l.begin(); // C++98中语法auto it = l.begin(); // C++11之后推荐写法while (it != l.end()){cout << *it << " ";++it;}cout << endl;// 使用反向迭代器逆向打印list中的元素// list<int>::reverse_iterator rit = l.rbegin();auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;return 0;
}
2.3 list capacity
函数声明 | 接口说明 |
---|---|
front | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
2.4 list element access
函数声明 | 接口说明 |
---|---|
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
2.5 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中的有效元素 |
代码演示:
#include<iostream>
#include<vector>
using namespace std;void PrintList(const list<int>& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";}cout << endl;
}// list插入和删除
// push_back/pop_back/push_front/pop_front
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);PrintList(L);// 删除list尾部节点和头部节点L.pop_back();L.pop_front();PrintList(L);
}// 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);PrintList(L);// 在pos前插入5个值为5的元素L.insert(pos, 5, 5);PrintList(L);// 在pos前插入[v.begin(), v.end)区间中的元素vector<int> v{ 7, 8, 9 };L.insert(pos, v.begin(), v.end());PrintList(L);// 删除pos位置上的元素L.erase(pos);PrintList(L);// 删除list中[begin, end)区间中的元素,即删除list中的所有元素L.erase(L.begin(), L.end());PrintList(L);
}// resize/swap/clear
void TestList3()
{// 用数组来构造listint array1[] = { 1, 2, 3 };list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));PrintList(l1);// 交换l1和l2中的元素list<int> l2;l1.swap(l2);PrintList(l1);PrintList(l2);// 将l2中的元素清空l2.clear();cout << l2.size() << endl;
}int main()
{TestList1();TestList2();TestList3();return 0;
}
运行结果:
2.6 list的迭代器失效
前面已经说过了,此处可以将迭代器理解为类似于指针的东西,迭代器失效即迭代器指向的节点失效了,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list进行插入操作时不会导致迭代器失效,只有删除时才会失效,并且失效的是被删除节点的迭代器,其他迭代器不会受到影响。
二.list的模拟实现
1. list的节点
template<class T>
struct list_node
{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}
};
2. list的成员变量
template<class T>
class list
{typedef list_node<T> Node;
public://成员函数
private:Node* _head; //哨兵位的头节点
};
没有用访问限定符限制的成员class
默认是私有的,struct
默认是公有的,如果一个类既有公有也有私有就用class
,全部为公有一般用struct
。这不是规定,只是个惯例。
3.list迭代器相关问题
简单分析:
&emsp; 这里不能像以前一样给一个结点的指针作为迭代器,如果it
是typedef的节点的指针,it
解引用得到的是节点,不是里面的数据,但是我们期望it
解引用是里面的数据,++it
我们期望走到下一个节点去,而list
中++走不到下一个数据,因为数组的空间是连续的,++可以走到下一个数据。但是链表达不到这样的目的。所以原身指针已经无法满足这样的行为,怎么办呢?这时候我们的类就登场了
用类封装一下节点的指针,然后重载运算符,模拟指针。
例如:
reference operator*()const
{return (*node).data;
}
self& opertor++()
{node = (link_type)((*node).next);return *this;
}
3.1 普通迭代器
template<class T>
struct list_iterator
{typedef list_node<T> Node;typedef list_iterator<T> Self;Node* _node;list_iterator(Node* node):_node(node){}T& operator*() //用引用返回可以读数据也可以修改数据{return _node->_data;}T* operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}
};
3.2 const迭代器
const迭代器在定义的时候不能直接定义成typedef const list_iterator<T> const_iterator
,const迭代器的本质是限制迭代器指向的内容不能被修改,而前面的这种写法限制了迭代器本身不能被修改,所以迭代器就不能进行++
操作。那该怎能办呢?答案是我们可以实现一个单独的类:
template<class T>
struct list_const_iterator
{typedef list_node<T> Node;typedef list_const_iterator<T> Self;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data; //返回这个数据的别名,但是是const别名,所以不能被修改}const T* operator->(){return &_node->_data; //我是你的指针,const指针}Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s){return _node != s._node;}
};
普通法迭代器与const迭代器的区别就是:普通迭代器可读可写,const迭代器只能读
上面是我们自己实现的普通迭代器和const迭代器,用两个类,并且这两个类高度相似,下来就让我们一起看一看库里面是怎么实现的吧!
我们可以看到库里面是写了两个模板,让编译器去生成对应的类。其本质上也是写了两个类,只不过是让编译器去生成对应的类。
迭代器不需要我们自己写析构函数、拷贝构造函数、赋值运算符重载函数,因为这里要的是浅拷贝,例如我把一个迭代器赋值给另外一个迭代器,就是期望两个迭代器指向同一个节点,这里用浅拷贝即可,拷贝给你我们两个迭代器就指向同一个节点。
4. list的成员函数
4.1 list的空初始化
void empty_init() //空初始化
{_head = new Node();_head->_next = _head;_head->_prev = _head;
}
4.2 push_back
//普通版本
void push_back(const T& x)
{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版本insert(end(),x);
4.3 构造函数
list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr)
{}
4.4 insert
iterator insert(iterator position; const T& val)
{Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}
4.4 erase
iterator erase(iterator pos)
{assert(pos != end());Node* del = pos._node;Node* prev = del->_prev;Node* next = del->_next;prev->_next = next;next->_prev = prev;delete del;return iterator(next);
}
4.5 push_front
void push_front(const T& x)
{insert(begin(), x);
}
4.6 pop_front
void pop_front()
{erase(begin());
}
4.7 pop_back
void pop_back()
{erase(--end());
}
4.8 clear
void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}
4.8 析构函数
~list()
{clear();delete _head;_head = nullptr;
}
4.9 swap
void swap(list<T>& tmp)
{std::swap(_head, tmp._head);//交换哨兵位的头节点
}
4.10 赋值运算符重载
//现代写法
//lt2=lt3
//list<T>& operator=(list<T> lt)
list& operator=(list lt) //不加模板参数
{swap(lt);//交换就是交换哨兵位的头节点return *this;
}//lt3传给lt去调用拷贝构造,所以lt就和lt3有一样大的空间一样大的值,lt2很想要,也就是/this想要,lt2之前的数据不想要了,交换给lt,此时lt2就和lt3有一样大的空间一样大的值,
//lt出了作用域就被销毁了
构造函数和赋值运算符重载函数的形参和返回值类型可以只写类名 list
,不需要写模板参数,这种写法在类里面可以不加,只能在类里面可以这样写,类外面是不行的,一般情况下加上好一点。
最后想说:
本章我们STL的List
就介绍到这里,下期我将介绍关于stack
和queue
的有关知识,如果这篇文章对你有帮助,记得点赞,评论+收藏 ,最后别忘了关注作者,作者将带领你探索更多关于C++方面的问题。
相关文章:

【C++】- 掌握STL List类:带你探索双向链表的魅力
文章目录 前言:一.list的介绍及使用1. list的介绍2. list的使用2.1 list的构造2.2 list iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers2.6 list的迭代器失效 二.list的模拟实现1. list的节点2. list的成员变量3.list迭代器相关问题3.1…...
基于streamlit搭简易前端页面
前端小白第一次用streamlit搭简易页面,记录一下。 一些tips 每次与页面进行交互,如点击按钮、上传文件等,streamlit就会重新运行整个页面的所有代码。如果在页面渲染前需要对上传文件做很复杂的操作,重新运行所有代码就会重复这…...

Harmony Next开发通过bindSheet绑定半模态窗口
示例概述 Harmony Next开发通过bindSheet绑定半模态窗口 知识点 半模态窗口父子组件传值 组件 LoginComponent Component struct LoginComponent {// Prop 父子单项绑定值Prop message:string // Link 父子双向绑定值Link userName:stringLink password:stringLink isSh…...

YOLOv11改进,YOLOv11添加DLKA-Attention可变形大核注意力,WACV2024 ,二次创新C3k2结构
摘要 作者引入了一种称为可变形大核注意力 (D-LKA Attention) 的新方法来增强医学图像分割。这种方法使用大型卷积内核有效地捕获体积上下文,避免了过多的计算需求。D-LKA Attention 还受益于可变形卷积,以适应不同的数据模式。 理论介绍 大核卷积(Large Kernel Convolu…...

【51单片机】矩阵按键快速上手
51单片机矩阵按键是一种在单片机应用系统中广泛使用的按键排列方式,特别适用于需要多个按键但I/O口资源有限的情况。以下是对51单片机矩阵按键的详细介绍: 一、矩阵按键的基本概念 定义:矩阵按键,又称行列键盘,是…...

一文说清:git reset HEAD原理
1 使用add命令,将文件添加到暂存区 命令如下: 对比结果如下: 2 使用reset HEAD命令 如下: 结果对比如下: 忽略logs目录下的内容。 发现只是修改了index暂存区的内容。删掉了原来添加到暂存区的对象ID&#x…...
【前端面试题】书、定位问题、困难
看过什么书 《JavaScript 高级程序设计(第 4 版)》(作者:Matt Frisbie) 这是一本深入学习 JavaScript 语言的经典书籍。它详细地涵盖了 JavaScript 的高级特性,包括原型链、闭包、异步编程等复杂概念。以闭…...

WADesk 升级 Webpack5 一些技术细节认识5和4的区别在哪里
背景 升级过程中发现有很多新的知识点,虽然未来可能永远都不会再遇到,但是仍然是一次学习的好机会,可以让自己知道,打包软件的进化之路,和原来 Webpack 4 版本的差异在哪里。 移除的依赖记录 babel/register: 在 Nod…...
学习 Dockerfile 常用指令
学习 Dockerfile 常用指令 在构建 Docker 镜像时,Dockerfile 文件是一份至关重要的配置文件,它定义了构建镜像的所有步骤。通过在 Dockerfile 中使用不同的指令(命令),我们可以控制镜像的构建过程、设置环境、指定执行…...

day11 性能测试(3)——Jmeter 断言+关联
【没有所谓的运气🍬,只有绝对的努力✊】 目录 1、复习 2、查看结果树 多个http请求原因分析 3、作业 4、Jmeter断言 4.1 响应断言 4.1.1 案例 4.1.2 小结 4.2 json断言 4.2.1 案例 4.2.2 小结 4.3 断言持续时间 4.3.1 案例 4.3.2 小结 4.…...
ES6中的map和set
Map JS的数据对象(Obejct),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键(一定程度上对其的使用有限制) 比如下面代码 const data {} const element document.…...

UE5中实现Billboard公告板渲染
公告板(Billboard)通常指永远面向摄像机的面片,游戏中许多技术都基于公告板,例如提示拾取图标、敌人血槽信息等,本文将使用UE5和材质节点制作一个公告板。 Gif效果: 网格效果: 1.思路 通过…...

泊松编辑 possion editing图像合成笔记
开源地址: GitHub - kono-dada/Reproduction-of-possion-image-editing 掩码必须是矩形框...

#渗透测试#漏洞挖掘#红蓝攻防#SRC漏洞挖掘
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...

系列2:基于Centos-8.6Kubernetes 集成GPU资源信息
每日禅语 自省,就是自我反省、自我检查,自知己短,从而弥补短处、纠正过失。佛陀强调自觉觉他,强调以达到觉行圆满为修行的最高境界。要改正错误,除了虚心接受他人意见之外,还要不忘时时观照己身。自省自悟之…...
Coturn 实战指南:WebRTC 中的 NAT 穿透利器
1. 什么是 Coturn? Coturn 是一种开源的 TURN(Traversal Using Relays around NAT)服务器,用于解决 NAT 穿透问题。它帮助客户端在受限网络环境(例如防火墙或 NAT 后面)中实现双向通信,常用于 WebRTC 应用、VoIP、在线游戏等场景。 2. Cotur…...

基于卷积神经网络的Caser算法
将一段交互序列嵌入到一个以时间为纵轴的平面空间中形成“一张图”后,基于卷积序列嵌入的推荐(Caser)算法利用多个不同大小的卷积滤波器,来捕捉序列中物品间的点级(point-level)、联合的(union-…...
自闭症在学校:了解自闭症的特点,优化学校教育方式
在教育的广阔天地里,每一片叶子都承载着生命的独特韵律,每一朵花都在以自己的方式绽放。然而,在特殊教育的花园里,有一群孩子,他们或许不那么容易被看见,不那么容易与世界沟通,但他们同样拥有学…...

多线程的知识总结(8):用 thread 类 或全局 async (...) 函数,创建新线程时,谁才是在新线程里第一个被执行的函数
(40)用 thread 类 或全局 async (…) 函数,创建新线程时,谁才是在新线程里第一个被执行的函数? 弄清楚这个问题,有利于推测和理解线程中代码的执行流程。根据 thread 类 和 async (…࿰…...
ArcGIS地理空间平台manager存在任意文件读取漏洞
免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...