C++容器——list的模拟实现

目录

一.list的基本结构

二. 接下来就是对list类构造函数的设计了:

三.链表数据的增加:

四.接下来就是迭代器的创建了:

四.简单函数的实现:

五.构造与析构 

六.拷贝构造和赋值重载

传统写法:

现代写法:

七.迭代器模板类型


     一.list的基本结构

         想要模拟实现list类,就需要先了解它的底层架构,上篇博客讲到:list容器的底层是双向链表,那么就需要自定义一个节点类,通过节点类可以创建节点,设置节点的前后指针和数据值。之后便可以通过该类类型创建list类的成员变量。

template<class T>
struct list_node {	//该类为内部类,是list的内部类list_node(const T& val):_next(nullptr), _prev(nullptr), _data(val) {}//成员变量list_node* _next;    //后指针list_node* _prev;    //前指针T _data;            //值
};template<class T>
class list {public:typedef list_node<T>  node;    //将节点类作为类类型private:node* _head;	//指向堆区空间链表的指针size_t _size;    //计数
};

node* 类型就好比是对节点类的封装。 

二. 接下来就是对list类构造函数的设计了:

template<class T>
class list {public:typedef list_node<T>  node;    //将节点类作为类类型//初始化操作void empty_Init() {_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list()    //构造函数:_head(nullptr),_size(0){empty_Init();}private:node* _head;	//指向堆区空间链表的指针size_t _size;    //计数
};

        对构造函数的初始化设计就是:创建哨兵位头结点,让链表指针指向哨兵位头结点,由哨兵头节点去控制节点的增删查改,避免了由链表指针去控制,操作和形式上都方便了很多。

         注:哨兵位头结点的创建是在empty_Init()函数中进行的!

三.链表数据的增加:

template<class T>
class list{public:typedef Node<T> node;//尾插  void push_back(const T& val) {node* newnode = new node(val);node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;++_size;}//尾删void pop_back() {assert(!empty());node* tail = _head->_prev;node* last = tail->_prev;last->_next = _head;_head->_prev = last;delete tail;--_size;}//头插void push_front(const T& val) {node* newnode = new node(val);node* first = _head->_next;_head->_next = newnode;newnode->_prev = _head->_next;newnode->_next = first;first->_prev = newnode;++_size;}//头删void pop_front() {assert(!empty());node* first = _head->_next;node* second = first->_next;_head->_next = second;second->_prev = _head->_next;delete first;--_size;}//任意位置的插入iterator insert(iterator pos, const T& val=T()) {if (pos == this->begin()) {push_front(val);    //复用代码}else if (pos == this->end()) {push_back(val);    //复用代码}else {node* newnode = new node(val);node* cur = pos.phead;node* last = cur->_prev;last->_next = newnode;newnode->_prev = last;newnode->_next = cur;cur->_prev = newnode;++_size;}return pos;}//任意位置的删除iterator erase(iterator pos) {assert(!empty());if (pos == this->begin()) {pop_front();}else if (pos == this->end()) {pop_back();}else {node* cur = pos.phead;node* tail = cur->_next;node* last = cur->_prev;last->_next = tail;tail->_prev = last;delete cur;--_size;}return pos;}private:node* _head;	//指向堆区空间链表的指针size_t _size;    //计数};

        对于数据的增加和删除,头插头删、尾插尾删简单就不说了,重点是insert和erase函数的实现,如上代码,在insert和erase中,各有三种情况,其中头尾的操作直接复用函数即可,对于中间位置的插入删除情况,我想说的是,指定的pos参数是iterator类型——自定义迭代器类,它是指针!!! 它只是指向该节点元素的位置,所以想要获取该位置的节点,就需要pos.phead才能获取到该节点,只有获取到该节点,才能使用该节点附近的前后指针! 

 

四.接下来就是迭代器的创建了:

        在对vector、String容器的模拟实现中,我并没有单独创建迭代器,这是因为这两个容器的底层都是数组,是一段连续的地址空间,对于迭代器中的成员begin、end都是可以直接让指针进行类型的字节++/--进行的,很方便的,是使用原生指针来确定位置

        而对于list容器来说,它的底层是链表,各个节点的位置是不连续的,随机的。使用原生指针并不能遍历到每一个对象的元素!所以针对list容器,需要创建一个自定义类型的迭代器进行链表的遍历。 

template<class T>struct list_iterator{typedef list_node<T> node;node* _pnode;            //成员变量list_iterator(node* p)    //构造函数:_pnode(p){}T& operator*(){                    //指针解引用return _pnode->_data;}list_iterator<T>& operator++(){    //指针++_pnode = _pnode->_next;return *this;}bool operator!=(const list_iterator<T>& it){   //!=运算符重载return _pnode != it._pnode;}};template<class T>class list{public:typedef list_node<T> node;typedef list_iterator<T> iterator;iterator begin(){return iterator(_head->_next);}iterator end(){//iterator it(_head);//return it;return iterator(_head);}};

        在自定义的迭代器类中,我根据平常练习vector、String的迭代器代码中,写了几个一定会用到的运算符重载函数:解引用、指针++,遍历所用到的!=等函数。

        写好自定义迭代器类后,需要在list类中重命名该类。

        写好迭代器后,我们就可以试验一下了:

 

 

 注:上面的迭代器只是普通迭代器的实现,还会有const迭代器、反向迭代器需要实现,意味着还得再写两个迭代器类。

四.简单函数的实现:

template<class T>
class list{public:size_t size()const {return _size;//方法2:利用指针遍历,每遍历一次记一次数}bool empty() const {//方法1:return _size == 0;//方法2:return _head->next==_head;}void clear() {node* cur = _head->_next;while (cur != _head) {node* del = cur;cur = cur->_next;delete del;}cur->_next = _head;cur->_prev = _head;_size = 0;}T& front() {return  this->begin().phead->_data;}T& back() {return  this->end().phead->_prev->_data;}private:node* _head;size_t _size;};

五.构造与析构 

        有了迭代器,我们就可以对list构造函数进行迭代器区间构造实现了:

template<class T>
class list{
public://迭代器区间构造函数template<class Inputiterator>list(Inputiterator first, Inputiterator last) {		empty_Init();while (first != last) {push_back(*first);++first;}}void empty_Init() {_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list()    //无参构造{empty_Init();}//析构函数~list() {this->clear();delete _head;_head = nullptr;_size = 0;}
private:node* _head;size_t _size;
};

析构函数就是遍历链表中每个节点都进行遍历释放,置空指针,置零变量。

六.拷贝构造和赋值重载

 

传统写法:

//拷贝构造——传统写法list(const list<T>& lt) {empty_Init();for (auto& e : lt) {this->push_back(e);}}//赋值重载函数——传统写法list<T>& operator=(const list<T>& lt) {if (this != &lt) {this->clear();}for (const auto& e : lt) {this->push_back(e);}return *this;}

        拷贝构造和赋值重载本质上都相同,都是复制已有的list对象,然后深拷贝数据给自己。深拷贝就是创建一个属于自己的头结点,剩下的数据就是浅拷贝(无脑将数据以遍历的方式让自己的头指针进行指针链接)。 

现代写法:

    //调用std库中swap函数进行成员交换void Swap(list<T>& lt) {std::swap(this->_head, lt._head);std::swap(this->_size, lt._size);}	//拷贝构造——现代写法list(const list<T>& lt) {empty_Init();list<T> tmp(lt.begin(), lt.end());		//调用迭代器区间构造函数this->Swap(tmp);}//赋值重载——现代写法list<T>& operator=(list<T> lt) {this->Swap(lt);    //值传递,形参的改变不影响实参return *this;}

 

七.迭代器模板类型

        上面讲迭代器的最后说了,迭代器有普通版、const版、反向版、反向const版,意味着我们需要创建四个迭代器类型,但迭代器能用到的运算符重载函数都一样,都是解引用、指针++、!=运算符。

//自定义普通迭代器类
template<class T>struct list_iterator{typedef list_node<T> node;node* _pnode;list_iterator(node* p):_pnode(p){}T& operator*(){return _pnode->_data;}list_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}list_iterator<T>& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const list_iterator<T>& it){return _pnode != it._pnode;}};//const迭代器类template<class T>struct list_const_iterator{typedef list_node<T> node;node* _pnode;list_const_iterator(node* p):_pnode(p){}const T& operator*(){return _pnode->_data;}list_const_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}list_const_iterator<T>& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const list_const_iterator<T>& it){return _pnode != it._pnode;}};template<class T>class list{typedef list_node<T> node;public:typedef list_iterator<T> iterator;typedef list_const_iterator<T> const_iterator;const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}

        如上,普通迭代器类和const迭代器类唯一的区别:是在遍历上,const迭代器类的解引用运算符重载函数中不能用*it修改数据,那么这俩迭代器类中其他函数的实现完全一样,这极大的造成了代码的冗余,降低了可读性!!!

        于是为了在一种迭代器类中体现不同类型的迭代器,可以这样做:

template<class T, class Ref, class Ptr>	
struct _list_iterator {typedef list_node<T> node;typedef _list_iterator<T, Ref,Ptr> Self;	//Self是T与ref,ptr 模板类型的另一种别称//迭代器构造函数_list_iterator(node* p):_pnode(p) {}//解引用Ref operator*() {return _pnode->_data;}//箭头只有是结构体才可以用Ptr operator->() {return &_pnode->_data;		//返回的是该结点的地址}Self& operator++() {_pnode = _pnode->_next;return *this;}//后置++,使用占位符int,与前置++以示区分Self operator++(int) {Self tmp(*this);_pnode = _pnode->_next;return tmp;}//前置--Self& operator--() {_pnode = _pnode->_prev;return *this;}//后置--Self operator--(int) {Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const Self& lt) const {return _pnode != lt._pnode;}bool operator==(const Self& lt)  const{return _pnode == lt._pnode;}node* _pnode;
};

        在迭代器类中,采用了三个模板参数。这三个模板参数:T代表泛型值,Ref代表泛型引用,Ptr代表泛型指针,这三种参数主要应用于运算符重载函数的函数返回值,函数形参,相当方便,一举多得,通过不同实参的传递就可以调用不同类型的函数。

 

template<class T>
class list {
public:typedef list_node<T>  node;typedef _list_iterator<T, T&, T*> iterator;//typedef list_const_iterator<T> const_iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;

完整模拟实现代码 .h文件:

#include<iostream>
#include<assert.h>using std::cout;
using std::endl;template<class T>
struct list_node {	//该类为内部类,是list的内部类list_node(const T& val):_next(nullptr), _prev(nullptr), _data(val) {}//成员变量list_node* _next;list_node* _prev;T _data;
};//typedef list_iterator<T, T&> iterator;
//typedef list_iterator<T, const T&> const_iterator;//这种写法来源:vector<int>,vector<string>,vector<vector<int>> 
template<class T, class Ref, class Ptr>	//新增一个模板参数	,T是一种类型,ref是一种类型
struct list_iterator {typedef list_node<T> node;typedef list_iterator<T, Ref, Ptr> Self;	//Self是T与ref,ptr 模板类型的另一种别称//迭代器构造函数list_iterator(node* p):_pnode(p) {}//在下面的运算符重载中,const版与非const版只有解引用运算符重载函数的类型不同,其他运算符重载都一样//所以operator* 的类型需要使用ref,ref可以理解为constT&, 而非const对象也可以调用const函数,权限够//const对象不可调用非const函数,权限不够,所以使用ref//解引用Ref operator*() {return _pnode->_data;}//箭头只有是结构体才可以用Ptr operator->() {return &_pnode->_data;		//返回的是该结点的地址}//前置++,//为啥要用引用? 原因:return *this ,this(迭代器对象)出了该函数体,还存在(this的生命周期在该类中是全局的)Self& operator++() {_pnode = _pnode->_next;return *this;//既然this还在,那么直接用引用返回,栈帧中不开临时空间,减少拷贝次数,提高效率//记住:使用引用返回的前提是,要返回的值出了函数体仍在才可以使用,否则会报错}//后置++,使用占位符int,与前置++以示区分Self operator++(int) {Self tmp(*this);_pnode = _pnode->_next;return tmp;//返回tmp后,tmp为临时对象,出了函数就消失了,tmp对象不在,需要拷贝,那就得用传值返回,在栈帧中//创建一个临时空间去接收返回的tmp对象数据。设置一个默认参数和前置++做区分,构成函数重载。//若使用引用返回,那么该函数结束后,返回的tmp已经不存在了,引用返回返回野指针(随机值)就会报错!!!}Self& operator--() {_pnode = _pnode->_prev;return *this;}//后置--Self operator--(int) {Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const Self& lt) const {return _pnode != lt._pnode;}bool operator==(const Self& lt)  const{return _pnode == lt._pnode;}node* _pnode;
};//--------------------------------------------------------------------------------template<class T>
class list {
public:typedef list_node<T>  node;typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;void empty_Init() {_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_Init();}//析构~list() {this->clear();delete _head;_head = nullptr;_size = 0;}template<class Inputiterator>list(Inputiterator first, Inputiterator last) {		//拷贝构造的天选打工人//先初始化,给头节点,否则没法继续empty_Init();while (first != last) {push_back(*first);++first;}}void swap(list<T>& lt) {std::swap(this->_head, lt._head);std::swap(this->_size, lt._size);}void clear() {iterator it = this->begin();while (it != this->end()) {it = this->erase(it);}}//拷贝构造——传统写法/*list(const list<T>& lt) {empty_Init();for (auto& e : lt) {this->push_back(e);}}*///拷贝构造——现代写法list(const list<T>& lt) {empty_Init();list<T> tmp(lt.begin(), lt.end());		//调用迭代器区间构造函数this->swap(tmp);}//赋值重载——传统写法/*list<T>& operator=(const list<T>& lt) {if (this != &lt) {this->clear();}for (const auto& e : lt) {this->push_back(e);}return *this;}*///赋值重载——现代写法list<T>& operator=(list<T> lt) {this->swap(lt);return *this;}//迭代器iterator begin() {return iterator(_head->_next);}iterator end() {return iterator(_head);}//const迭代器const_iterator begin() const {return const_iterator(_head->_next);}const_iterator end() const {return const_iterator(_head);}//尾插void push_back(const T& val) {node* newnode = new node(T(val));node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}//insertiterator insert(iterator pos, const T& val) {node* newnode = new node(T(val));node* cur = pos._pnode;node* first = cur->_prev;first->_next = newnode;newnode->_prev = first;newnode->_next = cur;cur->_prev = newnode;_size++;//insert后返回新节点的位置,那么下一次pos就会指向最近一次创建新节点的位置了return iterator(newnode);}iterator erase(iterator pos) {//pos不能指向哨兵位头节点,因为pos一旦指向哨兵位头,那么该链表一定为空,空链表是不能再删数据的assert(pos != end());node* first = pos._pnode->_prev;node* last = pos._pnode->_next;first->_next = last;last->_prev = first;delete pos._pnode;pos._pnode = nullptr;--_size;return iterator(last);}void push_front(const T& val) {insert(begin(), val);}void pop_back() {erase(--end());}void pop_front() {erase(begin());}size_t size() const{/*size_t s = 0;iterator it = this->begin();while (it != this->end()) {++it;++s;}return s;*///复用insert和erase//因为在链表中,一切的新增和减少都是复用的insert和erase,所以在insert和erase中size++,size--即可return _size;}bool empty() const {//return _head->_next == _head;//也可以复用size()return _size == 0;}private:node* _head;	//头节点size_t _size;
};

 

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

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

相关文章

C#中未能找到为main方法指定的XXX.Program怎么解决

有时在修改项目名称后&#xff0c;报错未能找到为main方法指定的XXX.Program 解决办法&#xff1a; 点击进入项目属性&#xff0c;将启动对象设置为空或者你要指定的XXX.Program&#xff08;改名后的&#xff09;

akka 简单使用

由于AKka的核心是Actor&#xff0c;而Actor是按照Actor模型进行实现的&#xff0c;所以在使用Akka之前&#xff0c;有必要弄清楚什么是Actor模型。 Actor模型最早是1973年Carl Hewitt、Peter Bishop和Richard Seiger的论文中出现的&#xff0c;受物理学中的广义相对论(general…

服务器数据恢复-误操作导致存储VDisk丢失的数据恢复案例

服务器数据恢复环境&#xff1a; IBM某型号存储&#xff1b; Solaris操作系统&#xff0c;部署Oracle数据库。 服务器故障&#xff1a; 重建MDisk导致对应的存储池中的VDisk丢失&#xff0c;导致Solaris操作系统中的Oracle数据库无法使用。 服务器数据恢复过程&#xff1a; 1、…

理解Android中不同的Context

作者&#xff1a;两日的blog Context是什么&#xff0c;有什么用 在Android开发中&#xff0c;Context是一个抽象类&#xff0c;它是Android应用程序环境的一部分。它提供了访问应用程序资源和执行各种操作的接口。可以说&#xff0c;Context是Android应用程序与系统环境进行交…

面向对象——步入JavaScript高级阶段的敲门砖

目录 前言一、认识对象1.什么是对象2.对象的方法3.对象的遍历4.对象的深浅克隆 二、认识函数上下文1.函数的上下文规则 ★2.call和apply ★ 三、构造函数1.用new操作符调用函数2.类与实例3.构造函数和类" 四、原型和原型链1.prototype和原型链查找 ★2.在prototype上添加方…

Windows下安装Hadoop(手把手包成功安装)

Windows下安装Hadoop&#xff08;手把手包成功安装&#xff09; Windows下安装Hadoop&#xff08;手把手包成功安装&#xff09;一、环境准备1.1、查看是否安装了java环境 二、下载Hadoop的相关文件三、解压Hadoop安装包四、替换bin文件夹五、配置Hadoop环境变量六、检查环境变…

【数学建模】时间序列分析

文章目录 1. 条件2. 模型分类3. SPSS处理时间序列 1. 条件 1.使用于具有时间、数值两种要素 2.数据具有周期性可以使用时间序列分解 2. 模型分类 叠加模型【YTSCI】 序列的季节波动变化越来越大&#xff0c;反映变动之间的关系发生变化乘积序列【YTSC*I】 时间序列波动保持恒…

【多模态】16、DetCLIP | 构建超大词汇字典来进行开放世界目标检测

论文&#xff1a;DetCLIP: Dictionary-Enriched Visual-Concept Paralleled Pre-training for Open-world Detection 代码&#xff1a;无。。。 出处&#xff1a;NIPS2022 | 华为诺亚方舟 | 中山大学 | 香港科技大学 效果&#xff1a; 在 LVIS 的 1203 个类别上超越了 GLIP…

【树上乘法原理】ICPC Shanghai 2021 G, Edge Groups

http://oj.daimayuan.top/course/8/problem/400 题意&#xff1a; 思路&#xff1a; 求方案数&#xff0c;考虑组合数学 手摸一下样例发现&#xff0c;对于一棵子树&#xff0c;若边数为偶数&#xff0c;那么可以内部匹配&#xff0c;但是如果边数为奇数&#xff0c;那么就一…

嵌入式:QT Day2

一、继续完善登录框&#xff0c;当登陆成功时&#xff0c;关闭登陆页面&#xff0c;跳转到新的界面中 源码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> //用于打印输出 #include <QIcon> …

无涯教程-jQuery - jQuery.getScript( url, callback )方法函数

jQuery.getScript(url&#xff0c;[callback])方法使用HTTP GET请求加载并执行JavaScript文件。 该方法返回XMLHttpRequest对象。 jQuery.getScript( url, [callback] ) - 语法 $.getScript( url, [callback] ) 这是此方法使用的所有参数的描述- url - 包含请求…

mac 移动硬盘未正常退出,再次链接无法读取(显示)

&#xff08;1&#xff09;首先插入自己的硬盘&#xff0c;然后找到mac的磁盘工具 &#xff08;2&#xff09;打开磁盘工具&#xff0c;发现自己的磁盘分区在卸载状态&#xff1b;点击无法成功装载。 &#xff08;3&#xff09;打开终端&#xff0c;输入 diskutil list查看自…

Redis应用(1)——生成全局唯一标识ID

1 概述 在实际项目中&#xff0c;根据不同的业务逻辑需要生成唯一的标识id &#xff0c;如购买商品生成的订单号。尽管这个标识id功能非常的简单&#xff0c;但是如果不能成功的生成唯一标识id&#xff0c;那将会影响后续的业务逻辑 。我们可以使用数据库去生成唯一标识id&…

经营在线业务的首选客服工具--SS客服

随着网购正在快速取代传统零售业&#xff0c;各行各业的企业都在大力发展电子商务以取悦客户。但是&#xff0c;有这么多可用的电子商务平台&#xff0c;选择一款符合自己发展的平台确实不容易。电子商务平台不仅是企业在线销售产品和服务的地方&#xff0c;也是他们管理日常运…

Qt Core学习日记——第七天QMetaObject(上)

每一个声明Q_OBJECT的类都具有QMetaObject对象 Q_OBJECT宏源代码&#xff1a; #define Q_OBJECT \ public: \ QT_WARNING_PUSH \ Q_OBJECT_NO_OVERRIDE_WARNING \ static const QMetaObject staticMetaObject; \ virtual const QMetaObject *metaObject() const; \ vir…

深度学习入门教程(2):使用预训练模型来文字生成图片TextToImageGenerationWithNetwork

本深度学习入门教程是在polyu HPCStudio 启发以及资源支持下进行的&#xff0c;在此也感谢polyu以及提供支持的老师。 本文内容&#xff1a;在GoogleColab平台上使用预训练模型来文字生成图片Text To Image Generation With Network &#xff08;1&#xff09;你会学到什么&a…

Fiddler使用教程|渗透测试工具使用方法Fiddler

提示&#xff1a;如有问题可联系我&#xff0c;24小时在线 文章目录 前言一、Fiddler界面介绍二、菜单栏1.菜单Fiddler工具栏介绍Fiddler命令行工具详解 前言 网络渗透测试工具&#xff1a; Fiddler是目前最常用的http抓包工具之一。 Fiddler是功能非常强大&#xff0c;是web…

伦敦金在非农双向挂单

对伦敦金投资有一定经验的投资者都知道&#xff0c;在非农时期&#xff0c;伦敦金市场会出现很大的波动&#xff0c;那么我们如何才能抓住这些波动呢&#xff1f;答案是很难的。但是&#xff0c;有些投资者在多年实践中发明了一种双向挂单的方法&#xff0c;这里和大家一切分享…

向量数据库Milvus的四个版本

目录 MilvusLite版 单机版 分布式版 Milvus Cloud版 Milvus是一个功能强大的向量数据库管理系统,提供了多种版本,以满足不同用户的需求。以下是关于Milvus四个版本的具体介绍。 MilvusLite版 MilvusLite版是Milvus的轻量级版本,适合于小规模数据集和高性能计算场景。…

el-popover在原生table中,弹出多个以及内部取消按钮无效问题

问题&#xff1a;当el-popover和原生table同时使用的时候会失效&#xff08;不是el-table) <el-popover placement"bottom" width"500" trigger"click" :key"popover-${item.id}"></el-popover> 解决&#xff1a; :key…