【C++】——list的介绍和模拟实现

博主主页:Yan. yan.
C语言专栏
数据结构专栏
力扣牛客经典题目专栏
C++专栏

文章目录
- 一、 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(链表修改)
- 1.2.6、list operation(对链表的一些操作)
- 二、list的模拟实现
- 2.1、list的节点
- 2.2、list的成员变量
- 2.3、list的迭代器
- 2.3.1、普通迭代器
- 2.3.2、const迭代器
- 2.4、list的成员函数
- 2.4.1、构造函数
- 2.4.2、拷贝构造函数
- 2.4.3、赋值运算符重载
- 2.4.4、push_back
- 2.4.5、迭代器相关
- 2.4.6、 insert
- 2.4.7、erase
- 2.4.8、 push_front
- 2.4.10、pop_front
- 2.4.11、 size
- 2.4.12、clear
- 2.4.13、析构函数
一、 list的介绍和使用
1.1、list的介绍
- list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list 的底层是双向链表结构,双向链表中的每个元素存储在互不相关的独立节点中,在节点中通过指针指向的前一个元素和后一个元素。
1.2、list的使用
list的文本介绍
list在实际中非常重要,在实际中我们熟悉常用的接口就可以,下面列出了需要我们重点掌握的接口。
1.2.1、list的构造
| 构造函数 | 接口说明 |
|---|---|
| list() | list 的默认构造,构造空的 list |
| list(size_type n, const value_type& val = value_type()) | 构造的 list 中包含 n 个值为 val 的元素 |
| list(const list& x) | 拷贝构造函数 |
| list(InputIterator first, InputIterator last) | 用[first,last)区间中的元素构造 list |
void TestList1()
{list<int> l1; // 构造空的l1list<int> l2(4, 100); // l2中放4个值为100的元素list<int> l3(l2.begin(), l2.end()); // 用l2的[begin(), end())左闭右开的区间构造l3list<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;
}
1.2.2、list iterator的使用
| 函数声明 | 接口说明 |
|---|---|
| begin() + end() | 返回第一个元素的迭代器 + 返回最后一个元素下一个位置的迭代器 |
| rebegin() + rend() | 返回第一个元素的 reverse_iterator,即 end 位置,返回最后一个一个元素下一个位置的 reverse_iterator,即 begin 位置 |
注意: begin 与 end 为正向迭代器,对迭代器执行 ++ 操作,迭代器向后移动。rbegin 与 rend 为反向迭代器,对迭代器执行 ++ 操作,迭代器向前移动。由于 list 的底层物理空间并不连续,所以 list 的迭代器不再是原生指针,并且 list 的迭代器没有对 + 和 - 进行重载,只重载了 ++ 和 – ,因为空间不连续,重载 + 会比较复杂。即 l.begin() + 5 是不被允许的。
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 << " ";// *it = 10; 编译不通过}cout << endl;
}void TestList2()
{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;
}
1.2.3、list capacity(容量相关)
| 函数声明 | 接口说明 |
|---|---|
| empty | 检测 list 是否为空,是返回 true,否则返回 false |
| size | 返回 list 中有效节点个数 |
1.2.4、list element access(元素访问)
| 函数声明 | 接口说明 |
|---|---|
| front | 返回 list 的第一个节点中值的引用 |
| back | 返回 list 的最后一个节点中值的引用 |
1.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 中的有效元素 |
1.2.6、list operation(对链表的一些操作)
| 函数声明 | 接口说明 |
|---|---|
| reverse | 对链表进行逆置 |
| sort | 对链表中的元素进行排序(稳定排序) |
| merge | 对两个有序的链表进行归并,得到一个有序的链表 |
| unique | 对链表中的元素去重 |
| remove | 删除具有特定值的节点 |
| splice | 将 A 链表中的节点转移到 B 链表 |
二、list的模拟实现
2.1、list的节点
template<class T>
struct ListNode
{ListNode<T>* _next;ListNode<T>* _prev;T _val;ListNode(const T& val = T()){_next = nullptr;_prev = nullptr;_val = val;}
};
2.2、list的成员变量
class list
{typedef ListNode<T> Node;
public://一些成员函数
private:Node* _head;
}
2.3、list的迭代器
list 的迭代器不能再使用原生指针,如果 list 的迭代器使用原生指针的话,那对迭代器解引用得到的是一个节点,而我们希望对迭代器解引用可以得到节点里面存储的元素,并且 list 在底层的物理空间并不连续,如果使用原生指针作为 list 的迭代器,那对迭代器执行 ++ 操作,并不会让迭代器指向下一个节点。因此我们需要对 list 的迭代器进行封装,然后将一些运算符进行重载,以实现迭代器本该有的效果。
2.3.1、普通迭代器
template<class T>
struct _list_iterator
{typedef ListNode<T> Node;Node* _node;_list_iterator(Node* val){_node = val;}T& operator* (){return _node->_val;}T* operator-> ()//迭代器通过->应该指向节点中的元素,因此返回的是一个T类型的地址{return &(_node->_val);}bool operator!= (const _list_iterator<T>& right){return _node != right._node;}_list_iterator<T> operator++(){_node = _node->_next;return *this;}_list_iterator<T> operator++(int){_list_iterator<T> tmp(this->_node);_node = _node->_next;return tmp;}
};
这里的类名不能直接叫 iterator,因为每种容器的迭代器底层实现可能都有所不同,即可能会为每一种容器都单独实现一个迭代器类,如果都直接使用 iterator,会导致命名冲突。其次,迭代器类不需要我们自己写析构函数、拷贝构造函数、赋值运算符重载函数,直接使用默认生成的就可以,言外之意就是这里使用浅拷贝即可,因为迭代器只是一种工具,它不需要对资源进行释放清理,资源释放清理工作是在容器类中实现的,浅拷贝的问题就出在会对同一块空间释放两次,而迭代器无需对空间进行释放,所以浅拷贝是满足我们需求的。
2.3.2、const迭代器
上面我们实现了普通迭代器,那 const 迭代器该如何实现呢?直接在容器类里面写上一句 typedef const _list_iterator const_iterator 可以嘛?答案是不可以,const 迭代器本质是限制迭代器指向的内容不能修改,而 const 迭代器自身可以修改,它可以指向其他节点。前面这种写法,const 限制的就是迭代器本身,会让迭代器无法实现 ++ 等操作。那如何控制迭代指向的内容不能修改呢?可以通过控制 operator* 的返回值来实现。但是仅仅只有返回值类型不同,是无法构成函数重载的。那要怎样才能在一个类里面实现两个 operator* 让他俩一个返回普通的 T&,一个返回 const T& 呢?一般人可能想着那就再单独写一个 _list_const_iterator 的类,这样也行,就是会比较冗余,我们可以通过在普通迭代器的基础上,再传递一个模板参数,让编译器来帮们生成呀。除此之外, operator->也需要实现 const 版本,因此还需要第三个模板参数。
template<class T,class Ref, class Ptr>
struct _list_iterator
{typedef ListNode<T> Node;typedef _list_iterator<T, Ref, Ptr> self;Node* _node;_list_iterator(Node* val){_node = val;}Ref operator* (){return _node->_val;}Ptr operator-> (){return &(_node->_val);}bool operator!= (const self& right) const{return _node != right._node;}bool operator== (const self& right) const{return _node == right._node;}self operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(this->_node);_node = _node->_next;return tmp;}self operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}
};
//operator->的使用场景
struct A
{A(int a = 0, int b = 0){_a = a;_b = b;}int _a;int _b;
};void Textlist3()
{wcy::list<A> l;l.push_back(A(1, 2));l.push_back(A(3, 4));l.push_back(A(5, 6));l.push_back(A(7, 8));wcy::list<A>::iterator it = l.begin();while (it != l.end()){cout << it->_a << ',' << it->_b << " ";cout << endl;it++;}
}
2.4、list的成员函数
2.4.1、构造函数
list()
{_head = new Node;_head->_prev = _head;_head->_next = _next;
}
2.4.2、拷贝构造函数
list(const list& ll)
//list(const list<T>& ll)
{_head = new Node;_head->_prev = _head;_head->_next = _head;for (auto& e : ll){push_back(e);}
}
2.4.3、赋值运算符重载
void swap(list<T> l2)
{std::swap(_head, l2._head);
}list& operator=(const list ll)
//list<T>& operator=(const list<T> ll)
{//现代写法swap(ll);return *this;
}
2.4.4、push_back
void push_back(const T& val)
{//先找尾Node* tail = _head;while (tail->_next != _head){tail = tail->_next;}//插入元素Node* newnode = new Node(val);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}
2.4.5、迭代器相关
iterator begin()
{return _head->_next;//单参数的构造函数支持隐式类型转换
}iterator end()
{return _head;
}const_iterator begin() const
{return _head->_next;//单参数的构造函数支持隐式类型转换
}const_iterator end() const
{return _head;
}
2.4.6、 insert
iterator insert(iterator pos, const T& val)
{//找到 pos 位置的前一个位置Node* cur = pos._node;Node* prev = cur->_prev;//插入元素Node* newnode = new Node(val);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return newnode;
}
2.4.7、erase
iterator erase(iterator pos)
{assert(pos != end());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.4.8、 push_front
void pop_back()
{erase(--end());
}
2.4.10、pop_front
void pop_front()
{erase(begin());
}
2.4.11、 size
size_t size()
{size_t sz = 0;iterator it = begin();while (it != end()){it++;sz++;}return sz;
}
2.4.12、clear
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}
2.4.13、析构函数
~list()
{clear();delete _head;_head = nullptr;
}
clear 和 析构函数的主要区别在于是否释放头节点。
相关文章:
【C++】——list的介绍和模拟实现
P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。 P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。 博主主页:Yan. yan. …...
B树系列解析
我最近开了几个专栏,诚信互三! > |||《算法专栏》::刷题教程来自网站《代码随想录》。||| > |||《C专栏》::记录我学习C的经历,看完你一定会有收获。||| > |||《Linux专栏》࿱…...
docker 部署 WEB IDE
简介 问题描述:GitCode 的 Web IDE 不满足个人使用需求 如何解决:在本机或云服务器部署 Web IDE 如何解决 拉取容器镜像 docker pull coder/code-server 运行 docker run -d --name vscode -p 8080:8080 -p 8443:8443 -e PASSWORD"123456&quo…...
【Android】数据存储
本章介绍Android五种主要存储方式的用法,包括共享参数SharedPreferences、数据库SQLite、SD卡文件、App的全局内存,另外介绍重要组件之一的应用Application的基本概念与常见用法,以及四大组件之一的内容提供器ContentProvider的基本概念与常见…...
个人网络安全的几个重点与防御
1 浏览器 firefox 这是第一选择 如果你真的不明白可以找找各个浏览器漏洞 mail 的危险的 来自与代理和漏洞 浏览器溢出漏洞 实时注意更新就可以 2 防火墙 大家都用windows 只需在 gpedit.msc 设置 但有什么未知漏洞就不得而知了 因为美国的计划问题 网络端口溢出漏洞 但…...
python爬虫 - 初识爬虫
🌈个人主页:https://blog.csdn.net/2401_86688088?typeblog 🔥 系列专栏:https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、爬虫的关键概念 (一)HTTP请求与响应 ࿰…...
tomcat版本升级导致的umask问题
文章目录 1、问题背景2、问题分析3、深入研究4、umask4.1、umask的工作原理4.2、umask的计算方式4.3、示例4.4、如何设置umask4.5、注意事项 1、问题背景 我们的java服务是打成war包放在tomcat容器里运行的,有一天我像往常一样去查看服务的日志文件,却提…...
Golang | Leetcode Golang题解之第455题分发饼干
题目: 题解: func findContentChildren(g []int, s []int) (ans int) {sort.Ints(g)sort.Ints(s)m, n : len(g), len(s)for i, j : 0, 0; i < m && j < n; i {for j < n && g[i] > s[j] {j}if j < n {ansj}}return }...
vscode+stfp插件,实现远程自动同步文件代码
概述 远程同步代码,将本地代码实时保存到同一局域网内的另一台电脑(linux系统),这里的本地代码也可以是远程服务上的代码,即从一个远程ip同步到另一台远程ip服务器。 工具 vscode,SFTP插件 安装 vscod…...
python 实现djb2哈希算法
djb2哈希算法介绍 DJB2哈希算法是一种简单且快速的哈希算法,由Daniel J. Bernstein设计。这种算法的实现非常简单,适用于短键值的哈希表,也常被用于嵌入式设备和资源受限的系统。 基本原理 DJB2算法的原理是将输入的字符串视为一个字节数组…...
文件夹作为普通文件而非子模块管理
relaxed_ik_ros2 文件夹下存在 .gitmodules 文件和 .gitignore 文件。这说明该目录已经被 Git 识别为子模块。 要将这个文件夹作为普通文件而非子模块管理,你可以按以下步骤操作: 1. 删除子模块配置 首先删除 .gitmodules 文件中的子模块配置。你可以…...
7c结构体
文章目录 一、结构体的设计二、结构体变量的初始化2.1结构体在内存表示;**2.2**结构体类型声明和 结构体变量的定义和初始化只声明结构体类型声明类型的同时定义变量p1用已有结构体类型定义结构体变量p2*定义变量的同时赋初值。*匿名声明结构体类型 2.3 结构体嵌套及…...
浅聊前后端分离开发和前后端不分离开发模式
1.先聊聊Web开发的开发框架Spring MVC 首先要知道,Spring MVC是Web开发领域的一个知名框架,可以开发基于请求-响应模式的Web应用。而Web开发的本质是遵循HTTP(Hyper Text Transfer Protocol: 超文本传输协议)协议【发请求…...
RabbitMQ篇(死信交换机)
目录 一、简介 二、TTL过期时间 三、应用场景 一、简介 当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter) 消费者使用basic.reject或者basic.nack声明消费失败,并且消息的requeue参数设置为false消息是一个过…...
HBase 的 MemStore 详解
一、MemStore 概述 MemStore 是 HBase 的内存存储区域,它是一个负责缓存数据写入操作的组件。每当有写操作(如 Put 或 Delete)发生时,数据会首先被写入到 MemStore 中,而不是直接写入磁盘。MemStore 类似于数据库中的缓…...
【嵌入式软件-数据结构与算法】01-数据结构
摘录于老师的教学课程~~(*๓╰╯๓)~~内含链表、队列、栈、循环队列等详细介绍~~ 基础知识系列 有空再继续更~~~ 目录 【链表】 一、单链表 1、存储结构:带头结点的单链表 2、单链表结点类型的定义 3、创建单链表 1)头插法 2)尾插法 …...
Windows应用开发-解析AVI视频文件
本Windows应用解析AVI视频文件,以表格的方式显示AVI文件结构。并可以将结果保存到bmp图片。下面是,使用该应用解析一部AVI电影获得的图片。 应用开发信息 定义一个INFO结构,包含两个字符串对象,一个ULONGLONG变量,和…...
探索TCP协议的奥秘:Python中的网络通信
引言 在网络通信的世界里,TCP协议(传输控制协议)就如同一座桥梁,连接着数据的发送方和接收方。作为一名拥有20年实战经验的编码专家,我深知TCP协议在构建稳定、可靠的网络应用中的重要性。今天,我将带领大…...
每日学习一个数据结构-树
文章目录 树的相关概念一、树的定义二、树的基本术语三、树的分类四、特殊类型的树五、树的遍历六、树的应用场景 树的遍历一、前序遍历二、中序遍历三、后序遍历使用java代码实现遍历总结 树的相关概念 树是一种重要的非线性数据结构,在计算机科学中有着广泛的应用…...
简单PCL库读文件(linux vscode编译)
#include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/common/common.h> #include <iostream>int main(int argc, char** argv) {if (argc ! 2) {std::cerr << "请指定 PCD 文件路径" << std::endl;return -…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
