STL之my_list容器

devtools/2024/9/25 5:36:26/

listvectorvectorlistmy_list_0">前言:各位老铁好久不见了,今天分享的知识是自己实现一个简单的list容器,为什么我先跳过vector容器的自我实现呢?我个人觉得vector相对于list的自我实现简单一点,所以今天先分享实现my_list的知识

listlistlist_2">我们要实现my_list,首先我们要知道list有那些常用的接口,我们先看一看实现list的文档

list/list/?kw=list" rel="nofollow">实现list容器文档入口

在这里插入图片描述
list容器有这么多接口,我会从里面挑出常用的接口进行模拟实现。

list_7">list实现(这里讲的是带头的双向循环链表)

listlistlistmy_list_8">我们在数据结果中已经学习过list了,知道list内部结果主要包含三部分,一是存放的值,二是链接前面一个结点的指针,三是链接后面一个结点的指针。由于库里面也有已经实现好了的list,为了防止my_list和库里面的冲突,我们需要搞个命名空间封装起来,命名空间的名字随便用。

namespace ljy
{}

listlistlist_16">由于我们不知道list容器的类型是什么类型,所以我们需要搞个模板函数,对任意类型的list都适用。然后我们需要搞个类(私有的比较好,防止其他人修改成员变量)把list里面的成员给封装起来

namespace ljy
{template<class T>struct _list_node{_list_node<T>* _prev;_list_node<T>* _next;T _data;//任意类型的数据};
}

接下来我们需要把链表给初始化

namespace ljy
{template<class T>struct _list_node{_list_node<T>* _prev;_list_node<T>* _next;T _data;//任意类型的数据_list_node(const T& x = T())//这里是构造一个空的对象来充当缺省参数,防止没有提供参数从而导致编译器报错:_data(x), _prev(nullptr), _next(nullptr){}};
}

listlistlist_50">由于list不支持随机访问,我们只能通过迭代器进行访问list中的结点。所以我们先来实现list的迭代器的接口。

由于list迭代器底层是一个指针,所以我们如果需要访问list和修改list,就需要对指针进行解引用和取地址,因此我给list迭代器模板参数定义三个参数,一个是有关数据类型(T),一个是解引用(Ref),一个是取地址(Ptr)

//迭代器
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迭代器进行初始化

_list_iterator(Node* node):_node(node)
{}

我们需要分清对指针的*和->,一个是对指针解引用(表示取向指针指向的值),另一个是对指针取地址(表示指向结点的本身)

	//*Ref operator*(){return _node->_data;}
//->
Ptr operator->()
{return &_node->_data;
}

再接下来我们实现++和- -运算符的重载++和- - 分为前置++,前置- -,后置++和后置- -。前置和后置的区别是是否先返回值再进行++和- -

前置++

//前置++
Self& operator++()//返回的是this指针,出了作用域还存在,所以用引用返回
{_node = _node->_next;return *this;
}

后置++

//后置++       返回的是中间变量,出了作用域就不存在了,不需要使用引用返回
Self operator++(int)//在形参里面+int是为了和前置++区别开来。
{Self tmp(this);++(*this);return tmp;
}

前置- -

//前置--
Self& operator--()
{_node = _node->_prev;return *this;
}

后置- -

//后置--
Self operator--(int)
{Self tmp(this);_node = _node->_prev;return tmp;
}

然后再实现==和!=运算符重载

==运算符重载

bool operator==(const Self& lt)
{return _node == lt._node;
}

!=运算符重载

bool operator!=(const Self& lt)
{return _node != lt._node;
}

到这里list的迭代器就完成了。

list_154">接下来就到list的一些常用接口的实现

list的框架

template <class T>class list{typedef _list_node<T> Node;public:typedef _list_iterator<T,T*,T&> iterator;//普通迭代器typedef _list_iterator<T, T*, T&> const_iterator;//const迭代器private:Node* _head;};

返回头节点

//双向带头的循环链表(可修改头节点的位置和头节点指向的值)
iterator begin()
{//开始的结点在头节点的下一个结点//通过迭代器迭代寻找结点return iterator(_head->_next);
}
//通过const迭代器迭代寻找结点(不可修改头节点的位置和头节点指向的值)
const_iterator begin() const
{return const_iterator(_head->_next);
}

//双向带头的循环链表(可修改尾节点的位置和尾节点指向的值)

	iterator end(){//头结点指向的位置就是end位置return iterator(_head);}

//通过const迭代器迭代寻找结点(不可修改尾节点的位置和尾节点指向的值)

const_iterator end() const
{return const_iterator(_head);
}

然后实现在任意位置插入数据的接口

//在pos位置前插入数据void insert(iterator pos,const T& x){//先找出当前的结点Node* cur = pos._node;Node* prev = cur->_prev;//再开辟出一个新的结点Node* newnode = new Node(x);//再把prev newnode cur三个结点按前后顺序链接起来prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;}

再在任意插入位置插入数据的接口基础上实现头插和尾插

头插:就是在begin前一个位置插入

//头插
void push_front(const T& x)
{//直接在begin()前面的位置进行插入insert(begin(), x);
}

尾插:就是在头结点前一个位置进行插入,头节点前一个位置就是末尾
在这里插入图片描述

	//从尾部插入数据void push_back(const T& x){//end()就是头节点的前一个位置,也就是尾部return (end(), x);}

到这里插入数据的接口我们就实现完了,有插入数据必然就会有删除数据,所以接下来我们就开始实现删除数据。

首先我们先实现在任意位置删除数据,在文档中erase删除数据是将数据删除后返回当前数据的下一个位置。
在这里插入图片描述
在这里插入图片描述

	//在任意位置删除数据void erase(iterator pos, const T& x){//不能把头节点删除assert(pos != end());//先保存当前结点Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;//再把结点链接起来prev->_next = next;next->_prev = prev;}

然后在在任意位置删除数据的接口的基础上,再实现头删和尾删。

头删:begin()函数返回的位置就是头

void pop_front(const T& x)
{erase(begin(), x);
}

尾删:end()函数获取的位置是尾结点的下一个位置,只要先给end()函数- -就能找到尾结点从而删掉尾结点。

//尾删
void pop_back(const T& x)
{erase(--end());
}

listlist_293">list的删除数据接口到这里就完成了,然后我们再给list写初始化函数(个人习惯,先写完接口再写初始化),构造函数,拷贝构造函数,赋值运算符重载,析构函数。

构造函数:由于我们我们这里的list是双向带头循环的链表,所以我们需要先开辟一个头节点出来,把头结点和自身链接起来。
在这里插入图片描述

list()
{//new出一个头结点_head = new Node;//让头节点指向自己_head->_next = _head;_head->_prev = _head;
}

拷贝构造函数:

//拷贝构造
//lt2(lt1)
list(const list<T>& lt)//传一个类对象过来
{//先构造出一个和lt一模一样的头节点,然后再直接把lt的数据插入到lt2中_head = new Node;_head->_next = _head;_head->_prev = _head;for (auto e : lt){push_back(e);//这里其实是this->push_back()}
}

赋值运算符重载(现代写法:借助第三方实现相对应的功能,再把原来的和第三方进行交换)

//operator=
//lt3=lt2
list<T>& operator=(list<T> lt)//这里创建对象借助了拷贝构造,lt2通过lt1进行拷贝构造
{//直接把lt3和lt2的头结点进行交换就可以了swap(_head = lt._head);//再返回this指针return *this;
}

listlistlistlist_342">总结:这次的文章分享list容器的底层代码知识,让我们懂得如何实现list容器的一些常用接口,希望我们能通过理解list容器的底层代码,从而加深对list容器的使用的理解。


http://www.ppmy.cn/devtools/105569.html

相关文章

2023年最新JavaScript 基础面试题

高级题目 实现一个异步队列 编写一个异步队列&#xff0c;支持添加任务和批量执行任务的功能。每个任务都是一个异步函数&#xff0c;队列需要保证任务按照添加的顺序执行。 class AsyncQueue { constructor() { // 你的代码 } enqueue(task) { // 你的代码 } start()…

【leetcode刷题记录】二叉树遍历

中序遍历 public List<Integer> inorderTraversal(TreeNode root) { // List<Integer> result new ArrayList<>(); // midAccTree(result,root); // return result;//栈迭代解决List<Integer> result new ArrayList<>()…

Vue学习

概述 Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09;是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 特点 Vue的核心是一个响应的数据绑定系统&#xff0c;它通过把标签和数据绑定来简化用户的操…

域名证书,泛域名证书,sni

文章目录 前言一、证书1.全域名证书2.泛域名证书 二、域名证书的使用1、浏览器请求域名证书流程对全域名证书的请求流程对泛域名证书的请求流程ssl client-hello携带server name 报文 2、浏览器对证书的验证流程 三、域名证书和sni 前言 本文介绍了泛域名证书和全域名证书的区别…

Spring Boot 入门

1.1.1 什么是Spring Boot Spring Boot是一个开源的Java应用框架&#xff0c;由Pivotal团队提供&#xff0c;旨在简化Spring应用的初始搭建以及开发过程。‌ Spring Boot通过使用特定的配置方式&#xff0c;使得开发人员不再需要定义样板化的配置&#xff0c;从而在快速应用开发…

使用 Pandas 进行数据可视化:全面指南(六)

在数据分析的过程中,数据的可视化是一个至关重要的环节。通过图形展示数据,不仅能够帮助我们直观地理解数据,还能够揭示数据背后的规律和趋势。Pandas 作为 Python 生态系统中强大的数据分析库,不仅提供了数据处理和分析的功能,还内置了方便易用的可视化方法。本文将详细介…

k8s-pod 实战六 (如何在不同的部署环境中调整startupprobe的参数?)

在不同的部署环境中(如开发、测试、生产环境),你可能希望对 startupProbe 的参数进行调整,以适应不同的需求和条件。以下是几种常见的方法和实践: 方法一:使用 Kustomize 1. 目录结构 假设你的项目目录结构如下: my-app/ ├── base/ │ └── deployment.yaml …

C++单例模式

文章目录 设计模式单例模式饿汉模式懒汉模式 设计模式 设计模式&#xff08;Design Pattern&#xff09;是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的&#xff1a;为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模…