STL —— list
本篇文章主要讲解 list模拟实现的相关内容
1. list简介
列表
(list)是C++标准模板库(STL)中的一个容器,它是一个双向链表数据结构,用于存储元素。与vector不同,列表中的元素在内存中不是连续存储的,而是通过指针相互连接形成链表。这种设计使得列表对于插入和删除操作非常高效,但是在查找特定元素时相对较慢,因为需要按序遍历整个链表。
2. list模拟实现
2.1 list类的相关成员变量
在
C++的标准库中,list的实现方式是带头双向循环链表,因此在类中,我们需要一个头指针_head。至于每个节点,我们也同样需要构造一个类,其中成员变量包含_prev,_next和数据_val。
template<class T>
struct ListNode
{ListNode<T>* _prev;ListNode<T>* _next;T _val;ListNode(const T& x = T()):_prev(nullptr),_next(nullptr),_val(x){}
};
template<class T>
class list
{
public:typedef ListNode<T> Node;list(){_head = new Node;_head->_prev = _head;_head->_next = _head;}
private:Node* _head;
};
2.2 尾插
尾插太简单了,直接上代码:
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;
}
2.3 迭代器
在库中,我们不管迭代器的底层是如何实现的,但是我们都要用相同的方法使用迭代器,例如之前讲过的
vector,string,在g++中的实现方法就是原生指针,来实现例如++、--、*等功能,但是这里list由于不是连续存储的,所以用原生指针正常的++、--等功能并不能达到我们的预期,因此我们可以把迭代器搞成一个类类型,并用运算符重载来改变它的功能。
void list_test1()
{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;
}
2.3.1 迭代器类的成员变量
迭代器类中其实就包含了一个指向结点类型的指针,因为我们的目的就是改变原生指针的相关操作,来实现迭代器相关的操作。
代码如下:
struct ListNodeIterator
{typedef ListNode<T> Node;Node* _node;ListNodeIterator(Node* node):_node(node){}
};
2.3.2 迭代器类的实现
template<class T>
struct ListNodeIterator
{typedef ListNode<T> Node;typedef ListNodeIterator<T> Self;Node* _node;ListNodeIterator(Node* node):_node(node){}T& operator*(){return _node->_val;}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& it){return _node != it._node;}
};
2.3.3 insert 和 erase
insert和erase传的参数就是iterator,模拟实现代码如下:
void insert(iterator pos, const T& x)
{Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}
void erase(iterator pos)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;cur = nullptr;
}
iterator erase(iterator pos)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;cur = nullptr;return next;
}
2.3.4 begin 和 end
begin和end是在迭代器中的成员函数,返回头和尾的迭代器即可:
typedef ListNodeIterator<T> iterator;
iterator begin()
{return iterator(_head->_next);// 单参数类型的构造函数支持隐式类型转换,以下依法也可以:// return _head->_next;
}
iterator end()
{return iterator(_head);// return _head;
}
2.3.5 insert 和 erase的复用
push_back、push_front、pop_back、pop_front都可以复用insert和erase,代码如下:
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;*/insert(end(), x);
}
void pop_back()
{erase(--end());
}
void push_front(const T& x)
{insert(begin(), x);
}
void pop_front()
{erase(begin());
}
测试代码:
void list_test1()
{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;lt.pop_back();lt.pop_back();it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;lt.push_front(100);lt.push_front(200);lt.push_front(300);it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;lt.pop_front();lt.pop_front();it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}

2.3.5 operator->的重载
- 先看一下这段代码:
void list_test2()
{struct A{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}};list<A> lt;A a(1, 2);lt.push_back(a);lt.push_back(A(3, 4));lt.push_back({ 5,6 });list<A>::iterator it = lt.begin();while (it != lt.end()){// 主要看这里cout << (*it)._a1 << " " << (*it)._a2 << " ";cout << endl;++it;}
}
我们注意到当我们访问自定义类型的数据需要这样:
(*it)._a1进行访问,但是迭代器就是为了模仿指针的相关操作,例如我们有A*这种类型的指针如何进行访问A中的数据呢?
A* aa;
(*aa)._a1;
// 上面的方法很别扭,我们正常用指针都是用->访问的,所以我们如何实现->的重载呢?
aa->_a1;
- 实现方法如下:
T* operator->()
{return &_node->_val;
}
为什么这样就行了呢,我们知道自定义类型
A存储在了节点的_val中,这里返回了_val的地址,如果按照正常的思路进行访问,应该按照如下的方式:
cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << " ";所以应该有两个箭头:第一个箭头代表运算符的重载,第二个代表指针解引用访问数据:
cout << it->->_a1 << " " << it->->_a2 << " ";但是编译器进行了简化,两个箭头变成了一个箭头
cout << it->_a1 << " " << it->_a2 << " ";
`
void list_test2()
{struct A{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}};/*A* aa;(*aa)._a1;aa->_a1;*/list<A> lt;A a(1, 2);lt.push_back(a);lt.push_back(A(3, 4));lt.push_back({ 5,6 });list<A>::iterator it = lt.begin();while (it != lt.end()){cout << (*it)._a1 << " " << (*it)._a2 << " ";cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << " ";//cout << it->->_a1 << " " << it->->_a2 << " ";cout << it->_a1 << " " << it->_a2 << " ";cout << endl;++it;}
}

2.4 const迭代器
- 我们先看以下这段代码:
void Print(const list<int>& lt)
{list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}void list_test3()
{list<int> lt;lt.push_back(1);lt.push_back(1);lt.push_back(8);lt.push_back(6);lt.push_back(0);Print(lt);
}
- 很明显,会报错,提示不能从
const转换为非const:

- 很多人可能会想着在
begin和end后加上const进行修饰,但其实也不行,这样虽然传入的值是const类型,但是返回的值不是const类型,就会导致返回的值能被修改,但是要求是返回的值是const类型,所以这种想法是不行的,下面是错误的示范:
iterator begin() const
{return iterator(_head->_next);// 单参数类型的构造函数支持隐式类型转换,以下写法也可以:// return _head->_next;
}
iterator end() const
{return iterator(_head);// return _head;
}
void Print(const list<int>& lt)
{list<int>::iterator it = lt.begin();while (it != lt.end()){// 这里可以修改*it += 10;cout << *it << " ";++it;}cout << endl;
}void list_test3()
{list<int> lt;lt.push_back(1);lt.push_back(1);lt.push_back(8);lt.push_back(6);lt.push_back(0);Print(lt);
}

- 那应该如何解决呢?在此之前,我们需要了解一下这段代码:
const iterator begin()
{return iterator(_head->_next);// 单参数类型的构造函数支持隐式类型转换,以下写法也可以:// return _head->_next;
}
const iterator end()
{return iterator(_head);// return _head;
}
- 当我们在返回值前加上
const,代表返回的迭代器不能被修改,例如不能进行++it; - 但是我们是想着迭代器
指向的内容不能被修改,因此这种方法是不可行的。 - 可以类比一下这段代码:
// const修饰的是解引用之后的内容
const int* a;
// const修饰的是指针本身
int* const a;
- 解决方法其实很简单,之前说过既然不满足要求,那我们就自己造轮子,自己写一个类;
- 这个类其实也很简单,就把
ListNodeIterator这个类中的两个运算符重载函数的返回值改变一下就可以了,一个是*,另一个是->:
template<class T>
struct ListNodeConstIterator
{typedef ListNode<T> Node;typedef ListNodeConstIterator<T> Self;Node* _node;ListNodeConstIterator(Node* node):_node(node){}const T& operator*(){return _node->_val;}const T* operator->(){return &_node->_val;}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& it){return _node != it._node;}
};
在list类中加入这两个函数:
const_iterator begin() const
{return const_iterator(_head->_next);// 单参数类型的构造函数支持隐式类型转换,以下写法也可以:// return _head->_next;
}const_iterator end() const
{return const_iterator(_head);// return _head;
}
- 这时候就不能修改了


2.5 模板的作用
我们发现,在两个迭代器中,只用两个函数的返回值不同,其他的全部都一样,看上去非常冗余,那我们可不可以用一种方法来解决这种冗余呢?肯定是可以的,我们这个时候就可以用到模板:
template<class T, class Ref, class Ptr>
struct ListNodeIterator
{typedef ListNode<T> Node;typedef ListNodeIterator<T, Ref, Ptr> Self;Node* _node;ListNodeIterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){return &_node->_val;}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& it){return _node != it._node;}
};
template<class T>
class list
{
public:typedef ListNode<T> Node;typedef ListNodeIterator<T, T&, T*> iterator;typedef ListNodeIterator<T,const T&, const T*> const_iterator;iterator begin() {return iterator(_head->_next);// 单参数类型的构造函数支持隐式类型转换,以下写法也可以:// return _head->_next;}iterator end() {return iterator(_head);// return _head;}const_iterator begin() const{return const_iterator(_head->_next);// 单参数类型的构造函数支持隐式类型转换,以下写法也可以:// return _head->_next;}const_iterator end() const{return const_iterator(_head);// return _head;}// ......
};
这里虽然只写了一份
iterator类,但是在编译的时候,编译器会根据你的需要生成两份iterator类,所以模板很强大。
3. 拷贝构造、赋值重载、析构
3.1 析构函数
- 析构函数释放掉空间即可,记住更新一下迭代器。
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}~list()
{clear();delete _head;_head = nullptr;
}
3.2 拷贝构造
- 拷贝构造新建一个头节点,然后尾插。
void empty_init()
{_head = new Node;_head->_prev = _head;_head->_next = _head;
}list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}
3.3 赋值重载
- 赋值重载现代写法,之前讲过类似的方法:
void swap(list<T>& lt)
{std::swap(_head, lt._head);
}list<int>& operator=(list<T> lt)
{swap(lt);return *this;
}
相关文章:
STL —— list
博主首页: 有趣的中国人 专栏首页: C专栏 本篇文章主要讲解 list模拟实现的相关内容 1. list简介 列表(list)是C标准模板库(STL)中的一个容器,它是一个双向链表数据结构,…...
申请SSL证书
有很多方法可以确保您的网站安全。添加SSL证书可针对恶意攻击提供额外且关键的保护层。 即使网站不接受交易,您仍然需要保护用户的登录详细信息、地址和其他个人信息。 没有SSL证书的网站使用HTTP(一种基于文本的协议),这意味着…...
深入浅出 -- 系统架构之负载均衡Nginx环境搭建
引入负载均衡技术可带来的收益: 系统的高可用:当某个节点宕机后可以迅速将流量转移至其他节点。系统的高性能:多台服务器共同对外提供服务,为整个系统提供了更高规模的吞吐。系统的拓展性:当业务再次出现增长或萎靡时…...
notepad++绿色版添加右键菜单
解压路径 D:\Green\notepad_v8.0_x64_绿色版 添加右键菜单.reg 新建nodepad添加右键菜单.reg文件 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\NotePad] "Edit with &Notepad" "Icon""D:\\Green\\notepad_v8.0_x64_绿色版…...
7 个 iMessage 恢复应用程序/软件可轻松恢复文本
由于误操作、iOS 升级中断、越狱失败、设备损坏等原因,您可能会丢失 iPhone/iPad 上的 iMessages。意外删除很大程度上增加了这种可能性。更糟糕的是,这种情况经常发生在 iDevice 缺乏备份的情况下。 (iPhone消息消失还占用空间?&…...
DockerFile启动jar程序
1.创建Dockerfile 在项目的根目录下创建一个名为Dockerfile的文件,并使用文本编辑器打开它。Dockerfile的内容如下: # 基础镜像 FROM openjdk:8-jre # 创建目录 RUN mkdir -p /usr/app/ # 设置工作目录 WORKDIR /usr/app # 将JAR文件复制到容器中,注:…...
基于R、Python的Copula变量相关性分析及AI大模型应用
在工程、水文和金融等各学科的研究中,总是会遇到很多变量,研究这些相互纠缠的变量间的相关关系是各学科的研究的重点。虽然皮尔逊相关、秩相关等相关系数提供了变量间相关关系的粗略结果,但这些系数都存在着无法克服的困难。例如,…...
鸿蒙组件学习_Tabs组件
说明 该组件从API Version 7 开始支持。 子组件 仅可包含子组件TabContent 参数 barPosition 设置Tabs的页签位置,默认值: BarPosition.StartStart vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。End vertic…...
【LangChain学习之旅】—(19)BabyAGI:根据气候变化自动制定鲜花存储策略
【LangChain学习之旅】—(19)BabyAGI:根据气候变化自动制定鲜花存储策略 AutoGPTBaby AGIHuggingGPTLangChain 目前是将基于 CAMEL 框架的代理定义为 Simulation Agents(模拟代理)。这种代理在模拟环境中进行角色扮演,试图模拟特定场景或行为,而不是在真实世界中完成具体…...
thinkphp6入门(21)-- 如何删除图片、文件
假设文件的位置在 /*** 删除文件* $file_name avatar/20240208/d71d108bc1086b498df5191f9f925db3.jpg*/ function deleteFile($file_name) {// 要删除的文件路径$file app()->getRootPath() . public/uploads/ . $file_name; $result [];if (is_file($file)) {if (unlin…...
虚拟内存知识详解
虚拟内存 单片机的 CPU 是直接操作内存的「物理地址」 在这种情况下,要想在内存中同时运行两个程序是不可能的 操作系统是如何解决这个问题呢? 关键的问题是这两个程序都引用了绝对物理地址,而这正是我们最需要避免的。 可以把进程所使用的…...
数据结构初阶:顺序表和链表
线性表 线性表 ( linear list ) 是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串 ... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性…...
在flutter中添加video_player【视频播放插件】
添加插件依赖 dependencies:video_player: ^2.8.3插件的用途 在Flutter框架中,video_player 插件是一个专门用于播放视频的插件。它允许开发者在Flutter应用中嵌入视频播放器,并提供了一系列功能来控制和定制视频播放体验。这个插件对于需要在应用中展…...
golang微服务框架特性分析及选型
目录 一、微服务框架特性(10个)包括:Istio、go-zero、go-kit、go-kratos、go-micro、rpcx、kitex、goa、jupiter、dubbo-go、tarsgo 1、特性及使用场景2、比较 二、web框架特性(7个)包括:gin、fiber、beego…...
苹果cmsV10 MXProV4.5自适应PC手机影视站主题模板苹果cms模板mxone pro
演示站:http://a.88531.cn:8016 MXPro 模板主题(又名:mxonepro)是一款基于苹果 cms程序的一款全新的简洁好看 UI 的影视站模板类似于西瓜视频,不过同对比 MxoneV10 魔改模板来说功能没有那么多,也没有那么大气,但是比较且可视化功…...
GPU的了解
3D动画揭秘显卡的GPU是如何工作的_哔哩哔哩_bilibili 位于显卡中。 与CPU区别: 100名小学生和1位数学博士 做100道非常简单的算术题,小朋友一个人一道题,比博士快。 做1道非常复杂的数学问题,只有博士可以做出来。 CPU主要用于快…...
鸿蒙实战开发-如何使用Stage模型卡片
介绍 本示例展示了Stage模型卡片提供方的创建与使用。 用到了卡片扩展模块接口,ohos.app.form.FormExtensionAbility 。 卡片信息和状态等相关类型和枚举接口,ohos.app.form.formInfo 。 卡片提供方相关接口的能力接口,ohos.app.form.for…...
蓝桥杯刷题 前缀和与差分-[2128]重新排序(C++)
问题描述 给定一个数组 A 和一些查询 L**i, R**i,求数组中第 L**i 至第 R**i 个元素之和。 小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。小蓝想知道相比原数组,所有查询结果的总和最多…...
STM32重要参考资料
stm32f103c8t6 一、引脚定义图 二、时钟树 三、系统结构图 四、启动配置 (有时候不小心短接VCC和GND,芯片会锁住,可以BOOT0拉高试试(用跳线帽接)) 五、最小系统原理图 可用于PCB设计 六、常见折腾人bug…...
[StartingPoint][Tier0]Preignition
Task 1 Directory Brute-forcing is a technique used to check a lot of paths on a web server to find hidden pages. Which is another name for this? (i) Local File Inclusion, (ii) dir busting, (iii) hash cracking. (目录暴力破解是一种用于检查 Web 服务器上的大…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
