当前位置: 首页 > article >正文

C++STL(六)——list模拟

目录

  • 本次所需实现的三个类
  • 一、结点类的模拟实现
    • 构造函数
  • 二、迭代器类的模拟实现
    • 为什么有迭代器类
    • 迭代器类的模板参数说明
    • 构造函数
    • ++运算符的重载
    • - -运算符的重载
    • ==和!=运算符的重载
    • *运算符的重载
    • ->运算符的重载
      • 引入模板第二个和第三个参数
  • 三、list的模拟实现
    • 3.1 默认成员函数
      • 构造函数
      • 拷贝构造函数
      • 赋值运算符重载函数
      • 析构函数
    • 3.2 迭代器相关函数
      • begin和end
    • 3.3 访问容器相关函数
      • front和back
    • 3.4 插入、删除函数
      • insert和erase
      • push_back和pop_back
      • push_front和pop_front
    • 3.5 其他函数
      • size
      • clear
      • empty
      • swap
  • 总结


本次所需实现的三个类

一、结点类的模拟实现

list类是由结点类和迭代器类组成

我们经常说list在底层实现时就是一个链表,更准确来说,list实际上是一个带头双向循环链表。
在这里插入图片描述

因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)。

而对于该结点类的成员函数来说,我们只需实现一个构造函数即可。因为该结点类只需要根据数据来构造一个结点即可,而结点的释放则由list的析构函数来完成。

构造函数

template<class T>				//由于该list可能是任何类型则使用模板类
struct list_node				//存放结点成员变量
{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T())		//缺省值初始化,T不一定是内置类型所以不能直接给0:_prev(nullptr), _next(nullptr), _val(val){}
};

注意: 若构造结点时没有传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。

二、迭代器类的模拟实现

引入:

list迭代器是一个自定义类型,内部成员是结点指针(内置类型),我们本身想要的就是结点指针,结点指针就可以做迭代器,它不能像原生指针(如:vector、string)一样地址是连续的而list地址不是,因为底层结构的差异,所以用一个类封装结点指针,然后重载运算符后就可以像内置类型一样访问。

为什么有迭代器类

在学习string和vector时都没有说专门要实现一个迭代器类,为什么实现list的时候就需要实现一个迭代器类呢?

因为string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针进行自增、自减以及解引用等操作,就可以对相应位置的数据进行一系列操作,因此string和vector当中的迭代器就是原生指针。

在这里插入图片描述

但是对于list来说,其各节点在内存中位置可能不都是连续的,大多情况下都是随机的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作,需找到下一个结点才能访问。
在这里插入图片描述迭代器的意义是让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。

既然list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符操作进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器。比如使用list当中的迭代器进行自增操作时,实际上执行了node = node->next语句。

list迭代器类实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为在使用者角度看起来和普通指针一样。

迭代器类的模板参数说明

list迭代器类的模板参数列表当中有三个模板参数

template<class T, class Ref, class Ptr>

在list的模拟实现当中,我们typedef重命名了两个迭代器类型,普通迭代器和const迭代器。

typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;

迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型。

构造函数

迭代器类实际上就是对结点指针进行了封装,其成员变量就只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造一个迭代器对象。

Node* _node;__list_iterator(Node* node):_node(node)
{}

++运算符的重载

当然也分为前置和后置++

self& operator++()			//返回类型还是迭代器
{_node = _node->_next;					//下一个位置return *this;
}self operator++(int)
{self<T> 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;
}

==和!=运算符的重载

当使用==运算符比较两个迭代器时,实际上想知道的是这两个迭代器是否是同一个位置的迭代器,判断这两个迭代器当中的结点指针的指向是否相同即可。!=运算符则相反。

//通过结点的指针比较,两数据比较时end返回的数据具有常性要加const
bool operator!=(const self& it)
{return _node != it._node;
}
bool operator==(const self& it)
{return _node == it._node;
}

*运算符的重载

使用解引用操作符时,是想得到该位置的数据内容。因此,直接返回当前结点指针所指结点的数据即可,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改。

Ref operator*()								//出了作用域还在可以引用返回
{return _node->_val;						//结点指针的数据
}

->运算符的重载

有些情景下我们使用迭代器的时候可能会用到->运算符。

void test2()
{struct A{A(int a=0,int b=0):_a(a),_b(b){}int _a;int _b;};ling::list<A> lt;lt.push_back(A(1,1));lt.push_back(A(2,2));lt.push_back(A(3,3));lt.push_back(A(4,4));ling::list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << *it << " ";					//遍历对象是自定义类型要重载流插入才可以打印//都能实现遍历//cout << (*it)._a << " "<<(*it)._b << endl;	cout << it->_a << " " << it->_b << endl;++it;}cout << endl;
}

对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可

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

引入模板第二个和第三个参数

如上例子:
在这里插入图片描述

三、list的模拟实现

3.1 默认成员函数

list是一个带头双向循环链表,在构造一个list对象时,申请一个头结点,并让其前驱指针和后继指针都指向自己
在这里插入图片描述
由于有时容易忘记写上显示实例化模板参数才构成类型

typedef list_node<T> node;				//重命名一下

构造函数

list()							//初始化构成双链表
{_head = new Node;			//给头节点申请空间head->_prev = head;			//前后指针指向自己head->_next = head;
}

拷贝构造函数

拷贝构造函数就是根据所给list容器,拷贝构造出一个对象。对于拷贝构造函数,先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面。

//拷贝构造函数
list(const list<T>& lt)
{_head = new node; 		//申请一个头结点_head->_next = _head; 	//头结点的后继指针指向自己_head->_prev = _head; 	//头结点的前驱指针指向自己for (const auto& e : lt)			//记得引用{push_back(e);		 //将容器lt当中的数据一个个尾插到新构造的容器后面}
}

赋值运算符重载函数

一般两种写法

//传统写法
list<T>& operator=(const list<T>& lt)
{if (this != &lt) 			//避免自己给自己赋值{clear(); 				//清空容器for (const auto& e : lt){push_back(e); 		//将容器lt当中的数据一个个尾插到链表后面}}return *this; 				//支持连续赋值
}//现代写法
list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{swap(lt); 					//交换这两个对象return *this; 				//支持连续赋值
}

析构函数

对象进行析构时,首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空

//析构函数
~list()
{clear(); 			//清理容器delete _head; 		//释放头结点_head = nullptr; 	//头指针置空
}

3.2 迭代器相关函数

begin和end

begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。

list带头双向循环链表,第一个有效数据的迭代器就是使用头结点的下一个结点的地址构造出来的迭代器,最后一个有效数据的下一个位置的迭代器就是使用头结点的地址构造出来的迭代器。(最后一个结点的下一个结点就是头结点)

//单参数的构造函数支持隐式类型转换,两种写法都可以都是生成匿名对象
iterator begin()					
{return _head->_next;//return iterator(_head->_next);
}
iterator end()
{return _head;//return iterator(_head);
}

3.3 访问容器相关函数

front和back

分别用于获取第一个有效数据和最后一个有效数据,因此,实现front和back函数时,直接返回第一个有效数据和最后一个有效数据的引用即可。

T& front()
{return *begin(); 		//返回第一个有效数据的引用
}
T& back()
{return *(--end()); 		//返回最后一个有效数据的引用
}//不可修改
const T& front() const
{return *begin(); 		//返回第一个有效数据的const引用
}
const T& back() const
{return *(--end()); 		//返回最后一个有效数据的const引用
}

3.4 插入、删除函数

insert和erase

当然还是任意位置插入和删除,由于会迭代器失效所以有返回值

//插入
iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;
}//删除
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;return next;
}

有了这对增删函数就可以复用在其它增删的函数身上了

push_back和pop_back

分别对应尾插、尾删

//尾插
void push_back(const T& x)
{Node* tail = _head->prev;				//找到尾Node* newnode = new Node(x);			//调用结点的构造函数//更新尾结点tail->_next = newnode;newnode->_prev = tail;_head->_prev = newnode;newnode->_next = _head;//可直接复用insertinsert(end(),x);
}//尾删
void pop_back()
{erase(--end());
}

push_front和pop_front

//头插
void push_front(const T& x)
{insert(begin(), x);
}//头删
void pop_front()
{erase(begin());
}

3.5 其他函数

size

获取当前容器当中的有效数据个数,因为list是链表,所以只能通过遍历的方式逐个统计有效数据的个数。

size_t size()
{size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;
}

clear

用于清空容器,通过遍历的方式,逐个删除结点,只保留头结点

void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}

empty

用于判断容器是否为空,直接判断该容器的begin函数和end函数所返回的迭代器,是否是同一个位置的迭代器即可。(此时说明容器当中只有一个头结点)

bool empty() const
{return begin() == end(); //判断是否只有头结点
}

swap

用于交换两个容器,list容器当中存储的实际上就只有链表的头指针,将这两个容器当中的头指针交换即可。

void swap(list<T>& lt)
{std::swap(_head, lt._head); 		//交换两个容器当中的头指针即可
}

总结

由于list类不一定只接收一个类型如:内置类型(int、double),自定义类型,所以三个类都使用了模板。
类名+模板参数才构成类型容易忘记,往往typedef重命名一下它们也更方便使用。
在这里插入图片描述

相关文章:

C++STL(六)——list模拟

目录 本次所需实现的三个类一、结点类的模拟实现构造函数 二、迭代器类的模拟实现为什么有迭代器类迭代器类的模板参数说明构造函数运算符的重载- -运算符的重载和!运算符的重载*运算符的重载->运算符的重载引入模板第二个和第三个参数 三、list的模拟实现3.1 默认成员函数构…...

HTML5--网页前端编程(下)

HTML5–网页前端编程(下) 9.常用标签下 (1)表格标签 用来展示数据,显示数据,规整条理,可读性好 基本语法 <table><tr> <td>单元格内的文字</td> <td>单元格内的文字</td>… </tr> <tr> <td>单元格内的文字&l…...

Spring 的 ResponseEntity 包装器使用详解

简介 在 Spring 中&#xff0c;ResponseEntity 是 HTTP 响应的包装器。它允许自定义响应的各个方面&#xff1a; HTTP 状态码 响应主体 HTTP 请求头 使用 ResponseEntity 允许完全控制 HTTP 响应&#xff0c;并且它通常用于 RESTful Web 服务中从控制器方法返回响应。 基…...

Git 分布式版本控制工具使用教程

1.关于Git 1.1 什么是Git Git是一款免费、开源的分布式版本控制工具&#xff0c;由Linux创始人Linus Torvalds于2005年开发。它被设计用来处理从很小到非常大的项目&#xff0c;速度和效率都非常高。Git允许多个开发者几乎同时处理同一个项目而不会互相干扰&#xff0c;并且在…...

linux部署ollama+deepseek+dify

Ollama 下载源码 curl -L https://ollama.com/download/ollama-linux-amd64.tgz -o ollama-linux-amd64.tgz sudo tar -C /usr -xzf ollama-linux-amd64.tgz启动 export OLLAMA_HOST0.0.0.0:11434 ollama serve访问ip:11434看到即成功 Ollama is running 手动安装deepseek…...

torch_bmm验算及代码测试

文章目录 1. torch_bmm2. pytorch源码 1. torch_bmm torch.bmm的作用是基于batch_size的矩阵乘法,torch.bmm的作用是对应batch位置的矩阵相乘&#xff0c;比如&#xff0c; mat1的第1个位置和mat2的第1个位置进行矩阵相乘得到mat3的第1个位置mat1的第2个位置和mat2的第2个位置…...

Vue3 特点

不强制要求组件有根节点 // vue2 <template><div><h1>标题</h1><p>内容</p></div> </template>// vue3 <template><h1>标题</h1><p>内容</p> </template> 注意事项 虽然 Vue 3 不再强制…...

mysql8 C++源码中创建表函数,表字段最大数量限制,表行最大存储限制

在 MySQL 8 的 C 源码中&#xff0c;表的最大字段数量限制体现在 MAX_FIELDS 宏定义中。这个宏定义了表中可以拥有的最大字段数量。 代码中的体现 在 mysql_prepare_create_table 函数中&#xff0c;有以下代码段检查表的字段数量是否超过最大限制&#xff1a; cpp if (alt…...

CTFHub-RCE系列wp

目录标题 引言什么是RCE漏洞 eval执行文件包含文件包含php://input读取源代码远程包含 命令注入无过滤过滤cat过滤空格过滤目录分隔符过滤运算符综合过滤练习 引言 题目共有如下类型 什么是RCE漏洞 RCE漏洞&#xff0c;全称是Remote Code Execution漏洞&#xff0c;翻译成中文…...

【OneAPI】通过网页预渲染让搜索引擎收录网页

API简介 网页预渲染&#xff0c;适用于动态网页以及单页面的SEO&#xff0c;支持网页缓存。 您无须更改代码即可让搜索引擎收录您的网页。只要将需要预渲染的页面转发的本接口即可。 如果您使用Nginx作为网页服务器&#xff0c;推荐使用以下配置&#xff1a; #您的网站locat…...

从大规模恶意攻击 DeepSeek 事件看 AI 创新隐忧:安全可观测体系建设刻不容缓

作者&#xff1a;羿莉&#xff08;萧羿&#xff09; 全球出圈的中国大模型 DeepSeek 作为一款革命性的大型语言模型&#xff0c;以其卓越的自然语言处理能力和创新性成本控制引领行业前沿。该模型不仅在性能上媲美 OpenAI-o1&#xff0c;而且在推理模型的成本优化上实现了突破…...

【学习笔记】企业数字化转型顶层设计与企业架构TOGAF9.2-第0章 导论

数据要素资产化迈入关键发展期 围绕发挥数据要素乘数作用&#xff0c;研究实施“数据要素x”行动:从供需两端发力&#xff0c;在智能制造、商贸流通、交通物流、金融服务、医疗健康等若干重点领域&#xff0c;加强场景需求牵引&#xff0c;打通流通障碍、提升供给质量&#xf…...

Vue3 Ref全家桶深度解析:掌握响应式编程精髓

Vue3 Ref全家桶深度解析&#xff1a;掌握响应式编程精髓 一、Ref核心概念 1.1 响应式数据容器 const count ref(0) // 相当于创建了一个响应式容器&#xff1a; {value: 0,__v_isRef: true,// 其他响应式系统属性 }1.2 全家桶全景图 #mermaid-svg-VkHPjjlo16rOyItj {font-f…...

如何避免大语言模型中涉及丢番图方程的问题

希尔伯特第十问题是一个著名的数学问题,涉及不定方程(又称为丢番图方程)的可解答性。然而在大模型中,我们希望问题都是确定的可解的,或者说要尽可能的想办法避免不确定的不可解问题。由于丢番图方程问题是不可判定问题(即不存在一个有效的算法能够解决该类问题的所有实例…...

SpringCloud - Sentinel服务保护

前言 该博客为Sentinel学习笔记&#xff0c;主要目的是为了帮助后期快速复习使用 学习视频&#xff1a;7小快速通关SpringCloud 辅助文档&#xff1a;SpringCloud快速通关 源码地址&#xff1a;cloud-demo 一、简介 官网&#xff1a;https://sentinelguard.io/zh-cn/index.h…...

Java 使用腾讯翻译 API 实现含 HTML 标签文本,json值,精准翻译工具

注意&#xff1a;需搭配标题二的腾讯翻译工具使用 一-1、翻译标签文本工具 package org.springblade.common.utils;import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern;public class TencentTranslationFor…...

前端导出pdf,所见即所得

一、推荐方案&#xff1a;html2canvas jsPDF&#xff08;图片式PDF&#xff09; javascript import html2canvas from html2canvas; import jsPDF from jspdf;const exportPDF async (elementId, fileName) > {const element document.getElementById(elementId);// 1.…...

单片机上SPI和IIC的区别

SPI&#xff08;Serial Peripheral Interface&#xff09;和IC&#xff08;Inter-Integrated Circuit&#xff09;是两种常用的嵌入式外设通信协议&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的详细对比&#xff1a; — 1. 基本概念 SPI&#xff0…...

03-DevOps-安装并初始化Gitlab

Gitlab可以理解为是自己搭建的GitHub&#xff0c;也就是自己的代码仓库。 开启macvlan 在192.168.1.10服务器上&#xff0c;构建Macvlan网络&#xff0c;这种网络模式可以为每个容器独立分配ip。 docker network create -d macvlan \--subnet192.168.1.0/24 \--ip-range192.16…...

RabbitMQ 从入门到精通:从工作模式到集群部署实战(五)

#作者&#xff1a;闫乾苓 系列前几篇&#xff1a; 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;一&#xff09;》&#xff1a;link 《RabbitMQ 从入门到精通&#xff1a;从工作模式到集群部署实战&#xff08;二&#xff09;》&#xff1a; lin…...

DFS+回溯+剪枝(深度优先搜索)——搜索算法

DFS也就是深度优先搜索&#xff0c;比如二叉树的前&#xff0c;中&#xff0c;后序遍历都属于DFS。其本质是递归&#xff0c;要学好DFS首先需要掌握递归。接下来咱们就一起来学习DFS涉及的算法。 一、递归 1.什么是递归&#xff1f; 递归可以这样理解把它拆分出来&#xff0…...

使用PyCharm创建项目以及如何注释代码

创建好项目后会出现如下图所示的画面&#xff0c;我们可以通过在项目文件夹上点击鼠标右键&#xff0c;选择“New”菜单下的“Python File”来创建一个 Python 文件&#xff0c;在给文件命名时建议使用英文字母和下划线的组合&#xff0c;创建好的 Python 文件会自动打开&#…...

ArrayList和LinkedList有什么区别?在什么情况下使用ArrayList更高效?

ArrayList和LinkedList在Java中是两种常用的数据结构&#xff0c;分别基于数组和链表实现。它们在性能、内存使用和适用场景上各有特点。 ArrayList与LinkedList的主要区别 数据结构&#xff1a; ArrayList&#xff1a;基于动态数组实现&#xff0c;元素存储在连续的内存空间…...

Spring MVC 拦截器(Interceptor)与过滤器(Filter)的区别?

1、两者概述 拦截器&#xff08;Interceptor&#xff09;&#xff1a; 只会拦截那些被 Controller 或 RestController 标注的类中的方法处理的请求&#xff0c;也就是那些由 Spring MVC 调度的请求。过滤器&#xff08;Filter&#xff09;&#xff1a; 会拦截所有类型的 HTTP …...

elasticsearch实战应用从入门到高效使用java集成es快速上手

Elasticsearch 因其出色的性能、可扩展性和易用性,成为了处理大规模数据和构建搜索引擎的首选工具。本文将通过一个实际案例,详细讲解如何在 Spring Boot 项目中集成 Elasticsearch,进行数据索引、搜索、聚合分析等操作。 一、Elasticsearch 简介 Elasticsearch 是一个基于…...

Spring Boot 整合 JPA 实现数据持久化

目录 前言 一、JPA 核心概念与实体映射 1. 什么是 JPA&#xff1f; 2. JPA 的主要组件 3. 实体映射 4. 常见的字段映射策略 二、Repository 接口与自定义查询 1. 什么是 Repository 接口&#xff1f; 2. 动态查询方法 3. 自定义查询 4. 分页与排序 三、实战案例&…...

如何优化网站结构以促进快速收录?

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/104.html 优化网站结构以促进快速收录&#xff0c;可以从以下几个方面入手&#xff1a; 一、合理规划页面结构 扁平化结构&#xff1a;采用扁平化的网站结构&#xff0c;减少层级&#xf…...

【零基础学Mysql】常用函数讲解,提升数据操作效率的利器

以耳倾听世间繁华&#xff0c;以语表达心中所想 大家好,我是whisperrrr. 前言&#xff1a; 大家好&#xff0c;我是你们的朋友whisrrr。在日常工作中&#xff0c;MySQL作为一款广泛使用的开源关系型数据库&#xff0c;其强大的功能为我们提供了便捷的数据存储和管理手段。而在…...

防火墙安全综合实验

防火墙安全综合实验 一、拓扑信息 二、需求及配置 实验步骤 需求一&#xff1a;根据下表&#xff0c;完成相关配置 设备接口VLAN接口类型SW2GE0/0/2VLAN 10AccessGE0/0/3VLAN 20AccessGE0/0/1VLAN List&#xff1a;10 20Trunk 1、创建vlan10和vlan20 2、将接口划分到对应…...

在Linux上创建虚拟网卡

在 Linux 上创建虚拟网卡可以通过多种方式进行&#xff0c;常见的方式是使用 ip 命令来配置虚拟网卡。以下是一个简单的步骤指南&#xff0c;用于创建虚拟网卡&#xff1a; 步骤 1: 查看现有的网络接口 首先&#xff0c;查看当前网络接口的状态&#xff0c;可以使用以下命令&…...