当前位置: 首页 > 编程日记 > 正文

STL模拟实现1.0 -- list和iterator模拟实现和简单分析

引言

C ++ 标准模本库《STL》中有很多优秀的代码实现,不然怎么能叫做C++标准模板库呢,其中一个实现就是有一个容器,叫做list。所谓容器其实就是存储相同类型数据的一个存储集合,list的底层实现其实就是一个链表。

我们的普通数组在使用的时候可以定义一个指针指向一个节点,然后使指针 ++ 就可以访问下一个节点,我们想要我们的list也能够使用这个功能,于是就出现了迭代器iterator,通过定义一个iterator,我们可以使用++访问下一个节点,也可以使用*来找到iterator维护的节点的数据,本文就简单模拟实现iterator。

iterator的使用

看下面的代码

#include<iostream>
using namespace std;
#include<list>
void TestList()
{list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);list<int>::iterator it = l.begin();while (it != l.end()){cout << *it << endl;it++;}
}

这个时候输出的结果就是1 2 3,这样使用list访问数据是不是很方便呢,从上面的使用中,我们也可以终于理解迭代器到底是什么东西了,迭代器其实就是一个维护list中各个节点的一个东西,其中的begin()和end()放回的也是一个迭代器,那么迭代器到底是一个什么东西呢 ,我们呢通过下面的下面的模拟实现可以知道迭代器到底是什么。

简单模拟实现list和iterator

talk is cheap,show you this code!
代码的后面是模拟实现的分析哦,那才是重点

#include<iostream>
using namespace std;#include<list>template<class T>
struct _ListNode
{_ListNode<T>* _next;_ListNode<T>* _prev;T _data;_ListNode(T d = 0):_data(d), _next(NULL), _prev(NULL){}
};//这个T标记的是维护的数据类型,Ref是引用,这个引用是对数据的引用,就是这里面data的引用
//Ptr是指针,这个指针也是指向数据data的指针这两个模板类型我们可以在
//List这个类里面使用,因为在编写List_Iterator的时候,会经常使用Ref和Ptr
template<class T, class Ref, class Ptr>
class List_Iterator
{typedef _ListNode<T> Node;typedef List_Iterator<T, Ref, Ptr> Self;   //这个地方写的 有点失误了,就是把List_Iterator的单词写错了
public:List_Iterator(Node* it):_it(it){}List_Iterator(const List_Iterator& it){_it = it._it;}//List_Iterator& operator++()Self& operator++(){_it = _it->_next;return *this;}Self operator++(int)    //注意前置++返回的是引用,而后置++返回的是Self{Node* tmp = _it;_it = _it->_next;return tmp;}//T& operator*()Ref operator*(){return _it->_data;}bool operator!=(const Self& it){return _it != it._it;    //一开始写这个!=运算符重载的时候也出现了错误,就是我一开始是拿this和&it比//这显然是不对的,两个迭代器不可能是相等的,所以这个时候应该是拿//这两个迭代器所指向的内容来比较,就是那他们的成员变量来 进行比较才正确}private:Node* _it;
};//这里我们需要做一个修改就是,我们需要把这个链表定义成一个双向的有头的循环链表
//这个工作就是要在构造函数的时候去做了
//所以这里_tail就不需要了//有头结点,_head维护了一个头结点,但是这个节点不放置任何的数据,因为这样在后面的增删查改的时候使用的时候会非常的方便
template<class T>
class _List
{typedef _ListNode<T> Node;public:typedef List_Iterator<T, T&, T*> Iterator;//const迭代器typedef List_Iterator<T, const T&, const T*> Const_Iterator;//这里为什么传递的是const T&和const T*呢,如果我们这样传递的话,在实现iterator的时候,里面有个函数是//Ref operator*()//{//  return _it->_data;//}//这个时候因为我们传递的是const的,所以这个时候的Ref就成了const的了,这个时候的iterator所维护的节点//里面的数据就不可改变了,这就实现了const的作用了//这也是我们为什么设计iterator的时候为什么设计的是三个模板参数了//配合着const_iterator我们还需要模拟实现返回时const的end和begin_List():_head(new Node)   //这里报了一个错误就是,没有合适的构造函数可以使用,原因是我上面写_ListNode的时候//传参了,但是这里没有传参数,所以我上面的额时候把传递的参数默认为d = 0这个时候就算是我不传递参数//也没有什么问题了//  , _tail(_head){_head->_next = _head;_head->_prev = _head;}//这里开始设计的时候想着返回值是Node*,然后外面使用的时候,可以拿这个Node*构造一个迭代器,调用迭代器//的构造函数,就生成了一个迭代器了,但是问题在于,这样做的话,是不是会引起我们的某些时候的一些误解呢//我们可以直接返回的就是一个迭代器//但是如果我们这里设计的返回值是一个迭代器的话,我们需要在这个函数里面定义一个迭代器,然后返回的时候//生成一个临时的对象,返回给外层的迭代器,这个时候调用了一次拷贝构造函数,返回给外层的迭代器的时候//又调用了一次拷贝构造函数,这样会不会太复杂了呢//Iterator begin()//{//  if (_head->_next == _head)   //这里做了一个判断就是当我们的链表是空的时候,就不能直接返回next//  {//      return Iterator(NULL);//  }//  return Iterator(_head->_next);//}//在使用Const_Iterator的时候出现了问题就是,为什么我下面的普通的begin函数不注释 的时候,就编译不过呢Const_Iterator begin() const   //返回值是Const_iterator为了和上面的匹配,然后后面 的一个const为为了//this指针所指向的内容{if (_head->_next == _head)   //这里做了一个判断就是当我们的链表是空的时候,就不能直接返回next{return Const_Iterator(NULL);}return Const_Iterator(_head->_next);}//Node* begin() const//{//  if (_head->_next == _head)   //这里做了一个判断就是当我们的链表是空的时候,就不能直接返回next//  {//      return NULL;//  }//  return _head->_next;//}/*Iterator end(){if (_head->_next == _head){return Iterator(NULL);}return Iterator(_head);}*/Const_Iterator end() const{if (_head->_next == _head){return Const_Iterator(NULL);}return Const_Iterator(_head);}void Pushback(const T& data){Node* cur = new Node(data);cur->_prev = _head->_prev;_head->_prev = cur;cur->_next = _head;cur->_prev->_next = cur;}//void Print()//{//  Const_Iterator it = begin();   //这个原因是,我们的begin不是 const的,但是我们的const_ietator是const的//  while (it != end())//  {//      cout << *it << endl;//      it++;//  }//}
private:Node* _head;
//  Node* _tail;
};void TestList()
{_List<int> l;l.Pushback(0);   //一开始的时候,我这里设置的这个参数是引用,这个时候是辩不过的,因为我们的0是在常量区的l.Pushback(1);l.Pushback(2);_List<int>::Const_Iterator it = l.begin();/*_List<int>::Ierator it = l.begin();cout << *it << endl;++it;cout << *it << endl;++it;cout << *it << endl;*/
//  _List<int>::Iterator it = l.begin();//*it = 5;//l.Print();/*list<int> l;l.push_back(0);l.push_back(1);list<int>::const_iterator it = l.begin();*/}

这里我们按照一个类一个类的来进行分析

_ListNode

首先看到的是_ListNode,这里就不用过多的解释什么了,成员变量是两个指向该节点的指针,和一个存放数据的东西;这里只实现了一个构造函数,构造函数完成的初始化的时候把两个指针置为NULL

List_Iterator

  • 构造函数,我们的List_Iterator底层维护的实际上就是一个指针,指向的就是我们的链表的一个个节点,构造函数中是把一个指针给了迭代器中,这个 函数一般在我们的list中的begin函数中使用到
  • 拷贝构造函数。这个是拿一个已经存在的迭代器去初始化 另一个迭代器。
  • 前置 ++ 和后置 ++ 。Self& operator++()和Self operator++(int)
    这两个函数是我们实现的重点,因为我们 开始写迭代器 的目的就是希望能够像一个指针使用它,直接使用 ++ 操作就可以实现对下一个节点的访问了。这里我们 应该注意的一个问题就是,我们的前置 ++ 和后置 ++ 的返回值应该是不一样的,因为前置++返回的是先 ++ 后返回,返回的是 ++ 后的结果,所以这个时候应该返回的是Self的引用,但是后置 ++ 是先返回在 ++ ,这个时候是不能够使用引用的,因为我们既然要先返回再 ++ ,我们就要在 函数的里面先将 ++ 前的值记录下来,所以这个时候就需要定义一个变量保存 ++ 前的结果,然后返回,如果这个时候使用的是引用的话,我们的函数结束之后,刚刚的变量就释放掉了,所以这个时候的引用是错误的。

  • *号运算符的重载。这个重载返回的应该是迭代器 所指向的数据,所以这个时候返回的是T&,然后我们又在上面对这个T&进行了一个封装,所以这个时候返回值直接使用Ref。

_List

关于链表,我们这里维护的是一个双向的循环的有头节点的双向链表

typedef List_Iterator<T, T&, T*> Iterator;//const迭代器typedef List_Iterator<T, const T&, const T*> Const_Iterator;

这里重点说明一下,为什么要将迭代器声明为三个模板参数的,这里就可以很好的体现了复用性了

这里为什么传递的是const T&和const T*呢,如果我们这样传递的话,在实现iterator的时候,里面有个函数是
Ref operator*()
{
return _it->_data;
}
这个时候因为我们传递的是const的,所以这个时候的Ref就成了const的了,这个时候的iterator所维护的节点
里面的数据就不可改变了,这就实现了const的作用了
这也是我们为什么设计iterator的时候为什么设计的是三个模板参数了
配合着const_iterator我们还需要模拟实现返回时const的end和begin

相关文章:

hdc mfc 画扇形图_使用echarts绘制条形图和扇形图

使用echarts绘制条形图和扇形图简单举例说明下echarts如何绘制条形图和扇形图代码示例echarts绘制条形图和扇形图var mychart1echarts.init(document.getElementById(chart1),light);// 指定图表的配置项和数据var option {title: {text: ECharts 入门示例},tooltip: {},legen…

Java学习笔记45:Java 线程与主线程之间的变量关系

运行下面的代码&#xff1a; package com.test.www;public class Test {public static int count 0;public static void inc() {//这里延迟1毫秒&#xff0c;使得结果明显try {Thread.sleep(1);} catch (InterruptedException e) {}count;}public static void main(String[] a…

会计的思考(36):会计--企业运营的数码相机

新浪网上看到一张图片&#xff0c;拍的是2009年12月3日的武汉市&#xff0c;整个城市在笼罩在一片污浊空气当中&#xff0c;让人震惊。虽然我在深圳&#xff0c;但也清楚好不到哪去。 震惊之余&#xff0c;感谢新浪&#xff0c;更感谢某位摄影师&#xff0c;让我们了解到生活的…

spring cloud连载第二篇之Spring Cloud Config

Spring Cloud Config Spring Cloud Config为分布式服务提供了服务侧和客户侧的外部配置支持。通过Spring Cloud Config你可以有一个统一的地方来管理所有应用的外部配置。 默认服务端存储实现用的是git&#xff0c;因此&#xff0c;它很容易支持配置环境的标签版本&#xff0c;…

CodeOnly

关于设置[Key]标志 要先添加 程序集的引用 在添加 using System.ComponentModel.DataAnnotations; 命名控件转载于:https://www.cnblogs.com/since87/archive/2013/06/09/3129399.html

继承和多态 2.0 -- 继承的六个默认成员函数

本文重要介绍普通继承中如何写派生类的六个默认成员函数&#xff0c;主要是针对在派生类中&#xff0c;如何调用基类的六个默认成员函数 需要说明的一点就是&#xff0c;如果子类中没有调用父类的函数时&#xff0c;系统会自动生成一个。 构造函数 子类中有父类的成员&#…

js 让鼠标右下角有一排小字_js布局中一排大字下面接着一排小字怎么打出来?...

展开全部可以使用 TypewriterJS 实现效果是这样 ,百度图片少了一部分&#xff0c;可以通过链接32313133353236313431303231363533e78988e69d8331333433626438看效果链接: 网页链接示例在这里: 网页链接文档地址: 网页链接使用步骤引用 TypewriterJShtmlcss.article__title {fon…

Hudson神奇的环境变量

Hudson神奇的环境变量 http://blog.sina.com.cn/s/blog_798f21a00100z6zw.html转载于:https://blog.51cto.com/myloveworld/950156

英语影视台词---四、Sideways

英语影视台词---四、Sideways 一、总结 一句话总结&#xff1a;杯酒人生 Sideways&#xff0c;大致意思是“偏离、倾斜、转向…”。很明显中文译名与英文原名并没有什么关联&#xff0c;《杯酒人生》这个名字&#xff0c;其实也可以译为《并肩前行》&#xff0c;应该是从影片内…

继承和多态 3.0 -- 菱形继承

单继承和多继承 C的继承方式是支持单继承和多继承的&#xff0c;首先看一下代码&#xff0c;分清单继承和多继承 单继承 class A { public:int _a; };class B :public A { public:int _b; };class C : public A { public:int _c; }; 类似于上面的方式就是单继承&#xff0c;或…

C# split 几种使用方法

第一种方法&#xff1a; string s "abcdeabcdeabcde"; string[] sArray s.Split(c); foreach (string i in sArray) Console.WriteLine(i.ToString()); Console.ReadKey();输出下面的结果:abdeabdeabd…

模糊匹配 读音_onenote搜索机制详解②:两种搜索模式,模糊与精确匹配

先从纯文本搜索讲起&#xff0c;这是最基本也是最重要的。从这篇开始&#xff0c;以及接下来连续几篇文章&#xff0c;都会介绍搜索的基础功能。注意&#xff0c;这几篇文章中谈论的都是基本的、正常的搜索功能&#xff0c;暂时不考虑Bug等因素。在很多软件&#xff08;例如wor…

EXT3与EXT4的主要区别

Linux kernel 自 2.6.28 开始正式支持新的文件系统 Ext4。 Ext4 是 Ext3 的改进版&#xff0c;修改了 Ext3 中部分重要的数据结构&#xff0c;而不仅仅像 Ext3 对 Ext2 那样&#xff0c;只是增加了一个日志功能而已。Ext4 可以提供更佳的性能和可靠性&#xff0c;还有更为丰富的…

Java IO 4 : RandomAccessFile

RandomAccessFile:   认识&#xff1a;java输入/输出流体系中功能最丰富的文件内容访问类 既可以读取文件内容&#xff0c;也可以向文件传输数据&#xff0c;并且支持“随机访问“的方式&#xff0c;程序可以跳转到任意地方来读写数据。 特点&#xff1a;与OutputStream/Writ…

二叉树 1.0 -- 创建二叉树、遍历二叉树、二叉树常见问题求解

树的结构主要是为了查找&#xff0c;这个主要是为了搜索&#xff0c;树的结构关注的不是增删查改 树 广义上面的树的结构我们不知道树的一个节点是有几个子节点的&#xff0c;所以这个时候我们需要定义的一种结构就是&#xff0c;一个节点的孩子是可以动态的增加的&#xff0…

impala 本年格式化时间_hive,hbase,impala之间的对比

hbase在三者中更注重的是存储&#xff0c;它实现了类似mysql的double write机制&#xff0c;但是它是一种NoSQL的数据库&#xff0c;并且是可以支持列式存储的&#xff0c;算是比较大的一个内存Hash表。hbase也采用了类似mysql中的mvcc的思想通过时间戳来做版本控制。hbase是在…

简单上手的游戏引擎

物理游戏引擎 GameSalad 转载于:https://www.cnblogs.com/sgdkg/archive/2013/06/14/3135882.html

Linux内核中关于定时器Timer的应用

2019独角兽企业重金招聘Python工程师标准>>> 在Touchscreen驱动中 1 声明 Ad7877.c (\linux-2.6.30.4\drivers\input\touchscreen): struct timer_list timer; /* P: lock */ 2 初始化 在函数 static int __devinit ad7877_probe(struct spi_device *spi) 中 执行 …

类加载过程中几个重点执行顺序整理

类的加载过程&#xff1a; 1、 JVM会先去方法区中找有没有相应类的.class存在。如果有&#xff0c;就直接使用&#xff1b;如果没有&#xff0c;则把相关类的.class加载到方法区 2、 在.class加载到方法区时&#xff0c;会分为两部分加载&#xff1a;先加载非静态内容&#xff…

二叉树 2.0 -- 非递归遍历

二叉树递归遍历存在的问题 如果我们的二叉树只有左子树&#xff0c;而且树的高度还很深的时候&#xff0c;这个时候递归调用遍历的时候&#xff0c;栈帧空间开辟的较大&#xff0c;很可能造成栈溢出。但是我们一个程序中&#xff0c;为堆分配的空间要比栈大的多&#xff0c;这…

田忌赛马贪心算法_田忌赛马 贪心算法

算法实验课回顾田忌赛马问题描述&#xff1a;你一定听说过田忌赛马的故事吧&#xff1f;如果3匹马变成n匹(n<100)&#xff0c;齐王仍然让他的马按照优到劣的顺序初赛&#xff0c;田忌可以按任意顺序选择他的赛马出赛。赢一局&#xff0c;田忌可以得到200两银子&#xff1b;输…

[转][小结][三种方法]实现WPF不规则窗体

实现WPF不规则窗体的三种常用的方法如下&#xff1a; 1.使用Blend等工具绘制一个不规则xaml&#xff0c;然后作为窗体的背景。这个可以参考xiaowei0705的这篇博文&#xff1a;WPF制作不规则的窗体 。 2.给window的Clip属性赋Path值。这个可以参考DebugLZQ前面的博文&#xff1a…

VirtualBox虚拟机网络连接设置的四种方式

这里我先给大家大致讲解下VBox的网络配置及应用。 VirtualBox的提供了四种网络接入模式&#xff0c;它们分别是&#xff1a;1、NAT 网络地址转换模式(NAT,Network Address Translation)2、Bridged Adapter 桥接模式3、Internal 内部网络模式4、Host-only Adapter 主机…

redis-deskmanager 连不上 虚拟机 - centos redis

1、没设置redis密码 &#xff1a; https://blog.csdn.net/HUXU981598436/article/details/54668779 2、关闭防火墙 转载于:https://www.cnblogs.com/Jomini/p/9650650.html

数据库1.0 -- 数据库的基本操作

安装数据库 安装数据库的时候我们需要安装三个软件&#xff0c;使用下面的命令&#xff0c;可能还会出现一些问题&#xff0c;关于数据库的安装&#xff0c;大家可以上网自行百度 yum install mysql yum install mysql-server yum install mysql-devel 我个人的理解大概是这…

12,缓冲运动。匀速运动停止条件

缓冲运动&#xff1a;iSpeed(iTarget-oDiv.offsetLeft)/7;速度离目标点越远&#xff0c;速度越大&#xff0c;离目标点越近速度越小&#xff1b; 只支持1px是最小单位&#xff0c;没有0.5px。所以当iSpeed为小数时如&#xff08;0.7&#xff09;&#xff0c;oDiv.style.LeftoDi…

如何配置mac的mysql环境_mac安装mysql数据库及配置环境变量

安装mysql下载mysql。我下载的是&#xff1a;mysql-8.0.11-macos10.13-x86_64.dmg双击打开mysql-8.0.11-macos10.13-x86_64.dmg&#xff0c;然后双击mysql-8.0.11-macos10.13-x86_64.pkg一路点击继续&#xff0c;傻瓜式安装&#xff0c;没什么好说的此处选择“Use Legacy Passw…

三层交换机vlan间访问(第一种方式)

真的是原创&#xff0c;但是得感谢Ys_routesim软件的制作方。我将命令调试后并进行了解释。若是属于侵权&#xff0c;请立即告知我。不过学习了网工后&#xff0c;大段解读源代码不属于侵权吧。呵呵。 交换机的三层交换实际是具有路由功能的交换机&#xff0c;比如思科的Cisco …

(转载)深入浅出设计模式——桥接模式(Bridge Pattern)

模式动机设想如果要绘制矩形、圆形、椭圆、正方形&#xff0c;我们至少需要4个形状类&#xff0c;但是如果绘制的图形需要具有不同的颜色&#xff0c;如红色、绿色、蓝色等&#xff0c;此时至少有如下两种设计方案&#xff1a; 第一种设计方案是为每一种形状都提供一套各种颜色…

数据库2.0 -- 数据类型和数据表的基本操作

mysql支持多种数据类型&#xff0c;一般可以分为&#xff0c;数值&#xff0c;日期时间和字符&#xff08;串&#xff09; 数值类型 日期和时间类型 字符串类型 创建数据表 我们首先应该明白的就是一个结构的问题&#xff0c;一个用户可以管理多个数据库&#xff0c;每个数据…