【C++笔记】list使用详解及模拟实现
前言
各位读者朋友们大家好!上期我们讲了vector的使用以及底层的模拟实现,这期我们来讲list。
目录
- 前言
- 一. 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
- 二. list的模拟实现
- 2.1 list的底层结构
- 2.2 普通迭代器
- 2.3 const迭代器
- 2.4 insert
- 2.5 erase
- 2.6 迭代器失效
- 2.7 list的析构函数
- 2.9 list的构造函数
- 2.8 operator=
- 三. 按需实例化
- 四. initializer_list
- 五. list.h
- 结语
一. list的介绍及使用
1.1 list的介绍
list的文档

这里的list就是双向带头循环链表

1.2 list的使用
list的接口较多,我们要先掌握如何正确的使用,然后再去深入研究底层的原理,以达到可扩展的能力。以下是list的一些常见的重要接口。
1.2.1 list的构造
| 构造函数(Constructor) | 接口说明 |
|---|---|
| list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
| list() | 构造空的list |
| list(const list& x) | 拷贝构造 |
| list(InputItetator first,InputIterator last) | 用一段迭代器区间构造list |
- list (size_type n, const value_type& val = value_type())

- list()

- list(const list& x)

- list(InputItetator first,InputIterator last)

1.2.2 list iterator的使用
这里我们暂时将list的迭代器理解为指针,该指针指向list中的某一节点
| 函数声明 | 接口说明 |
|---|---|
| begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
| rbegin + rend | 返回第一个元素的reverse_iterator,即end位置的迭代器+返回begin位置前一个位置的迭代器 |
可以看到这里的list的迭代器是双向的迭代器



如果想在某个位置插入元素就不能对迭代器进行+运算了
这里我们在第三个位置之前插入1314,要对begin进行三次自加,而不能使用begin+3
list<int> l(5, 520);
int k = 3;
list<int>::iterator it = l.begin();
while (k--)
{++it;
}
l.insert(it, 1314);
for (auto a : l)
{cout << a << " ";
}
cout << endl;

1.2.3 list capacity
| 函数声明 | 接口说明 |
|---|---|
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list的有效节点个数 |
void test_list2()
{list<int> l1,l2;if (l1.empty()){cout << "true" << endl;cout << l1.size() << endl;}else{cout << "false" << endl;cout << l1.size() << endl;}l2.push_back(1314);cout << l2.size() << endl;
}

1.2.4 list element access
| 函数声明 | 接口说明 |
|---|---|
| front | 返回list的第一个节点中的值的引用 |
| back | 返回list的最后一个节点中的值的引用 |
void test_list3()
{list<int> l;l.push_back(520);l.push_back(520);l.push_back(520);l.push_back(520);for (auto a : l){cout << a << " ";}cout << endl;l.front() = 1314;l.back() = 1314;for (auto a : l){cout << a << " ";}
}

将第一个和最后一个位置的值改为了1314
1.2.5 list modifiers
| 函数声明 | 接口说明 |
|---|---|
| push_front | 在list首元素之前插入值为val的元素 |
| pop_front | 删除list的第一个元素 |
| push_back | 尾插值为val的元素 |
| emplace_back | 尾插一个元素 |
| pop_back | 将list的最后一个元素删除 |
| insert | 在pos位置插入值为val的元素 |
| erase | 删除pos位置的元素 |
-
push_front

-
pop_front

-
push_back 和 pop_back

-
emplace_back

emplace_back在功能上跟push_back类似。但是emplace_back支持直接构造,不用再拷贝构造了,在最后一种情况下emplace_back比push_back高效。 -
insert 和 erase

-
splice

将一个链表插到另一个链表的指定位置
void test_list4()
{list<int> lt1,lt2;lt1.push_back(520);lt1.push_back(520);lt2.push_back(1314);lt2.push_back(1314);lt1.splice(lt1.begin(), lt2);for (auto a : lt1){cout << a << " ";}cout << endl;for (auto a : lt2){cout << a << " ";}
}

插入后lt2就被置空了。
这一接口也可以用来调整结点的顺序

- merge

这一功能的实现的是有序链表的合并

将大的尾插到一个头节点后最后将头节点接到lt1上
上面就讲完了list常用接口的使用,下面我们开始模拟实现list
二. list的模拟实现
2.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){}
};template <class T>
class list
{typedef list_node<T> Node;
public:
list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
private:Node* _head;size_t _size;
};
2.2 普通迭代器
因为list的节点在内存中不是连续存储的,因此不能使用原生指针作为迭代器,我们可以封装一个类来作为迭代器,通过运算符重载来实现迭代器的功能。
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++(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 *this;}bool operator==(const Self& s) const{return _node == s._node;}bool operator!=(const Self& s) const{return _node != s._node;}
};




2.3 const迭代器

但是这样要写成两个类,而且两个类的方法只有两个不同,很是冗余,有没有方法能实现成一个类呢?
我们看一下库里是如何实现的:

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){}Ptr operator -> (){return &_node->_data;}Ref 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 *this;}bool operator==(const Self& s) const{return _node == s._node;}bool operator!=(const Self& s) const{return _node != s._node;}
};
写一段程序来体现一下实例化的过程

2.4 insert

iterator insert(iterator pos, const T& x)
{Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;// prev newnode curnewnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;++_size;return newnode;
}
有了insert我们可以服复用hinsert来实现push_back和push_front
void push_back(const T& x)
{insert(end(), x);
}
void push_front(const T& x)
{insert(begin(), x);
}
2.5 erase
将pos节点的前节点和后节点相连然后将pos节点释放即可

iterator erase(iterator pos)
{assert(pos != end());Node* next = pos._node->_next;Node* prev = pos._node->_prev;next->_prev = prev;prev->_next = next;delete pos._node;--_size;return next;
}
有了erase就可以复用erase来实现pop_back和pop_front了
void pop_back()
{erase(--end());
}
void pop_front()
{erase(begin());
}
2.6 迭代器失效

2.7 list的析构函数
~list()
{clear();delete _head;_head = nullptr;
}
void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}
将所有节点删除之后再将头结点释放
2.9 list的构造函数
void empty_init()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
list()
{empty_init();
}
list(const list<T>& tmp)
{empty_init();for (auto& a : tmp){push_back(a);}
}
构造一个头节点,将tmp的节点尾插到头节点后
2.8 operator=
list<T>& operator=(list<T> tmp){swap(tmp);return *this;}
依旧是现代写法

三. 按需实例化
编译器在对模板进行实例化的时候,使用哪些成员函数就实例化哪些成员函数,不会全部实例化。

四. initializer_list
C++11中支持下面的写法:

不需要一直push_back数据,这里是因为支持了initializer_list

initializer_list底层是两个指针,第一个指针指向第一个数据,第二个指针指向最后一个数据的下一位置

我们写的list如果要支持这种写法需要写一个新的构造函数

list(initializer_list<T> il)
{empty_init();for (auto& a : il){push_back(a);}
}
跟普通的构造函数一样,只是参数变了而已,最正确的写法应该如下,因为我们是构造函数


这样就是隐式类型转换了

所以就有了下面的玩法:

五. list.h
namespace Yuey
{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){}};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){}Ptr operator -> (){return &_node->_data;}Ref 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 *this;}bool operator==(const Self& s) const{return _node == s._node;}bool operator!=(const Self& s) const{return _node != s._node;}};struct AA{int _a1 = 520;int _a2 = 1314;};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;void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}list(initializer_list<T> il){empty_init();for (auto& a : il){push_back(a);}}list(const list<T>& tmp){empty_init();for (auto& a : tmp){push_back(a);}}~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}void swap(list<T>& tmp){std::swap(_head, tmp._head);std::swap(_size, tmp._size);}list<T>& operator=(list<T> tmp){swap(tmp);return *this;}iterator begin(){return iterator(_head->_next);}iterator end(){return _head;}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return _head;}iterator insert(iterator pos, const T& x){Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;// prev newnode curnewnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;++_size;return newnode;}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}iterator erase(iterator pos){assert(pos != end());Node* next = pos._node->_next;Node* prev = pos._node->_prev;next->_prev = prev;prev->_next = next;delete pos._node;--_size;return next;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}private:Node* _head;size_t _size;};
}
结语
以上我们就讲完了list的用法以及模拟实现,希望对大家有所帮助,感谢大家的阅读,欢迎大家批评指正!
相关文章:
【C++笔记】list使用详解及模拟实现
前言 各位读者朋友们大家好!上期我们讲了vector的使用以及底层的模拟实现,这期我们来讲list。 目录 前言一. list的介绍及使用1.1 list的介绍1.2 list的使用1.2.1 list的构造1.2.2 list iterator的使用1.2.3 list capacity1.2.4 list element access1.…...
【机器学习】机器学习中用到的高等数学知识-7.信息论 (Information Theory)
熵 (Entropy):用于评估信息的随机性,常用于决策树和聚类算法。交叉熵 (Cross-Entropy):用于衡量两个概率分布之间的差异,在分类问题中常用。 信息论作为处理信息量和信息传输的数学理论,在机器学习中具有广泛的应用。…...
《现代制造技术与装备》是什么级别的期刊?是正规期刊吗?能评职称吗?
问题解答 问:《现代制造技术与装备》是不是核心期刊? 答:不是,是知网收录的第二批认定学术期刊。 问:《现代制造技术与装备》级别? 答:省级。主管单位:齐鲁工业大学࿰…...
09 - Clickhouse的SQL操作
目录 1、Insert 1.1、标准 1.2、从表到表的插入 2、Update和Delete 2.1、删除操作 2.2、修改操作 3、查询操作 3.1、with rollup:从右至左去掉维度进行小计 3.2、with cube : 从右至左去掉维度进行小计,再从左至右去掉维度进行小计 3.3、with …...
如何解决pdf.js跨域从url动态加载pdf文档
摘要 当我们想用PDF.js从URL加载文档时,将会因遇到跨域问题而中断,且是因为会触发了PDF.js和浏览器的双重CORS block,这篇文章将会介绍:①如何禁用pdf.js的跨域?②如何绕过浏览器的CORS加载URL文件?②如何使…...
深入理解TTY体系:设备节点与驱动程序框架详解
往期内容 本专栏往期内容:Uart子系统 UART串口硬件介绍 interrupt子系统专栏: 专栏地址:interrupt子系统Linux 链式与层级中断控制器讲解:原理与驱动开发 – 末片,有专栏内容观看顺序 pinctrl和gpio子系统专栏…...
库的操作(MySQL)
1.创建数据库 语法: CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification:[DEFAULT] CHARACTER SET charset_name[DEFAULT] COLLATE collation_name说明: 大写的表示关键字 [ ] 是可…...
在 for 循环中,JVM可能会将 arr.length 提升到循环外部,仅计算一次。可能会将如何解释 详解
在 Java 的 for 循环中,JVM 有能力进行优化,将 arr.length 的访问提升到循环外部,避免每次迭代都重新计算 arr.length。这种优化主要是由于 JVM 的 即时编译器(JIT) 和 逃逸分析(Escape Analysis࿰…...
回溯--数据在内存中的存储:整数、大小端和浮点数的深度解析
目录 引言 1. 整数在内存中的存储 1.1 原码、反码和补码 1.2 为什么使用补码? 1.3 示例代码:整数的存储 2. 大小端字节序和字节序判断 2.1 什么是大端和小端? 2.2 为什么会有大端和小端之分? 2.3 字节序的判断小程序 2.…...
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…...
探索设计模式:原型模式
设计模式之原型模式 🧐1. 概念🎯2. 原型模式的作用📦3. 实现1. 定义原型接口2. 定义具体的原型类3. 定义客户端4. 结果 📰 4. 应用场景🔍5. 深拷贝和浅拷贝 在面向对象编程中,设计模式是一种通用的解决方案…...
NLP论文速读(EMNLP 2023)|工具增强的思维链推理
论文速读|ChatCoT: Tool-Augmented Chain-of-Thought Reasoning on Chat-based Large Language Models 论文信息: 简介: 本文背景是关于大型语言模型(LLMs)在复杂推理任务中的表现。尽管LLMs在多种评估基准测试中取得了优异的成绩…...
JVM垃圾回收详解.②
空间分配担保 空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。 《深入理解 Java 虚拟机》第三章对于空间分配担保的描述如下: JDK 6 Update 24 之前,在发生 Minor GC 之前,虚拟机必须先检查老年代最大…...
什么是事务,事务有什么特性?
事务的四大特性(ACID) 原子性(Atomicity) 解释:原子性确保事务中的所有操作要么全部完成,要么全部不做。这意味着事务是一个不可分割的工作单元。在数据库中,这通常通过将事务的操作序列作为一个…...
深入解析:如何使用 PyTorch 的 SummaryWriter 进行深度学习训练数据的详细记录与可视化
深入解析:如何使用 PyTorch 的 SummaryWriter 进行深度学习训练数据的详细记录与可视化 为了更全面和详细地解释如何使用 PyTorch 的 SummaryWriter 进行模型训练数据的记录和可视化,我们可以从以下几个方面深入探讨: 初始化 SummaryWriter…...
企业微信中设置回调接口url以及验证 spring boot项目实现
官方文档: 接收消息与事件: 加密解密文档:加解密库下载与返回码 - 文档 - 企业微信开发者中心 下载java样例 加解密库下载与返回码 - 文档 - 企业微信开发者中心 将解压开的代码 ‘将文件夹:qq\weixin\mp\aes的代码作为工具拷…...
电脑超频是什么意思?超频的好处和坏处
嗨,亲爱的小伙伴!你是否曾经听说过电脑超频?在电脑爱好者的圈子里,这个词似乎非常熟悉,但对很多普通用户来说,它可能还是一个神秘而陌生的存在。 今天,我将带你揭开超频的神秘面纱,…...
在 AMD GPU 上构建深度学习推荐模型
Deep Learning Recommendation Models on AMD GPUs — ROCm Blogs 2024 年 6 月 28 日 发布者 Phillip Dang 在这篇博客中,我们将演示如何在支持 ROCm 的 AMD GPU 上使用 PyTorch 构建一个简单的深度学习推荐模型 (DLRM)。 简介 DLRM 位于推荐系统和深度学习的交汇…...
阿里云IIS虚拟主机部署ssl证书
宝塔配置SSL证书用起来是很方便的,只需要在站点里就可以配置好,但是云虚拟主机在管理的时候是没有这个权限的,只提供了简单的域名管理等信息。 此处记录下阿里云(原万网)的IIS虚拟主机如何配置部署SSL证书。 进入虚拟…...
Python运算符列表
运算符 描述 xy,x—y 加、减,“"号可重载为连接符 x*y,x**y,x/y,x%y 相乘、求平方、相除、求余,“*”号可重载为重复,“%"号可重载为格式化 <,<,&…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
