当前位置:   article > 正文

[ C++ ] STL---list的模拟实现

[ C++ ] STL---list的模拟实现

目录

结点类的模拟实现

迭代器类的模拟实现

构造函数

前置++与后置++

前置- -与后置 - -

== 与 !=运算符重载

* 运算符重载

-> 运算符重载

普通迭代器总体实现代码

list类的实现

list类的成员变量

构造函数

迭代器

insert()

erase()

push_front/push_back/pop_front/pop_back

front/back

clear()

empty()

swap()

拷贝构造函数

赋值运算符重载

析构函数


结点类的模拟实现

list 的底层结构为带头双向循环链表,所以结点类里的成员变量

T _data(数据)ListNode<T>* _prev(前驱指针)ListNode<T>* _next(后继指针)

成员函数只需要构造函数即可(指针初始化为nullptr,数据以缺省参数的形式进行初始化);

  1. template<class T>
  2. struct ListNode
  3. {
  4. //结点中的成员变量
  5. T _data;//数据
  6. ListNode<T>* _prev;//前驱指针
  7. ListNode<T>* _next;//后继指针
  8. //结点类的构造函数
  9. ListNode(const T& val=T())
  10. : _data(val)
  11. , _prev(nullptr)
  12. , _next(nullptr)
  13. {}
  14. };

迭代器类的模拟实现

迭代器并不关心容器底层的数据结构为顺序表、链表或者树型结构,提供统一的方式访问、修改容器中的数据并且遍历区间为左闭右开[begin,end);

vector与string的模拟实现中,迭代器均为原生指针,是因为vector和string底层物理空间连续(顺序表),那么list可否采用原生指针(结点指针)作为迭代器?

思考一:

若it为结点指针,it++,能否从链表的当前位置指向链表当前位置的下一位置?  (×)

思考二:

若it为结点指针,*it,能否得到链表结点中的数据_data?(×

解决方案:

采用原生指针作为list的迭代器均不满足需求,运算符重载可以对已有的运算符重新进行定义,赋予其另一种功能,从而满足需求,但是运算符重载的前提为自定义类型,而指针本身为内置类型,只能将结点指针封装为自定义类型,从而使用运算符重载满足需求;

迭代器的成员变量 : Node* _node;(_node为指向结点的指针)
迭代器的成员函数 : 运算符重载函数;

  1. template<class T>
  2. //使用struct关键字封装结点指针,方便访问数据成员_node
  3. //若使用class关键字封装节点指针,需要提供函数接口访问_node
  4. struct __List_iterator
  5. {
  6. typedef ListNode<T> Node;
  7. Node* _node;
  8. };

构造函数

  1. //__List_iterator it();
  2. //需要结点指针对_node进行有参构造且不能给定缺省值为nullptr,
  3. //否则解引用操作导致系统崩溃
  4. //__List_iterator(Node* node=nullptr)(×)
  5. //因此迭代器遍历链表时必须给定有效参数,参数为结点指针;
  6. __List_iterator(Node* node)
  7. :_node(node)
  8. {}

思考:迭代器内部需要实现析构函数,拷贝构造函数吗?

      1. 提供链表的结点指针给迭代器,方便迭代器访问链表结点,并不需要释放结点;

          而且对于内置类型(指针)成员变量,编译器自动生成的析构函数不做任何处理;

      2. 将一个迭代器拷贝给另一个迭代器,只需要两个迭代器指向同一个链表结点,

          而编译器自动生成的拷贝构造函数实现了浅拷贝,所以不需要实现拷贝构造函数;

前置++与后置++

前置++,this指针出作用域销毁,但是this指针指向的对象在函数结束不会被销毁,以传引用的方式返回以提升效率

  1. //++it
  2. __List_iterator<T>& operator++()
  3. {
  4. _node = _node->_next;
  5. return *this;
  6. }

返回类型太长,使用typedef重定义类型名;

  1. typedef __List_iterator<T> self;
  2. self& operator++()
  3. {
  4. _node = _node->_next;
  5. return *this;
  6. }

C++规定:

后置++重载时多增加一个int类型的参数,但调用函数时参数不需要传递,编译器自动传递;

后置++,tmp为临时拷贝对象,出作用域销毁,只能以传值的方式返回

  1. self operator++(int)
  2. {
  3. self tmp(*this);
  4. _node = _node->_next;
  5. return tmp;
  6. }

前置- -与后置 - -

  1. //--it
  2. self& operator--()
  3. {
  4. _node = _node->_prev;
  5. return *this;
  6. }
  7. //it--
  8. self operator--(int)
  9. {
  10. self tmp(*this);
  11. _node = _node->_prev;
  12. return tmp;
  13. }

== 与 !=运算符重载

==运算符重载比较两个迭代器对象的_node指针指向是否相同;

  1. bool operator==(const self& s)
  2. {
  3. return _node == s._node;
  4. }

!=运算符重载比较两个迭代器对象的_node指针指向是否相同;

  1. bool operator!=(const self& s)
  2. {
  3. return _node != s._node;
  4. }

* 运算符重载

重载 * 运算符的目的是为了得到迭代器对象的_node指针所指向的数据;

  1. T& operator*()
  2. {
  3. return _node->_data;
  4. }

-> 运算符重载

  1. struct Date
  2. {
  3. int _year = 2024;
  4. int _month = 1;
  5. int _day = 1;
  6. };
  7. int main()
  8. {
  9. //链表中的数据成员为自定义类型Date
  10. list<Date> lt;
  11. Date d1;
  12. lt.push_back(d1);
  13. //it为封装结点指针的迭代器类
  14. list<Date>::iterator it = lt.begin();
  15. //结点中的数据成员访问方式1: 结构体变量.结构体成员
  16. cout << (*it)._year << " " << (*it)._month << " " << (*it)._day << endl;
  17. cout << it.operator*()._year << " " << it.operator*()._month << " " << it.operator*()._day << endl;
  18. //结点中的数据成员访问方式2: 结构体指针->结构体成员
  19. //it->_year本应该为it->->_year,但由于->->可读性差,编译器优化为->;
  20. //第一个->去调用operator->重载函数返回Date*的指针,第二个->用来去访问自定义类型的成员变量;
  21. cout << it->_year << " " << it->_month << " " << it->_day << endl;
  22. cout << it.operator->()->_year << " " << it.operator->()->_month <<" " << it.operator->()->_day<< endl;
  23. }

当迭代器内部重载了operator->()函数,且该函数返回结点中的数据成员的地址,便可以使用->访问自定义类型数据中的成员变量;

  1. T* operator->()
  2. {
  3. return &_node->_data;
  4. }

普通迭代器总体实现代码

  1. template<class T>
  2. struct __List_iterator
  3. {
  4. typedef ListNode<T> Node;
  5. Node* _node;
  6. __List_iterator(Node* node)
  7. :_node(node)
  8. {}
  9. typedef __List_iterator<T> self;
  10. //++it
  11. self& operator++()
  12. {
  13. _node = _node->_next;
  14. return *this;
  15. }
  16. //it++
  17. self operator++(int)
  18. {
  19. self tmp(*this);
  20. _node = _node->_next;
  21. return tmp;
  22. }
  23. //--it
  24. self& operator--()
  25. {
  26. _node = _node->_prev;
  27. return *this;
  28. }
  29. //it--
  30. self operator--(int)
  31. {
  32. self tmp(*this);
  33. _node = _node->_prev;
  34. return tmp;
  35. }
  36. bool operator==(const self& s)
  37. {
  38. return _node == s._node;
  39. }
  40. bool operator!=(const self& s)
  41. {
  42. return _node != s._node;
  43. }
  44. T& operator*()
  45. {
  46. return _node->_data;
  47. }
  48. T* operator->()
  49. {
  50. return &_node->_data;
  51. }
  52. };

无论是普通迭代器还是const迭代器,均需要迭代器遍历容器中的内容,因此迭代器本身可以被修改,区别仅在于迭代器指向的内容是否可以被修改,那么该如何实现const迭代器类呢?

由于const迭代器本质为保护迭代器指向的内容不允许被修改,若实现const迭代器类,只需要普通迭代器的operator*()与operator->()两个接口的返回值采用const修饰,便保护容器中的内容不会被修改其余接口均保持不变;

  1. template<class T>
  2. struct __List_const_iterator
  3. {
  4. typedef ListNode<T> Node;
  5. Node* _node;
  6. __List_const_iterator(Node* node)
  7. :_node(node)
  8. {}
  9. typedef __List_const_iterator<T> self;
  10. self& operator++();
  11. self operator++(int);
  12. self& operator--();
  13. self operator--(int);
  14. bool operator==(const self& s);
  15. bool operator!=(const self& s);
  16. const T& operator*()
  17. {
  18. return _node->_data;
  19. }
  20. const T* operator->()
  21. {
  22. return &_node->_data;
  23. }
  24. };

上述实现方案,在同一份文件中存在普通迭代器类与const迭代器类,两者之间仅有两个接口的返回值不同,如此便造成了代码的冗余,导致可读性变差,那么该如何改进呢?

迭代器类增加两个模版参数,使用时便可实例化出普通迭代器与const迭代器;

  1. //迭代器实现最终总体代码,只给出函数声明与普通迭代器代码相同
  2. template<class T,class Ref,class Ptr>
  3. struct __List_iterator
  4. {
  5. typedef ListNode<T> Node;
  6. Node* _node;
  7. __List_iterator(Node* node)
  8. :_node(node)
  9. {}
  10. typedef __List_iterator<T> self;
  11. self& operator++();
  12. self operator++(int);
  13. self& operator--();
  14. self operator--(int);
  15. bool operator==(const self& s);
  16. bool operator!=(const self& s);
  17. Ref operator*()
  18. {
  19. return _node->_data;
  20. }
  21. Ptr operator->()
  22. {
  23. return &_node->_data;
  24. }
  25. };

当在list类中定义两个迭代器类,普通迭代器类,const迭代器类(Ref为 T&/const T& 类型,Ptr为 T*/const T* 类型)

  1. typedef __List_iterator<T,T&,T*> iterator;
  2. typedef __List_iterator<T,const T&,const T*> const_iterator;

当使用普通迭代器对象时,实例化出普通迭代器类(iterator),使用const迭代器对象时,实例化出const迭代器类(const_iterator) ;

list类的实现

list类的成员变量

list类的成员变量只需要一个头结点,便可通过迭代器访问其他节点元素;

  1. template<class T>
  2. class list
  3. {
  4. typedef ListNode<T> Node;
  5. public:
  6. typedef __List_iterator<T, T& , T*> iterator;//重命名普通迭代器
  7. typedef __List_iterator<T, const T&, const T*> const_iterator;//重命名const迭代器
  8. private:
  9. Node* _head;
  10. };

构造函数

  1. list()
  2. {
  3. _head = new Node; // 申请一个头结点
  4. _head->_next = _head; // 后继指针指向自己
  5. _head->_prev = _head; // 前驱指针指向自己
  6. }

迭代器

begin() : 构造出指针指向第一个结点的迭代器对象;
end() :    构造出指针指向头结点的迭代器对象;

  1. iterator begin()
  2. {
  3. //return iterator(_head->_next);
  4. //单参数的构造函数支持类型转换__List_iterator(Node* node)
  5. //支持Node* 转换为 迭代器对象
  6. return _head->_next;
  7. }
  8. iterator end()
  9. {
  10. return _head;
  11. }
  12. const_iterator begin() const
  13. {
  14. return _head->_next;
  15. }
  16. const_iterator end() const
  17. {
  18. return _head;
  19. }

insert()

  1. 新开辟一个结点newnode(值为val),得到当前结点的指针,前驱结点的指针;
  2. 前驱结点的_next 指向 newnode,newnode的_prev指向前驱结点;
  3. newnode的_next 指向当前结点,当前结点的_prev指向newnode;
  1. iterator insert(iterator pos, const T& x)
  2. {
  3. Node* cur = pos._node;//当前结点指针
  4. Node* prev = cur->_prev;//前驱结点指针
  5. Node* newnode = new Node(x);//新开辟结点
  6. prev->_next = newnode;
  7. newnode->_prev = prev;
  8. newnode->_next = cur;
  9. cur->_prev = newnode;
  10. //return iterator(newnode);
  11. return newnode;
  12. }

erase()

  1. 得到前驱结点和后继结点的指针;
  2. 前驱结点的_next 指向后继结点;
  3. 后继结点的_prev指向前驱结点;
  4. 删除当前结点,返回删除位置的下一个位置;
  1. iterator erase(iterator pos)
  2. {
  3. assert(pos != end());//不能删除空双向循环链表
  4. Node* cur = pos._node;//当前结点指针
  5. Node* prev = cur->_prev;//前驱结点指针
  6. Node* next = cur->_next;//后继结点指针
  7. prev->_next = next;
  8. next->_prev = prev;
  9. delete cur;
  10. return next;//返回删除位置的下一位置
  11. }

push_front/push_back/pop_front/pop_back

push_back :  尾插即在头结点前插入一个结点;
pop_back   :  尾删,删除最后一个结点;
push_front  :  头插即在第一个结点前(非头结点)插入一个结点;
pop_front   :   头删,删除第一个结点(非头结点);

  1. void push_back(const T& x)
  2. {
  3. insert(end(), x);
  4. }
  5. void push_front(const T& x)
  6. {
  7. insert(begin(), x);
  8. }
  9. void pop_back()
  10. {
  11. erase(--end());
  12. }
  13. void pop_front()
  14. {
  15. erase(begin());
  16. }

front/back

front() : 返回第一个结点数据的引用;
end()  :  返回最后一个结点数据的引用;

  1. T& front()
  2. {
  3. //*begin-->T& operator*()
  4. return *begin();
  5. }
  6. const T& front()const
  7. {
  8. return *begin();
  9. }
  10. T& back()
  11. {
  12. return *(--end());
  13. }
  14. const T& back()const
  15. {
  16. return *(--end());
  17. }

clear()

双向循环链表只保留头结点,遍历链表时调用erase接口进行删除,注意调用erase后迭代器it已经失效,使用返回值接收,自动指向下一结点;

  1. void clear()
  2. {
  3. iterator it = begin();
  4. while (it != end())
  5. {
  6. it = erase(it);
  7. }
  8. }

empty()

  1. //判断容器是否非空
  2. bool empty()const
  3. {
  4. return begin() == end();
  5. }

swap()

  1. //交换容器的头指针
  2. void swap(list<T>& tmp)
  3. {
  4. std::swap(_head, tmp._head);
  5. }

拷贝构造函数

  1. 申请一个头结点,构造空双向循环链表(新容器);
  2. 将 lt 中的数据拷贝到新构造的容器中;
  1. list(const list<T>& lt)
  2. {
  3. _head = new Node; // 申请一个头结点
  4. _head->_next = _head; // 后继指针指向自己
  5. _head->_prev = _head; // 前驱指针指向自己
  6. for (const auto& e : lt) // 拷贝到新构造的容器中
  7. {
  8. push_back(e);
  9. }
  10. }

赋值运算符重载

传统写法:

  1. 释放除了头结点以外的所有结点;
  2. 将 lt 中的数据拷贝到新构造的容器中;
  1. list<T>& operator=(const list<T>& lt)
  2. {
  3. // 防止自己给自己赋值
  4. if (this != &lt)
  5. {
  6. clear(); // 清空数据
  7. for (const auto& e : lt) // 拷贝到新构造的容器中
  8. {
  9. push_back(e);
  10. }
  11. }
  12. return *this; // 支持连续赋值
  13. }

现代写法:

  1. 拷贝构造出 lt 对象
  2. 交换 this 和 lt 的 _head 指针,出了作用域,lt 调用析构函数,释放掉原this的结点
  1. list<T>& operator=(list<T> lt) //拷贝构造lt对象
  2. {
  3. std::swap(_head, lt._head); //交换指针
  4. return *this; //支持连续赋值
  5. }

析构函数

  1. 使用 clear() 释放除了头结点以外的结点;
  2. 释放掉头结点;
  1. ~list()
  2. {
  3. clear();
  4. delete _head;
  5. _head = nullptr;
  6. }

欢迎大家批评指正,博主会持续输出优质内容,谢谢大家观看,码字不易,希望大家给个一键三连支持~ 你的支持是我创作的不竭动力~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/306276
推荐阅读
相关标签
  

闽ICP备14008679号