模拟实现list

目录

  • list的实现结构
  • 节点的实现
  • 迭代器的实现
    • 第一个模板参数T
    • 第二个模板参数Ref
    • 第三个模板参数Ptr
  • 实现list中的接口函数
    • 插入和删除
    • 赋值重载和拷贝构造
    • 析构函数
  • 总结

list的实现结构

STL库中的list的结构是双向循环链表,所以我们这里也实现一个双向循环链表

我们这里采取带头双向循环链表

为了方便节点间的操作,可以将节点封装成一个结构体

list中的迭代器是双向迭代器,只支持++--操作,不支持+,-,所以不能用一个指针用作迭代器,所以还需要将迭代器封装成一个结构体,在结构体中对迭代器的一些操作进行实现


节点的实现

template<class T>struct __list_node{__list_node<T>* _next;__list_node<T>* _prev;T _val;__list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}};

主义,这是一个类模板,类名是__list_node,类型是__list_node<T>
所以2个指针_next_prev的类型是__list_node<T>,分别指向前一个节点和后一个节点

现在,我们就可以把头节点作在为ist类中的成员变量

节点的类型为__list_node<T>,比较麻烦,这里我们可以 typedef __list_node<T> Node

因为在类外我们不会直接用到Node,所以把typedef __list_node<T> Node放到private中就可以

同时实现构造函数,让_head_next指向自己,_prev也指向自己

template<class T >class list{typedef __list_node<T> Node;public:list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}private:Node* _head;};

迭代器的实现

list的迭代器是双向迭代器,只支持++--操作,不支持+,-
所以不能只是简单的服用指针
要对指针进行封装,在结构体中实现++,--,*,->等操作

第一个模板参数T

list迭代器的底层其实还是Node*类型的指针

	template<class T>struct __list__iterator{typedef __list_node<T> Node;typedef __list__iterator<T> Iter;__list__iterator(Node* pnode):_pnode(pnode){}Node* _pnode;};

因为总使用__list_node<T>__list__iterator<T>比较麻烦,所以typedef __list_node<T> Node; typedef __list__iterator<T> Iter;

++操作实际上的底层是当前迭代器指向当前指向节点的下一个节点
--操作实际上的底层是当前迭代器指向当前指向节点的前一个节点

所以下面就可以实习++,--操作

Iter& operator++()
{_pnode = _pnode->_next;return *this;
}Iter operator++(int)
{Iter tmp(*this);_pnode = _pnode->_next;return tmp;
}Iter& operator--()
{_pnode = _pnode->_prev;return *this;
}Iter operator--(int)
{Iter tmp(*this);_pnode = _pnode->_prev;return tmp;
}

解引用操作也就是返回迭代器指向节点中的值

T& operator*()
{return _pnode->_val;
}T* operator->()
{return &_pnode->_val;
}

!===就是比较迭代器下面的指针

bool operator!=(const Iter it)
{return _pnode != it._pnode;
}bool operator==(const Iter it)
{return _pnode == it._pnode;
}

接下来在list中实现begin()end()函数
__list__iterator<T>麻烦,typedef成iterator

typedef __list__iterator<T> iterator;

我们需要在类外使用到iterator,所以这句typedef要放到public中

list的结构如下:
在这里插入图片描述
因为begin()返回的是指向第一个有效节点的迭代器,所以_head->_next就是第一个有效节点

iterator begin()
{return iterator(_head->_next);
}

end()返回的是最后一个节点的下一个位置,这里最后一个节点的下一个位置就是_head
在这里插入图片描述

iterator end()
{return iterator(_head);
}

第二个模板参数Ref

现在有一个问题,就是如何实现const_iterator,在前面iterator的基础上,我们可以在封装一个const_iterator的结构体,其中的返回值改为const T&const T*

但是再封装一个,就会导致代码冗余

这里我们引入第二个模板参数class Ref
这个参数是用来接收返回引用的类型的可以是T&也可以是const T&

所以这时的*重载返回值就是Ref

Ref operator*()
{return _pnode->_val;
}

第三个模板参数Ptr

同理,对于->重载的返回类型是指针
引入第三个模板参数Ptr

它是用来接收T*const T*

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

现在就可以在list中实现cbegincend1

首先还是将typedefiteratorconst_iterator

typedef __list__iterator<T, T&, T*> iterator;
typedef __list__iterator<T, const T&, const T*> const_iterator;
const_iterator begin()const
{return const_iterator(_head->_next);
}const_iterator end()const
{return const_iterator(_head);
}

实现list中的接口函数

插入和删除

这里的inserterase的实现和C语言中实现链表的操作一样,比较容易

这里唯一需要注意的是:insert函数返回插入新节点的迭代器,erase函数返回删除节点后,删除节点下一个节点位置的迭代器

iterator insert(iterator it, const T& val)
{Node* newnode = new Node(val);Node* cur = it._pnode;Node* prev = it._pnode->_prev;newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;newnode->_prev = prev;_size++;return newnode;
}iterator erase(iterator it)
{assert(it != _head);//需要检查一下it是否是头节点位置的迭代器,头节点不能删除Node* del = it._pnode;Node* prev = it._pnode->_prev;Node* nex = it._pnode->_next;prev->_next = nex;nex->_prev = prev;_size--;delete del;return nex;
}

实现了inserterase后,通过复用这两个函数,就可以实现push_back,push_front,pop_back,pop_front函数了

void push_back(const T& val)
{insert(end(), val);
}void push_front(const T& val)
{insert(begin(), val);
}void pop_back()
{erase(_head->_prev);
}void pop_front()
{erase(_head->_next);
}

赋值重载和拷贝构造

赋值重载的实现,可以使用现代写法,和在vector中赋值重载的写法类似,使用swap

void swap(list<T> lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}//赋值重载现代写法
list<T> operator=(list<T> lt)	
{swap(lt);return *this;
}

拷贝构造,我们先构造出一个list,然后用push_back向里面插入节点

//拷贝构造
list(const list<T>& lt)
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto& e : lt){push_back(e);}}

这里有一点:
库中复制拷贝和运算符重载中参数列表中形参类型不是list<T>,而是list类型没有指定后面的模板参数
在这里插入图片描述
在这里插入图片描述


析构函数

析构函数需要清理空间,对于链表来说,我们不能只清理头节点
我们需要清理掉有有效节点后,最后清理掉头节点

这里我们定义一个清理掉有有效节点的函数
使用erase+循环清理节点

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

然后在销毁函数中调用clear函数,再清理掉头节点

~list()
{clear();delete _head;_head = nullptr;
}

总结

此时,list的模拟实现就基本完成了

  • list的模拟实现中,有难度的地方就是把指针封装成迭代器,在封装中实现以及限制了迭代器的作用
  • 对于复杂类型,我们可以typedef出一个简单的别名,对于这个别名,如果要在类外使用就定义在public里,如果不允许在类外使用,就定义在private中

完整代码:

namespace my_list
{template<class T>struct __list_node{__list_node<T>* _next;__list_node<T>* _prev;T _val;__list_node(const T& val = T()):_next(nullptr), _prev(nullptr), _val(val){}};//typedef __list__iterator<T,T&,T*> iterator;//typedef __list__iterator<T, const T&, const T*> const_iterator;template<class T, class Ref, class Ptr>struct __list__iterator{typedef __list_node<T> Node;typedef __list__iterator<T, Ref, Ptr> Iter;__list__iterator(Node* pnode):_pnode(pnode){}Node* _pnode;bool operator!=(const Iter it){return _pnode != it._pnode;}bool operator==(const Iter it){return _pnode == it._pnode;}Iter& operator++(){_pnode = _pnode->_next;return *this;}Iter operator++(int){Iter tmp(*this);_pnode = _pnode->_next;return tmp;}Iter& operator--(){_pnode = _pnode->_prev;return *this;}Iter operator--(int){Iter tmp(*this);_pnode = _pnode->_prev;return tmp;}Ref operator*(){return _pnode->_val;}Ptr operator->(){return &_pnode->_val;}};template<class T >class list{typedef __list_node<T> Node;public:typedef __list__iterator<T, T&, T*> iterator;typedef __list__iterator<T, const T&, const T*> const_iterator;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);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}list(size_t n, const T& val = T()){empty_init();for (int i = 0; i < n; i++){push_back(val);}}~list(){clear();delete _head;_head = nullptr;}//拷贝构造list(const list<T>& lt)//list(const list& lt)   //库中的拷贝构造是这么定义的,list类型没有指定后面的模板参数{empty_init();for (auto& e : lt){push_back(e);}}void swap(list<T> lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//赋值重载现代写法list<T> operator=(list<T> lt)//list operator=(list lt)  //库中的赋值重载是这么定义的,list类型没有指定后面的模板参数{swap(lt);return *this;}size_t size() const{return _size;}bool empty() const{if (_head->_next == _head || _head->_prev == _head)return true;elsereturn false;}iterator insert(iterator it, const T& val){Node* newnode = new Node(val);Node* cur = it._pnode;Node* prev = it._pnode->_prev;newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;newnode->_prev = prev;_size++;return newnode;}iterator erase(iterator it){assert(it != _head);//需要检查一下it是否是头节点位置的迭代器,头节点不能删除Node* del = it._pnode;Node* prev = it._pnode->_prev;Node* nex = it._pnode->_next;prev->_next = nex;nex->_prev = prev;_size--;delete del;return nex;}void push_back(const T& val){insert(end(), val);}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(_head->_prev);}void pop_front(){erase(_head->_next);}void print(){iterator it = begin();while (it != end()){std::cout << *it << " ";it++;}std::cout << std::endl;}void clear(){iterator it = begin();while (it != end()){it = erase(it);//经过erase后的迭代器会失效,为了让循环继续下去,用it接收erase返回的迭代器(删除位置迭代器的下一个位置的迭代器)}_size = 0;}private:Node* _head;size_t _size; //加一个_size成员变量,如果不设置_size,在实现size()函数时就需要遍历list,时间复杂度为O(n)};}

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

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

相关文章

中介者模式

1、场景 假如没有总经理。下面三个部门&#xff1a;财务部、市场部、研发部。财务部要发工资&#xff0c;让大家核对公司需要跟市场部和研发部都通气&#xff1b;市场部要接个新项目&#xff0c;需要研发部处理技术、需要财务部出资金。市场部跟各个部门打交道。 虽然只有三个…

无涯教程-JavaScript - DVAR函数

描述 DVAR函数使用与指定条件相匹配的列表或数据库的列中的数字,根据样本估算总体的方差。 语法 DVAR (database, field, criteria)争论 Argument描述Required/Optionaldatabase 组成列表或数据库的单元格范围。 数据库是相关数据的列表,其中相关信息的行是记录,数据的列是…

大数据课程K17——Spark的协同过滤法

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的协同过滤概念; 一、协同过滤概念 1. 概念 协同过滤是一种借助众包智慧的途径。它利用大量已有的用户偏好来估计用户对其未接触过的物品的喜好程度。其内在思想是相似度的定义…

服务器放在香港好用吗?

​  相较于国内服务器&#xff0c;将网站托管在香港服务器上最直观的好处是备案层面上的。香港服务器上的网站无需备案&#xff0c;因此更无备案时限&#xff0c;购买之后即可使用。 带宽优势 香港服务器的带宽一般分为香港本地带宽和国际带宽、直连中国骨干网 CN2三种。香港…

【python】—— 函数详解

前言&#xff1a; 本期&#xff0c;我们将要讲解的是有关python中函数的相关知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;函数是什么 &#xff08;二&#xff09;语法格式 &#xff08;三&#xff09;函数参数 &#xff08;四&#xff09;函…

轻量、便捷、高效—经纬恒润AETP助力车载以太网测试

随着自动驾驶技术和智能座舱的不断发展&#xff0c;高宽带、高速率的数据通信对主干网提出了稳定、高效的传输要求&#xff0c;CAN(FD)、LIN已无法充分满足汽车的通信需求。车载以太网作为一种快速且扩展性好的网络技术&#xff0c;已经逐步成为了汽车主干网的首选。 此外&…

面试题汇总

文章目录 一. 腾讯二. 华为三. 快手1. Long 的长度和范围&#xff0c;为什么要减 1 (Java基础)2. 线程池配置无界队列了之后&#xff0c;拒绝策略怎么搞&#xff0c;什么时候用到无界队列 (JUC并发) 四. 美团五. 阿里六. 百度七. 字节八. 大疆1. 为什么创建进程开销比线程大? …

Python之作业(一)

Python之作业&#xff08;一&#xff09; 作业 打印九九乘法表 用户登录验证 用户依次输入用户名和密码&#xff0c;然后提交验证用户不存在、密码错误&#xff0c;都显示用户名或密码错误提示错误3次&#xff0c;则退出程序验证成功则显示登录信息 九九乘法表 代码分析 先…

win | wireshark | 在win上跑lua脚本 解析数据包

前提说明&#xff1a;之前是在linux 系统上配置的&#xff0c;然后现在 在配置lua 脚本 &#xff0c;然后 分析指定协议 的 数据包 其实流程也比较简单&#xff0c;但 逻辑需要缕清来 首先要把你 预先准备的 xxx.lua 文件放到wireshark 的安装文件中&#xff0c;&#xff08;我…

linux深入理解多进程间通信

1.进程间通信 1.1 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件…

《Linux从练气到飞升》No.20 Linux进程替换

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

不同写法的性能差异

“ 达到相同目的,可以有多种写法,每种写法有性能、可读性方面的区别,本文旨在探讨不同写法之间的性能差异 len(str) vs str "" 本部分参考自: [问个 Go 问题&#xff0c;字符串 len 0 和 字符串 "" &#xff0c;有啥区别&#xff1f;](https://segmentf…

WebSocket--技术文档--基本概念--《快速了解WebSocket协议》

阿丹&#xff1a; 不断学习新技术&#xff0c;丰富自己了解更多才能扩展更多世界可能。 官网 WebSocket首页、文档和下载 - HTML5开发相关 - OSCHINA - 中文开源技术交流社区 软件简介 WebSocket 是 HTML5 开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebS…

android:新建工程文件介绍

一、前言当我们新建一个app时会呈现出固定的工程文件&#xff0c;这篇文章介绍新建工程里的文件。 二、介绍 Structure:就是你选择哪个页面就会显示那个页面的结构&#xff0c;就比如说我选择的是MainActivity他就会显示这个页面所使用的方法。 1-2&#xff1a;是android自动生…

什么是架构,架构的本质是什么

不论是开发人员还是架构师&#xff0c;我们都一直在跟软件系统打交道&#xff0c;架构是在工作中出现最频繁的术语之一。那么&#xff0c;到底什么是架构&#xff1f;你可能有自己的答案&#xff0c;也有可能没有答案。对“架构”的理解需要我们不断在实践中思考、归纳、演绎&a…

【ES6】require、export和import的用法

在JavaScript中&#xff0c;require、export和import是Node.js的模块系统中的关键字&#xff0c;用于处理模块间的依赖关系。 1、require&#xff1a;这是Node.js中引入模块的方法。当你需要使用其他模块提供的功能时&#xff0c;可以使用require关键字来引入该模块。例如&…

查询优化器内核剖析之从一个实例看执行计划

学习查询优化器不是我们的目的&#xff0c;而是通过 它&#xff0c;我们掌握 SQL Server 是如何处理我们的 SQL 的&#xff0c;掌握执行计划&#xff0c;掌握为什么产生 I/O 问题&#xff0c; 为什么 CPU 使用老高&#xff0c;为什么你的索引加了不起作用... 如果&#xff0c;…

3DCAT携手华为,打造XR虚拟仿真实训实时云渲染解决方案

2023年5月8日-9日&#xff0c;以 因聚而生 众志有为 为主题的 华为中国合作伙伴大会2023 在深圳国际会展中心隆重举行。本次大会汇聚了ICT产业界的广大新老伙伴朋友&#xff0c;共同探讨数字化转型的新机遇&#xff0c;共享数字化未来的新成果。 华为中国合作伙伴大会2023现场&…

安装ArcGis时需要安装Micsoft.Net Framework 3.5 sp1

在安转ArcGis时遇到一个问题&#xff0c;解决方法如下 下载.Net 按照他的说明 将地址复制到迅雷中下载&#xff0c;并安装 就可以了 安装就可以了

【数据结构-队列】队列介绍

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…