C++list类模仿实现

闲聊 闲聊 1272 人阅读 | 0 人回复

<
C++list类模拟完成



list类的根本构造

144956xm9ogxgsgbx8bybx.jpg

xxxxSTL中list是一个单背带头轮回链表。除头结面没有存储有用疑息中,其他node结面存储有用疑息。同时,为了避免代码冗余,关于存储疑息范例差别的成绩,将采取模板的方法处理。
xxxxlist需求创立3个类:结面类、迭代器类战链表类。
结面类

成员变量

xxxx便像C言语中链表的创立,C++的链表也需求构建一个类(相似于C的构造体)包罗节面中数据疑息、下一节面地点战上一节面的地点三部门疑息。
结面类成员变量以下:
  1. template<class T>
  2. struct _list_node_
  3. {
  4.         T _val;                                        //存储数据
  5.         _list_node_<T>* _next;
  6.         _list_node_<T>* _prev;
  7. };
复造代码
成员函数

xxxx阐发我们需求写的成员函数:发明只要机关函数需求写,由于正在new新的结面时,会主动挪用它的机关函数,假如没有写便会发生随机值。可是结面没有会拷贝机关,没有会赋值。并且节面的开释是list掌握。以是没有需求写拷贝机关、赋值运算法重载战析构函数。
结面类完好代码以下:
144957zdzfgr2j0be26ee2.jpg

注释:因为结面类的成员变量需求被其他类间接会见,以是要将成员变量设置成私有,以是间接用struct(默许内乱容为私有public)。上面迭代器类同理
迭代器类

xxxx差别于string、vector两种容器,list的迭代器并非本死的指针,而是启拆结面指针成了一个类。
迭代器类代码以下:
  1.         template<class T, class Ref, class Ptr>
  2.         struct _list_iterator_                                        //迭代器便是需求浅拷贝,没有需求从头写拷贝机关大概复造
  3.         {                                                                                //析构函数:迭代器只是存一下指针,指针指背的结面由链表办理,没有需求迭代器来开释空间
  4.                 typedef _list_node_<T> node;
  5.                 typedef _list_iterator_<T, Ref, Ptr> self;
  6.                 _list_iterator_(node* pnode = nullptr)
  7.                         :_pnode(pnode)
  8.                 {}
  9.                 Ref operator*()                                //没有减援用便是返回一个暂时变量,不成变动具有常属性
  10.                 {
  11.                         return _pnode->_val;
  12.                 }
  13.                 Ptr operator->()
  14.                 {
  15.                         return &_pnode->_val;                                //it->->_year   it->得到T*,T*->得到T类中的内乱容
  16.                 }
  17.                 bool operator==(const self& s) const
  18.                 {
  19.                         return _pnode == s._pnode;
  20.                 }
  21.                 bool operator!=(const self& s) const
  22.                 {
  23.                         return _pnode != s._pnode;       
  24.                 }
  25.                 self& operator++()
  26.                 {
  27.                         _pnode = _pnode->_next;
  28.                         return *this;
  29.                 }
  30.                 self operator++(int)                        //返回的是暂时工具,以是没有返回援用
  31.                 {
  32.                         self tmp(*this);
  33.                         _pnode = _pnode->_next;
  34.                         return tmp;
  35.                 }
  36.                 self& operator--()
  37.                 {
  38.                         _pnode = _pnode->_prev;
  39.                         return *this;
  40.                 }
  41.                 self operator--(int)                        //返回的是暂时工具,以是没有返回援用
  42.                 {
  43.                         self tmp(*this);
  44.                         _pnode = _pnode->_prev;
  45.                         return tmp;
  46.                 }
  47.                 //成员变量
  48.                 node* _pnode;
  49.         };
复造代码
xxxx成绩一:为何要如许做呢?由于因为list并非序列型容器,而是一个一个结面经由过程地点毗连起去的。而为了包管一切容器迭代器利用办法的同一性,皆要有对迭代器++/–/*等操纵。++/–会间接会见上一个结面大概下一个结面的数据;解援用操纵则会间接会见当前结面存储的数据。假如我们利用本死指针(结面的指针),则没法到达迭代器所请求的成果。因而,我们需求将结面的指针包拆成类,经由过程运算符重载改动它的举动。到达如许的目标。
xxxx成绩两:为何迭代器类没有写拷贝机关、赋值重载战析构函数呢?由于迭代器正在拷贝机关战赋值的时分,便是期望做到浅拷贝,便是将值举办拷贝,让他们指背统一块空间,而没有是将指背的内乱容举办深拷贝。因而编译器主动天生的浅拷贝便够了。其次,因为迭代器并出有本人开拓空间,因而无空间的开释,以是没有需求析构函数。结面的开释是链表的使命
xxxx成绩三:迭代器的模板中,为什么需求三个模板参数?三个模板参数别离代表甚么意义?。我们皆明白,list的迭代器差别于string战vector的本死指针范例的迭代器,他是一个类。关于本死指针来讲。被const润饰战没有被const润饰的两种指针便是两品种型。因而关于一般的思想来讲,迭代器便该当完成成两品种,一个是iterator,一个是const_iterator,两种分隔写。由于假如仍是念string、vector一样纯真把const战非const两种迭代器看做一种,正在list类里typedef成const战非const范例的迭代器。如许便会招致const无效,仍是能够变动内乱容。由于,迭代器如故长短const,您只是把迭代器的范例名改成了const_iterator,并出有改动迭代器的本质。以是当挪用解援用的操纵符重载时,仍是会返回T&,仍是能够变动。以是第一种处理办法便是将iterator战const_iterator两种迭代器范例分红两个类去写,如许const润饰的链表便会来挪用const_iterator的类,非const链表便会挪用iterator的类。可是如许便会形成大批代码的冗余。
xxxx因而,便呈现了利用模板的办法,去辨别两种迭代器。
xxxx第一个参数模板class T:代表的是数据的范例。
xxxx第两个参数模板class Ref:代表的是结面中数据的援用。
xxxx第三个参数模板class Ptr:代表的是结面中数据的指针。
详细以下图:
144957cn9e66ej56586w0k.png

假如仍是有不睬解能够经由过程上面成员函数的剖析再次减深了解。
成员函数

1、解援用运算符重载

  1. Ref operator*()                                //没有减援用便是返回一个暂时变量,不成变动具有常属性
  2. {
  3.         return _pnode->_val;
  4. }
复造代码
xxxx关于string、vector迭代器是本死指针的容器来讲。他们的迭代器便是数据的地点,因而解援用能够间接拿到数据。而list并非,以是需求运算符重载去改动它的举动。能够经由过程对迭代器解援用间接获得数据。以是return _pnode->val
xxxx同时,关于返回值,我们需求返回的是一个援用,不然,返回的便是一个暂时变量,具有常属性,不成改动。并且,假如迭代器是const_iterator以是它的Ref便是const T&便不克不及对数据举办写,只能读。const范例迭代器返回的是T&,便可读可写了。
2、箭头运算符重载

  1. Ptr operator->()
  2. {
  3.         return &_pnode->val;
  4. }
复造代码
xxxx若迭代器为本死指针,则it->就能够间接获得it的内乱容,可是list的迭代器不成以,以是便需求运算符重载改动举动。
xxxx值得留意的是
144957wgwiz0g49b6dbtib.jpg

3、迭代器前置++

  1. typedef _list_iterator_<class T, class Ref, class Ptr> self
  2. self& operator++()
  3. {
  4.         _pnode = _pnode->_next;
  5.         return *this;
  6. }
复造代码
xxxxself是一个范例,返回的便是迭代器本人。self是typedef出去的。前置–相似便是将_next换成_prev。
4、迭代器后置++

  1. self operator++(int)
  2. {
  3.         self tmp(*this);
  4.         _pnode = _pnode->_next;
  5.         return tmp;
  6. }
复造代码
xxxx由于返回的是tmp,是暂时变量,以是不克不及返回援用。由于返回的值是改动之前的值,以是要报错本来的值,返回再将*this改动。后置–相似,便是将_next换成_prev。
5、迭代器的比力

xxxx迭代器的比力比力简朴,以是便没有细道了。
链表类

  1. template<class T>
  2. class list
  3. {
  4.         typedef _list_node_<T> node;
  5. public:
  6.         typedef _list_iterator_<T, T&, T*> iterator;
  7.         typedef _list_iterator_<T, const T&, const T*> const_iterator;
  8.         list()
  9.         {
  10.                 _head = (node*)malloc(sizeof(node));
  11.                 _head->_next = _head;
  12.                 _head->_prev = _head;
  13.         }
  14.         list(const list<T>& lt)
  15.         {
  16.                 _head = new node;
  17.                 _head->_next = _head;
  18.                 _head->_prev = _head;
  19.                 for (const T& e:lt)
  20.                 {
  21.                         push_back(e);
  22.                 }
  23.         }
  24.                
  25.         list<T>& operator=(list<T> lt)                //当代写法
  26.         {
  27.                 swap(_head, lt._head);
  28.                 return *this;
  29.         }
  30.         ~list()
  31.         {
  32.                 clear();
  33.                 delete _head;
  34.                 _head = nullptr;
  35.         }
  36.                        
  37.         void clear()
  38.         {
  39.                 iterator it = begin();
  40.                 while (it != end())
  41.                 {
  42.                         it = erase(it);
  43.                 }
  44.         }
  45.         iterator begin()
  46.         {
  47.                 return _head->_next;
  48.         }
  49.         const_iterator begin() const
  50.         {
  51.                 return _head->_next;
  52.         }
  53.         iterator end()
  54.         {
  55.                 return _head;
  56.         }
  57.         const_iterator end() const
  58.         {
  59.                 return _head;
  60.         }
  61.         void push_back(const T& x)
  62.         {
  63.                 node* tail = _head->_prev;
  64.                 node* newnode = new node(x);                //挪用node的机关函数
  65.                 tail->_next = newnode;
  66.                 newnode->_next = _head;
  67.                 _head->_prev = newnode;
  68.                 newnode->_prev = tail;
  69.         }
  70.         void insert(iterator pos, const T& x)
  71.         {
  72.                 assert(pos._pnode);
  73.                 node* cur = pos._pnode;
  74.                 node* prev = cur->_prev;
  75.                 node* newnode = new node(x);
  76.                 prev->_next = newnode;
  77.                 newnode->_next = cur;
  78.                 cur->_prev = newnode;
  79.                 newnode->_prev = prev;
  80.         }
  81.         iterator erase(iterator pos)
  82.         {
  83.                 assert(pos._pnode);
  84.                 assert(pos != end());
  85.                 node* prev = pos._pnode->_prev;
  86.                 node* next = pos._pnode->_next;
  87.                 delete pos._pnode;               
  88.                 prev->_next = next;
  89.                 next->_prev = prev;
  90.                 return iterator(next);
  91.         }
  92.         size_t size()
  93.         {
  94.                 size_t sz = 0;
  95.                 iterator it = begin();
  96.                 while (it != end())
  97.                 {
  98.                         it++;
  99.                         sz++;
  100.                 }
  101.                 return sz;
  102.         }
  103.         bool empty()
  104.         {
  105.                 return begin() == end();
  106.         }
  107. private:
  108.         node* _head;
  109. };
复造代码
成员变量

xxxx成员变量便是一个头结面。同时借需求typedef出结面、const_iterator战iterator
成员函数

1、机关函数

  1. list()
  2. {
  3.         _head = (node*)malloc(sizeof(node));
  4.         _head->_next = _head;
  5.         _head->_prev = _head;
  6. }
复造代码
xxxx留意_head需求利用malloc去开拓空间,由于头结面没有需求存储数据,以是没有需求初初化。假如用new的话存正在一个成绩,便是假如呈现数据范例T也是list,那末用new开拓头结面空间时,便会主动迪挪用机关函数初初化,初初化便要挪用机关函数,挪用机关函数便要new并且初初化,便会呈现一个有限轮回。以是便需求malloc去开拓空间。
xxxx关于空链表来讲,该当是头结面的next战prev皆指背本人。
2、拷贝机关

  1. list(const list<T>& lt)
  2. {
  3.         _head = (node*)malloc(sizeof(node));
  4.         _head->_next = _head;
  5.         _head->_prev = _head;
  6.         for(const auto& e : lt)
  7.         {
  8.                 push_back(e);
  9.         }
  10. }
复造代码
xxxx能够复用代码,利用push_back。可是push_back之前,必需是标准的空链表,必需初初化。
3、赋值重载

  1. list<T>& operator=(const list<T> lt)
  2. {
  3.         swap(_head, lt._head);
  4.         return *this;
  5. }
复造代码
xxxx那是一种当代写法,很简朴,先利用拷贝机关,机关出一个完整相同的,如许把*this的_head取拷贝机关出去的_head一换,如许便间接把拷贝机关出去的内乱容的给了this
4、析构函数

  1. ~list()
  2. {
  3.         clear();
  4.         delete _head;
  5.         _head = nullptr;
  6. }
复造代码
xxxx一样的原理,也是复用,先用clear,将链表置空(一切的结面皆被开释失落),然后再开释失落头结面。
5、clear

  1. void clear()
  2. {
  3.         iterator it = begin();
  4.         while(it != end())
  5.         {
  6.                 it = erase(it);
  7.         }
  8. }
复造代码
xxxx挨个开释一切结面便是复用erase,同时,由于开释结面后,便没法找到该结面后的结面,以是it要担当erase的返回值(即:被开释结面后结面的迭代器)
6、begin()、end()

  1. iterator begin()
  2. {
  3.         return _head->_next;
  4. }
  5. const_iterator begin() const
  6. {
  7.         return _head->_next;
  8. }
  9. iterator end()
  10. {
  11.         return _head->_prev;
  12. }
  13. const_iterator end() const
  14. {
  15.         return _head->_prev;
  16. }
复造代码
144958hg9kr90ongut0k1n.jpg

7、insert

  1. void insert(iterator pos, const T&x)
  2. {
  3.         assert(pos._pnode);
  4.         node* cur = pos._pnode;
  5.         node* prev = cur->_prev;
  6.         node* newnode = new node(x);
  7.         prev->_next = newnode;
  8.         newnode->_next = cur;
  9.         cur->_prev = newnode;
  10.         newnode->_prev = prev;
  11. }
复造代码
xxxxprev、cur、next三个结面的链接干系弄分明便可,因为是带头单背轮回,以是,没有需求考虑结面之间毗连的挨次,也没有需求考虑多种状况。需求考虑的便是pos除能否为空,空的地位不克不及操纵,断行一下。进步宁静性。
8、erase

  1. iterator erase(iterator pos)
  2. {
  3.         assert(pos._pnode);
  4.         assert(pos != end());
  5.         node* prev = pos._pnode->_prev;
  6.         node* next = pos._pnode->_next;
  7.         delete pos._pnode;
  8.         prev->_next = next;
  9.         next->_prev = prev;
  10.         return iterator(next);
  11. }
复造代码
xxxx删除后,为了避免迭代器生效,以是要把下一名置的的迭代器当作返回值返回。那是STL中容器的遍及做法。
其他小型接心

xxxx其他的接心皆比力比力简朴,间接看代码就可以大白,代码正在上里的list类完好版部门。
xxxx
xxxx
xxxx
xxxx
xxxx假如读者另有任何疑问,能够攻讦留行,大概公疑我,我们一同讨论,一同前进

免责声明:假如进犯了您的权益,请联络站少,我们会实时删除侵权内乱容,感谢协作!
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,按照目前互联网开放的原则,我们将在不通知作者的情况下,转载文章;如果原文明确注明“禁止转载”,我们一定不会转载。如果我们转载的文章不符合作者的版权声明或者作者不想让我们转载您的文章的话,请发帖留言提供原创证明,我们将积极配合您!
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并自负版权等法律责任。
回复

使用道具 举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则