模拟实现STL中的list

来源:互联网 发布:windows hadoop安装 编辑:程序博客网 时间:2024/06/06 04:58
模拟实现std::list
STL中实现的链表是一种带头结点的双向循环链表 相比于之前我们在C++初级阶段实现和模板实现的链表或者双向链表更加的方便,比如更加方便的遍历。方便查找,方便各种操作,具体的在实现的时候能够体会到。


实现代码:
#define _CRT_SECURE_NO_WARNINGS 1#include#include#includeusing namespace std;struct AA              //构造一个AA类型{int a;int b;};templatestruct NodeList{T _data;NodeList* _next;NodeList* _prev;NodeList(const T& a)     //为什么這里不加const就是错的   记住常量具有常属性 权限放大是危险的:_data(a)          //[*],_next(NULL),_prev(NULL){cout << "构造结点" << endl;}};//      迭代器templatestruct ListIterator                      //迭代器的目的就是遍历链表 没有必要写析构函数(析构把链表也析构了) 也没有写赋值运算符和拷贝构造,暂时是这么理解的{typedef NodeList Node;Node* _node;ListIterator(Node* node):_node(node){cout << "创建一个迭代器" << endl;}ref operator*(){   return _node->_data;         //出了作用域這个变量依然存在 所以返回引用}ptr operator->(){return &_node->_data;}ListIterator& operator++()    //注意:這里是前置++          ++i{_node = _node->_next;return *this;}ListIterator operator++(int)      //后置++           问题:虽然知道這里加一个(int)是为了区分前置和后置 ,但是对象怎么知道该调用哪一个?{ListIterator tmp = (*this);_node = _node->_next;return tmp;                              //   注意不能返回临时变量的引用  }bool operator!=(const ListIterator& l1){return this->_node != l1._node;}};// 主体templateclass List{typedef NodeList Node;private:Node* _head;public:typedef ListIterator Iterator;typedef ListIterator ConstIterator;       //constpublic:List():_head(new Node(T()))           //关于构造匿名对象,构造头节点{_head->_next = _head;_head->_prev = _head;}void PushBack(const T& d = T()){Node* tmp = new Node(d);Node* cur = _head->_prev;cur->_next = tmp;tmp->_next = _head;tmp->_prev = cur;_head->_prev = tmp;}void PopBack(){/*if (_head->_next = _head){cout << "没有有效节点" << endl;return;}else{Node* tail = _head->_prev;     //這样的PopBack有问题Node* tmp = tail->_prev;tmp->_next = _head;_head->_prev = tmp;delete tail;tail = NULL;}*/Node* tail = _head->_prev;Node* prev = tail->_prev;delete tail;prev->_next = _head;_head->_prev = prev;}Iterator Find(const T& d)        //通过迭代器来查找{Iterator it = Begin();while (it != End()){if ((*it) == d)return it;it++;}return End();}//随机位置的插入和删除//在pos前进行插入void Insert(Iterator pos, const T& d)     //注意:我这里的插入需要考虑是不是在头节点插入吗?  其实不需要,因为這是一个双向循环链表,并不需要考虑那么多{assert(pos._node);   //注意断言的使用  所指向的内容是否存在Node* Prev = pos._node->_prev;Node* Next = pos._node;Node* New = new Node(d);Prev->_next = New;New->_prev = Prev;New->_next = Next;Next->_prev = New;}//删除/*Iterator Erase(Iterator pos)                      {assert(pos._node&& pos != End());Node* Prev = pos._node->_prev;Node* Next = pos._node->_next;Prev->_next = Next;Next->_prev = Prev;delete pos._node;return Next}*/void Erase(Iterator& pos)               //更加好的改进方法,這里必须要使用引用,实际上是对下面的it4的操作{                                     //這个对使用类外打印函数的打印不造成影响assert(pos._node&& pos != End());Node* Prev = pos._node->_prev;Node* Next = pos._node->_next;Prev->_next = Next;Next->_prev = Prev;delete pos._node;pos = Prev;}ConstIterator Begin() const{ConstIterator Bit(_head->_next);                  //遍历是遍历有效节点   是从第二个开始的return Bit;}Iterator Begin(){Iterator Bit(_head->_next);                    return Bit;}ConstIterator End() const      //const修饰的成员函数,可以被const对象调用{ConstIterator Eit(_head);return Eit;}Iterator End(){Iterator Eit(_head);return Eit;}void Destory()      {Iterator it = Begin();while (it != End()){cout << "析构结点" << endl;Node* tmp = it._node;it++;delete tmp;}_head->_next = _head;_head->_prev = _head;}~List(){Destory();_head = NULL;}};//   打印   注意:迭代器的意义:1.能让很多容器都使用迭代器,使用同样的方式遍历和修改数据 2.能够在类外打印数据template void PrintList(List& l1){List::Iterator  it = l1.Begin();   //注意点:這里的l1是非const修饰的对象 非const对象可以调用 const成员函数   while (it != l1.End()){cout << *it << " ";it++;}cout << endl;}//template //void PrintList(const List& l1)//{//List::ConstIterator  it = l1.Begin();  //const修饰的对象只能调用const成员函数进行操作,所以上面我在没写const修饰的Begin()的时候编译是没法通过的//while (it != l1.End())                  //const修饰的对象实际上是不希望对他进行修改,但是实际上這里对象调用的两个接口不会对成员变量进行操作,操作的只有迭代器,所以为了达到我们使用std::list的迭代器那样的效果,//{                                      //我们必须重新写一个const迭代器,对迭代器管理的数据进行const约束才能最终达到目的,所以接下来又引出了为什么我自定义的迭代器的模板参数列表需要设置三个参数了(实际上是为了复用)//cout << *it << " ";//it++;//}//cout << endl;//}//--------------------------------------------------测试------------------------------------------------void test(){List l1;int a = 1;l1.PushBack(a);l1.PushBack(2);l1.PushBack(3);l1.PushBack(4);PrintList(l1);List l2;l2.PushBack(AA());l2.PushBack(AA());l2.PushBack(AA());l2.PushBack(AA());//PrintList(l2);List::Iterator it = l2.Begin();               //這里的it是指向AA结构体的指针while (it != l2.End()){it->a = 1;cout << it->b << " ";                //当是自定义类型的时候对->重载可以让迭代器指针访问结点内的结构体数据。it++;}List l3;l3.PushBack("aa");                   //如果不传参  调用缺省参数   string调用构造函数 初始的字符串为空  ""l3.PushBack("bb");l3.PushBack("cc");l3.PushBack("dd");l3.PushBack("ee");PrintList(l3);l3.PopBack();PrintList(l3);List::Iterator pos = l3.Find("cc");List::Iterator bit = l3.Begin();l3.Insert(pos, "kk");PrintList(l3);l3.Erase(pos);PrintList(l3);//cout << *pos << endl;//cout << pos - bit << endl;   //這样根本做不到指针相减得到元素个数  除非重载-号//********************迭代器失效问题***********************List  l4;l4.PushBack(1);l4.PushBack(2);l4.PushBack(3);l4.PushBack(4);l4.PushBack(5);l4.PushBack(6);l4.PushBack(7);l4.PushBack(8);/*List::Iterator it4 = l4.Begin();while (it4 != l4.End()){if (*it4 %2 == 0){it4 = l4.Erase(it4);}it4++;}*///如上:当我们想要通过迭代器删除其中的偶数的时候就会出现问题,也就是说:当我们删除一个迭代器指向的位置后想要让迭代器++ 迭代器可能会非法访问,这就是迭代器失效//這里就需要对删除做一些改进,解决问题。//如果我就是不想在使用部分进行修改,依然想要通过這种方式完成操作 ,那么就应继续在Erase内部进行修改  【腾讯面试题】List::Iterator it4 = l4.Begin();while (it4 != l4.End()){if (*it4 % 2 == 0){     l4.Erase(it4);}it4++;}}int  main(){test();return 0;}

在模拟STL中的list的时候遇到的问题和注意点:
1.在這里第一次模拟实现了迭代器,迭代器分三种:普通迭代器,const迭代器和反向迭代器,反向迭代器就是迭代器对象++可以倒着走。
普通迭代器的不仅能够遍历,还能够对链表的结点数据进行修改。
const迭代器就是遍历的时候不能对指向的链表结点数据进行修改,模拟实现的时候就有很多注意点。

(1)  普通迭代器和const迭代器在类外打印函数里面的形参就不一样,要注意,这也是是否使用const迭代器的前提

(2)  const修饰的对象只能够调用const修饰的成员函数,(const修饰的成员函数实质上是修饰的this指向的成员变量,函数内部的成员变量是不能修改的)  所以说在写const迭代器的时候 Begin( ) 和End( ) 都是需要用const 修饰的 
但是:但是這个时候调用了const修饰的Begin( ) 和End( )只不过对這两个结点的数据的修改,其中的迭代器我们在写的时候照样可以更改开头结点之后的链表的数据,所以就需要重新写一个迭代器,里面在*重载->的重载 ++的重载函数 都必须要不能够修改数据,這些的函数返回值是const修饰的,意思是带回来的值是不能被修改的。

2.注意:int 是一个类型,const int 是一个类型  這两者可以说看作没有任何关系  ,這是两种类型。

3.因为上面的原因,按道理来说可以重新编写一个迭代器,里面的模板参数是const T  但是這里STL使用了一种类似于模板嵌套的方法进行了复用,這个要再练习一次
---------------------迭代器模板参数列表--------------------------------------------------------------------
//template<classT,classRef,classPtr>
-----------------链表的内部将迭代器显示实例化的形式-------------------------------------
//typedefListIterator<T,T&,T*>Iterator;
//typedefListIterator<T,constT&,constT*>ConstIterator;
---------------------在main函数中实例化对象----------------------------------------------
//List<int> l1;


4.注意:Begin( ) 和End( )的区间是半闭半开的,注意再list里面的這两个的位置再哪里(带头结点的双向循环链表)
5.注意:对于這个链表的构造怎么构造?必须先把头节点new出来首尾相连 包括后面的拷贝构造也一定要先把头节点new出来 首尾相连,拷贝构造函数一定要记得在准备拷贝之前要做什么,深浅拷贝的问题




迭代器失效问题
为什么容器需要有迭代器? 目前我的理解是给看到這个容器的用户提供一个可以遍历容器的接口(虽然不是接口),在容器之外是看不到容器的指针的,只能看到迭代器。不同的容器都可以通过迭代器实现通过相同的操作进行遍历和修改,还有不同种类的迭代器。遍历容器之利器。但是在我们实现链表的时候,迭代器是存在问题的。要追究其本质:迭代器实际上是一个对象(假如叫it),這个对象的内容是链表结点的指针,当我们使用it++时候,节点指针往next走,但是我们想,假如it指向(形象的这么说)一个结点,然后這个结点被释放了,it++,结点往后走,会发生什么? 非法访问。
举一个实际的例子
当我们想要使用迭代器删除链表中的偶数的时候,会发生迭代器失效



原创粉丝点击