STL容器-- list的模拟实现(附源码)

STL容器-- list的模拟实现(附源码)

List的实现主要时考察我们对list这一容器的理解,和代码的编写能力,通过上节对list容器的使用,我们对list容器已经有了一些基本的了解·,接下来就让我们来实现一些list容器常见的功能函数来加深我们对c++工程的理解:

在这里插入图片描述

一、基本框架的搭建

首先我们需要将基本的框架搭建好, 从之前我们学数据结构时,对list的一些认识并结合c++ stl标准可以得出:

  • 定义节点的构造
  • 明确指针的指向
  • list的封装
    根据c++中的便准我们可以的位置,list时一个双向迭代器, 所以对应的, 我们需要定义的双向循环链表:

1.1节点的构造

template <class T>
struct ListNode
{T _data;ListNode<T>* _next;ListNode<T>* _prev;// 节点的构造函数ListNode(const T& val):_data(val),_next(nullptr),_prev(nullptr){}
};

在定义一个节点的时候,我们先让这个节点的_next and _prev指针为空

1.2 list的封装

    
template <class T>
struct ListNode
{T _data;ListNode<T>* _next;ListNode<T>* _prev;// 节点的构造函数ListNode(const T& val = T()):_data(val),_next(nullptr),_prev(nullptr){}
};template<class T>
class list
{
public:typedef ListNode<T> node;void empty_init(){// zdl :: 在初始化一个空链表时, 先定义一个哨兵位_head = new node();_head->_next = _head;_head->_prev = _head;}list(){empty_init();}// functon define:}private:node* _head;
};

同时为了方式访问冲突,我们可以将我们自己实现的类,放在我们自己定义的命名域中:

namespace zdl
{template <class T>struct ListNode{T _data;ListNode<T>* _next;ListNode<T>* _prev;// 节点的构造函数ListNode(const T& val):_data(val),_next(nullptr),_prev(nullptr){}};template <class T>struct ListNode{T _data;ListNode<T>* _next;ListNode<T>* _prev;// 节点的构造函数ListNode(const T& val = T()):_data(val),_next(nullptr),_prev(nullptr){}};template<class T>class list{public:typedef ListNode<T> node;void empty_init(){// zdl :: 在初始化一个空链表时, 先定义一个哨兵位_head = new node();_head->_next = _head;_head->_prev = _head;}list(){empty_init();}// functon define:}private:node* _head;};
}

这样list的基本框架就搭建好了!!

二、节点的尾插和头插

2.1 节点的头插 (push_back)

在这里插入图片描述

通过前面数据结构的学习。我们已经清楚了链表的结构,在进行数据尾插时, 就只是在改变指针的指向罢了:

void push_back(const T& val){node* creat = new node(val);node* tail = _head->_prev;tail->_next = creat;creat->_prev = tail;creat->_next = _head;_head->_prev = creat; }

2.2 节点的头插(push_front)

节点的头插和尾插十分的相似,
这里我们就直接展示一下代码:

void push_front(const T& val){node* fnode = new node(val);fnode->_next = _head->_next;fnode->_prev = _head;_head->_next->_prev = fnode;_head->_next = fnode;}

2.3 数据插入验证

为了方便起见, 我这里再再这个类里面定义一个打印链表的函数:

 void Print_list(){node* cur = _head->_next;while (cur != _head){cout << cur->_data << " ";cur = cur->_next;}cout << endl;}

完成了list容器的头插和尾插操作接下来我们可以来验证一下,我们的函数实现是否有问题:

#include"list.h"int main()
{zdl::list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_front(3);l1.Print_list();return 0;
}

通过运行可知, 这里的函数没有问题:

在这里插入图片描述

三、list迭代器的实现

list迭代器的功能和vetor的功能有很多相似的地方,但是二者在底层实现时,使用的不同的方法:
基于,list迭代器的特殊性质,我们会采用类封装的方式来满足list_iterator的特殊性质:

3.1迭代器基本功能的实现和封装处理

template<class T>struct list_iterator{typedef ListNode<T> node;typedef list_iterator<T> self;node* _nd;list_iterator(node* nd):_nd(nd){}// zdl:: 实现迭代器的基本功能:// 解引用数值T& operator*(){return _nd->_data;}// zdl:: 前置++ / --self& operator++(){_nd = _nd->_next;return *this;}self& operator--(){_nd = _nd->_prev;return *this;}// zdl:: 后置 ++ / --self operator++(int){self tmp = _nd;_nd = _nd->_next;return tmp;}self operator--(int){self tmp = _nd;_nd = _nd->_prev;return tmp;}// zdl:: != 运算bool operator!=(const self& s){return _nd != s._nd;}};

实现了list_iterator的功能后,我们就只需要将他封装道list类中就可以正常使用了:
使用typedef 封装迭器
在这里插入图片描述
基本的逻辑都是十分的简单的接下来我们来简单的验证一代码是否可行:
在这里插入图片描述

3.2 对结构体(类)的解引用

->运算符的重载

大家现在可以想象一下这样的场景,假设我现在在llist中存储的不是基础类型的元素,而是类等较为复杂的对象时,我们应当怎么正常的使用这个类对象的成员呢?
这时,我们就可以考虑对->运算符重载, 通过返回对象的地址,来访问成员变量和成员函数等
下面我们就来举个例子:
假设我现在我要在list中存储pos类:

struct pos
{int row;int col;//构造函数pos(const int& x = 0, const int& y = 0):row(x),col(y){}//成员函数void pos_show(){printf("(%d, %d) ",row, col);}
};

我们就只需要重载->运算符:

T* operator->()
{return &_nd->_data;
}

运行后可以得到:
在这里插入图片描述
但是大家可能会觉得很奇怪,通过->重载,返回的只是_data的地址,为什么能直接访问到元素呢?不应该使用it->->解引用两次才行啊?
这个其实就只是c++便准下为我们提供的特殊语法, 通过这样的规定使我们能够直接访问到元素, 当然c++也是支持这样的写法的:
在这里插入图片描述
但是不支持这样的写法:
在这里插入图片描述

3.3const迭代器的实现与模板参数的巧用

前面我们已经实现了,可读可写的迭代器,现在我们就来实现一下只读迭代器:
const iterator,
其实从功能上看,这个迭代器与原来的迭代器十分的相似,只是不能对指向对象的值进行修改,因此我们就只需要对* 和 ->运算符重载的时候稍加修改就可以得到我们想要的结果,即再创建一个类:
在这里插入图片描述
其他的共同功能粗需要改动,只需要改动下面两个重载函数:

 const T& operator*()
{return _nd->_data;
}
const T* operator->()
{return &_nd->_data;
}

紧接着我们还需要在llist类中实现const对象专用的begin()、end()
在这里插入图片描述
在这里插入图片描述

接下来我们来验证一下效果:
在这里插入图片描述
现在consr iterator也实现好了,但是这里依然还存在问题,这样我们将相当于为迭代器实现了两个类,这两个类的攻击能还高度重合,这并没有,体现出模板函数的简洁性,因此我们还可以通过其他的方式来优化我们现在的代码。
通过对源码的分析,参照我们或许可以从中得到一些启发, 从源码中可知我们可以得知,通过对模板参数的巧用就可以的实现代码的简化:

在这里插入图片描述
接下来我们就只需要将代码稍加改动就可以实现我们的目的:
在这里插入图片描述
在这里插入图片描述
接下来,我们再次运行看看是否可以达到简化代码的效果:
在这里插入图片描述
可以发现现在的代码依然有效,代码简化成功!!

四、丰富构造函数、list增删查改的实现

现在我们已经完成了list的简单构造函数,现在我们就可以参照 c++ library完成其他的构造函数:

4.1 list(size_t n, const T& x = T())

我们要实现的这个函数和标准库中的一样,并且直接复用我峨嵋你已经实现的函数就好了:

list(size_t n, const T& x = T())
{for (int i = 0; i < n; i++){push_back(x);}
}

直接来演示一下的效果:
在这里插入图片描述

4.2 拷贝构造

我们就只需要将被拷贝链表的元素一个一个的拷贝进链表就行了:

list(const list<T>& l1)
{empty_init();for (auto& e : l1) push_back(e);
}

我们来测试一下效果:
在这里插入图片描述

4.3 插入函数的实现(insert)

想要在这个双向链表中插入节点,我们就需要

  • 待插入的值

  • 待插入位置

所以,insert函数定义为: void insert(iterator pos, const T& val = T())

iterator insert(iterator pos, const T& val = T())
{node* cur = pos._nd;node* prev = pos._nd->_prev;node* insrt = new node(val);prev->_next = insrt;insrt->_prev = prev;insrt->_next = cur;cur->_prev = insrt;return iterator(cur->_prev);}

这个函数也十分的简单如果,有的uu还没有接触过链表,或者是已经忘了链表的增删查改,可以移步去看看我之前的博客:链表的介绍

4.4链表的删除(earse)

关于erase和函数的定义,我们就只需要拿到需要删除的位置就可以了, 所以定义为:void erase(iterator pos)

iterator erase(iterator pos)
{assert(pos != end()); //! 注意不能够将哨兵位删除!!node* cur = pos._nd;node* prev = cur->_prev;prev->_next = cur->_next;cur->_next->_prev = prev;delete cur;return iterator(prev->_next);}

现在我们就直接来测试一下这个函数是否可以满足我们的要求:
在这里插入图片描述
由此可知我们实现的都没有问题。

4.5链表元素的查找(find)

定义find函数时,我们就只需要给函数一个特定的需要查找到的值,然后使这个函数返回这个元素的位置(迭代器

iterator find(const T& val)
{auto it = begin();while (it != end() && *it != val){it++;}if (*it == val) return it;return nullptr;}

在这里插入图片描述

4.6 头删和尾删操作

前面我们已经实现了earse 现在我们就只需要对这个和拿书尽心你给复用就好了:

void pop_front()
{erase(++end());
}
void pop_back()
{erase(--end());
}

接下来我们就直接来演示这个函数是否有用:

在这里插入图片描述

五、析构函数与clear函数

最后我们就来实现一下list的析构函数,我们还是继续函数的复用:

// zdl:: 析构类函数的实现:~list(){// ! 不仅要将所有的数值删除还需要将哨兵位也清除!clear();delete _head;_head = nullptr;            }void clear(){// !! 不能删除哨兵位,就只是将所有的数值清空。auto it = begin();while (it != end()) it = erase(it);}

现在我们就将已有的链表清空试试:
在这里插入图片描述

六、代码展示

list.h

#pragma once #include<iostream>
#include<cassert>
using  namespace std;
namespace zdl
{template <class T>struct ListNode{T _data;ListNode<T>* _next;ListNode<T>* _prev;// 节点的构造函数ListNode(const T& val = T()):_data(val),_next(nullptr),_prev(nullptr){}};template<class T, class Ref, class Ptr>struct list_iterator{typedef ListNode<T> node;typedef list_iterator<T, Ref, Ptr> self;node* _nd;list_iterator(node* nd):_nd(nd){}// zdl:: 实现迭代器的基本功能:// 解引用数值Ref operator*(){return _nd->_data;}Ptr operator->(){return &_nd->_data;}// zdl:: 前置++ / --self& operator++(){_nd = _nd->_next;return *this;}self& operator--(){_nd = _nd->_prev;return *this;}self& operator+(size_t n){while (n--){_nd = _nd->_next;}return *this;}self& operator-(size_t n){while (n--){_nd = _nd->_prev;}return *this;}// zdl:: 后置 ++ / --self operator++(int){self tmp = _nd;_nd = _nd->_next;return tmp;}self operator--(int){self tmp = _nd;_nd = _nd->_prev;return tmp;}// zdl:: != 运算bool operator!=(const self& s){return _nd != s._nd;}};template<class T>class list{public:typedef ListNode<T> node;typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;void empty_init(){// zdl :: 在初始化一个空链表时, 先定义一个哨兵位_head = new node();_head->_next = _head;_head->_prev = _head;}list(){empty_init();}list(const list<T>& l1){empty_init();for (auto& e : l1) push_back(e);}list(size_t n, const T& x = T()){empty_init();for (size_t i = 0; i < n; i++){push_back(x);}}void push_back(const T& val){node* creat = new node(val);node* tail = _head->_prev;tail->_next = creat;creat->_prev = tail;creat->_next = _head;_head->_prev = creat; }void push_front(const T& val){node* fnode = new node(val);fnode->_next = _head->_next;fnode->_prev = _head;_head->_next->_prev = fnode;_head->_next = fnode;}void Print_list(){node* cur = _head->_next;while (cur != _head){cout << cur->_data << " ";cur = cur->_next;}cout << endl;}// zdl:: 增删查改的实现iterator insert(iterator pos, const T& val = T()){node* cur = pos._nd;node* prev = pos._nd->_prev;node* insrt = new node(val);prev->_next = insrt;insrt->_prev = prev;insrt->_next = cur;cur->_prev = insrt;return iterator(cur->_prev);}iterator erase(iterator pos){assert(pos != end()); //! 注意不能够将哨兵位删除!!node* cur = pos._nd;node* prev = cur->_prev;prev->_next = cur->_next;cur->_next->_prev = prev;delete cur;return iterator(prev->_next);}iterator find(const T& val){auto it = begin();while (it != end() && *it != val){it++;}if (*it == val) return it;return nullptr;}void pop_front(){erase(begin());}void pop_back(){erase(--end());}// zdl:: 析构类函数的实现:~list(){// ! 不仅要将所有的数值删除还需要将哨兵位也清除!clear();delete _head;_head = nullptr;            }void clear(){// !! 不能删除哨兵位,就只是将所有的数值清空。auto it = begin();while (it != end()) it = erase(it);}
// zdl:: 使用类来模拟迭代器的行为iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}private:node* _head;};
}

test.cpp


#include"list.h"
#include<list>
struct pos
{int row;int col;//构造函数pos(const int& x = 0, const int& y = 0):row(x),col(y){}//成员函数void pos_show(){printf("(%d, %d) ",row, col);}
};
int main()
{// zdl::list<pos> l1;// pos p[] = {{1, 2}, {3, 4}, {5, 6}};// for (int i = 0; i < 3; i++) l1.push_back(p[i]);// auto it = l1.begin();// while (it != l1.end())// {//     it->pos_show();//     it++;//     cout << endl;// }// cout << endl;// zdl::list<int> l2(5, 4);// for (auto&i : l2) cout << i << " ";// cout << endl;// zdl::list<int> l4(3, 3);// l4.Print_list();// l4.insert(l4.begin() + 2, 2);// l4.Print_list();// l4.erase(l4.begin() + 2);// l4.Print_list();// zdl::list<int>l5;// for (int i = 1; i <= 10; i++) l5.push_back(i);// cout << "原数组:" << endl;// l5.Print_list();// // zdl:: 现在我们要借助 find + erase函数来删除元素:5// l5.erase(l5.find(5));// cout << "删除后:" << endl;// l5.Print_list();//  zdl:: 进行头删和尾删// cout << "进行尾删和头删后:" << endl;// l5.pop_back();// l5.pop_front();// l5.Print_list();// cout << "现在将所有的元素都删除!" << endl;// l5.clear();// l5.Print_list();zdl::list<int> l1(10, 10);cout <<"l1:" << endl;l1.Print_list();zdl::list<int> l2(l1);cout << "l2:" << endl;l2.Print_list();return 0;
}

好,常用的接口我们就实现了,list学习到此告一段落,再见!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/66371.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lynx TiDB 慢日志收集工具

作者&#xff1a; 小龙虾爱大龙虾 原文来源&#xff1a; https://tidb.net/blog/7247e68f 简介 lynx 工具可以定时将 TiDB 集群的慢查询收集并持久化到后端数据库中&#xff0c;然后通过 grafana 查询展示出来&#xff0c;这可以帮助我们更好的分析慢查询日志。 背景 尽管…

Gin 源码概览 - 路由

本文基于gin 1.1 源码解读 https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip 1. 注册路由 我们先来看一段gin代码&#xff0c;来看看最终得到的一颗路由树长啥样 func TestGinDocExp(t *testing.T) {engine : gin.Default()engine.GET("/api/user", f…

【逆境中绽放:万字回顾2024我在挑战中突破自我】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 一、引言二、个人成长与盘点情感与心理成长学习与技能提升其它荣誉 三、年度创作历程回顾创作内容概…

【Linux 重装】Ubuntu 启动盘 U盘无法被识别,如何处理?

背景 U盘烧录了 Ubuntu 系统作为启动盘&#xff0c;再次插入电脑后无法被识别 解决方案&#xff08;Mac 适用&#xff09; &#xff08;1&#xff09;查找 USB&#xff0c;&#xff08;2&#xff09;格式化&#xff08;1&#xff09;在 terminal 中通过 diskutil list 查看是…

中职网络建设与运维ansible服务

ansible服务 填写hosts指定主机范围和控制节点后创建一个脚本&#xff0c;可以利用简化脚本 1. 在linux1上安装系统自带的ansible-core,作为ansible控制节点,linux2-linux7作为ansible的受控节点 Linux1 Linux1-7 Yum install ansible-core -y Vi /etc/ansible/hosts 添加…

数据库服务体系结构

1. 数据库服务应用配置 服务进行配置有什么作用&#xff1f; 实现服务运行启动 实现某些功能 应用配置有三种方式&#xff1f; 利用编译安装进行配置 编写配置文件信息 ,.默认的配置文件: /etc/my.cnf 利用启动命令参数配置信息&#xff0c;mysqld_safe --skip-grant-tables --…

Langchain+FastApi+Vue前后端Ai对话(超详细)

一、引入 首先可以先看下作者的文章 FastApi相关文章&#xff1a;创建最简单FastApi的项目Vue相关文章&#xff1a;最简单的aixos二次封装Langchain相关文章&#xff1a;如何使用LangSmith跟踪deepseek模型 二、后端搭建 1 项目文件结构 routers&#xff1a;存放api接口se…

简历_使用优化的Redis自增ID策略生成分布式环境下全局唯一ID,用于用户上传数据的命名以及多种ID的生成

系列博客目录 文章目录 系列博客目录WhyRedis自增ID策略 Why 我们需要设置全局唯一ID。原因&#xff1a;当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题。 问题&#xff1a;id的规律性太明显、…

Jira中bug的流转流程

Jira中bug的状态 1. 处理Bug的流程2. bug状态流转详述bug的状态通常包括 1. 处理Bug的流程 2. bug状态流转详述 bug的状态通常包括 未解决 1. 测试人员创建一个bug&#xff0c;填写bug的详细信息&#xff0c;如概要、bug级别、复现步骤、现状、预期结果等 2. 定位bug&#x…

Linux的几个基本指令

文章目录 一、几个基本指令1、ls 指令注意&#xff01; 2、pwd命令3、touch 指令4、mkdir 指令注意&#xff01;注意&#xff01; 5、cd 指令注意&#xff01; 6、cp 指令 今天我们学习Linux下的几个基本指令&#xff0c;本篇是在Xshell环境下执行的。 一、几个基本指令 1、…

数据库基础练习1(创建表,设置外键,检查,不为空,主键等约束)安装mysql详细步骤

安装MySQL详细步骤 1. 下载 MySQL 安装程序 访问 MySQL 官方网站&#xff1a;MySQL Downloads。在下载页面&#xff0c;选择 "MySQL Community (GPL) Downloads"。在 "MySQL Community Server" 部分&#xff0c;根据你的操作系统&#xff08;Windows&…

ThinkPHP 8的一对多关联

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

工业网口相机:如何通过调整网口参数设置,优化图像传输和网络性能,达到最大帧率

项目场景 工业相机是常用与工业视觉领域的常用专业视觉核心部件&#xff0c;拥有多种属性&#xff0c;是机器视觉系统中的核心部件&#xff0c;具有不可替代的重要功能。 工业相机已经被广泛应用于工业生产线在线检测、智能交通,机器视觉,科研,军事科学,航天航空等众多领域 …

java使用poi-tl自定义word模板导出

文章目录 概要整体架构流程创建word模板核心代码导出结果 概要 在软件开发领域&#xff0c;自定义Word模板的使用是导出格式化数据的一种常见做法。poi-tl&#xff08;Apache POI Template Language&#xff09;作为一款基于广受认可的Apache POI库的Word模板引擎&#xff0c;…

IDEA2023版中TODO的使用

介绍&#xff1a;TODO其实本质上还是注释&#xff0c;只不过加上了TODO这几个字符&#xff0c;可以让使用者快速找到。 注意&#xff1a;在类、接口等文件中&#xff0c;注释是使用// 即&#xff1a;// TODO 注释内容 在配置文件中&#xff0c;注释是使用# 即&#xff1a;# TO…

项目练习:若依管理系统字典功能-Vue前端部分

文章目录 一、情景说明二、若依Vue相关代码及配置1、utils代码2、components组件3、api接口代码4、Vuex配置5、main.js配置 三、使用方法1、html部分2、js部分 一、情景说明 我们在做web系统的时候&#xff0c;肯定会遇到一些常量选择场景。 比如&#xff0c;性别&#xff1a;…

LabVIEW开发X光图像的边缘检测

在医疗影像处理中&#xff0c;X光图像的分析对于骨折、肿瘤等病变的检测非常重要。X光图像中包含许多关键信息&#xff0c;然而&#xff0c;由于图像噪声的干扰&#xff0c;直接从图像中提取有用的特征&#xff08;如骨折的边缘&#xff09;变得非常困难。边缘检测作为图像处理…

【CC2640R2F】香瓜CC2640R2F之SPI读写W25Q80

本文最后修改时间&#xff1a;2022年01月08日 10:45 一、本节简介 本节以simple_peripheral工程为例&#xff0c;介绍如何使用SPI读写W25Q80&#xff08;外部flash&#xff09;。 二、实验平台 1&#xff09;CC2640R2F平台 ①协议栈版本&#xff1a;CC2640R2 SDK v1.40.00.4…

光谱相机如何还原色彩

多光谱通道采集 光谱相机设有多个不同波段的光谱通道&#xff0c;可精确记录每个波长的光强信息。如 8 到 16 个甚至更多的光谱通道&#xff0c;每个通道负责特定波长范围的光信息记录。这使得相机能分辨出不同光谱组合产生的相同颜色感知&#xff0c;而传统相机的传感器通常只…

C# 获取PDF文档中的字体信息(字体名、大小、颜色、样式等

在设计和出版行业中&#xff0c;字体的选择和使用对最终作品的质量有着重要影响。然而&#xff0c;有时我们可能会遇到包含未知字体的PDF文件&#xff0c;这使得我们无法准确地复制或修改文档。获取PDF中的字体信息可以解决这个问题&#xff0c;让我们能够更好地处理这些文件。…