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

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

引言

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

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

iterator的使用

看下面的代码

#include
using namespace std;
#include
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
using namespace std;#includetemplate<class T>
struct _ListNode
{_ListNode* _next;_ListNode* _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 Node;typedef List_Iterator 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 Node;public:typedef List_Iterator Iterator;//const迭代器typedef List_Iteratorconst 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::Ierator it = l.begin();cout << *it << endl;++it;cout << *it << endl;++it;cout << *it << endl;*/
//  _List::Iterator it = l.begin();//*it = 5;//l.Print();/*list l;l.push_back(0);l.push_back(1);list::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

更多相关:

  • 英语的重要性,毋庸置疑!尤其对广大职场人士,掌握英语意味着就多了一项竞争的技能。那,对于我们成人来说,时间是最宝贵的。如何短时间内在英语方面有所突破,这是我们最关心的事情。英语学习,到底有没有捷径可以走,是否可以速成?周老师在这里明确告诉大家,英语学习,没有绝对的捷径走,但是可以少走弯路。十多年的教学经验告诉我们,成功的学习方法可以借...

  • 展开全部 其实IDLE提供了一个显32313133353236313431303231363533e78988e69d8331333365663438示所有行和所有字符的功能。 我们打开IDLE shell或者IDLE编辑器,可以看到左下角有个Ln和Col,事实上,Ln是当前光标所在行,Col是当前光标所在列。 我们如果想得到文件代码...

  • 前言[1]从 Main 方法说起[2]走进 Tomcat 内部[3]总结[4]《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与源码解析 —— 精品合集》《Spri...

  • 【本文摘要】【注】本文所述内容为学习Yjango《学习观》相关视频之后的总结,观点归Yjango所有,本文仅作为学习之用。阅读本节,会让你对英语这类运动类知识的学习豁然开朗,你会知道英语学习方面,我们的症结所在。学习英语这类运动类知识,需要把握四个原则第一,不要用主动意识。第二,关注于端对端第三,输入输出符合实际情况第四,通过多个例子...

  • 点云PCL免费知识星球,点云论文速读。文章:RGB-D SLAM with Structural Regularities作者:Yanyan Li , Raza Yunus , Nikolas Brasch , Nassir Navab and Federico Tombari编译:点云PCL代码:https://github.co...

  • 原文出处: 韩昊    1 2 3 4 5 6 7 8 9 10 作 者:韩 昊 知 乎:Heinrich 微 博:@花生油工人 知乎专栏:与时间无关的故事   谨以此文献给大连海事大学的吴楠老师,柳晓鸣老师,王新年老师以及张晶泊老师。   转载的同学请保留上面这句话,谢谢。如果还能保留文章来源就更感激不尽了。 我保证这篇文章...

  • 原文出处: 韩昊   我保证这篇文章和你以前看过的所有文章都不同,这是 2012 年还在果壳的时候写的,但是当时没有来得及写完就出国了……于是拖了两年,嗯,我是拖延症患者…… 这篇文章的核心思想就是: 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维...

  • 很多Linux高手都喜欢使用screen命令,screen命令可以使你轻松地使用一个终端控制其他终端。尽管screen本身是一个非常有用的工具,byobu作为screen的增强版本,比screen更加好用而且美观,并且提供有用的信息和快捷的热键。 想象一下这样一个场景:你通过Secure Shell(ssh)链接到一个服务器,并...

  • NarrowbandPrimary Synchronization Signal时域位置每1个SFN存在一个NPSSSFNSubframeSymbol长度每个SFN5最后11个symbol11个symbols频域位置NB-IOT下行带宽固定180kHz,一个PRB,12个子载波。...

  •  [h1]反斜杠只能够阻止一个字符  [h2]位于键盘的左上角,和~公用一个键。...