目录
➡️0. 前言
😊1.简易框架实现
🐔1. list和__list_node分析实现
🐔2. 无参构造
😊2.迭代器实现
🐔1. list普通迭代器面临问题及解决方案
🐔2. __list_node\iterator\list三类分析
🐔3. list中const迭代器面临问题及解决方案
🐔4. list中模板参数为自定义类型迭代器优化
🐔5. list迭代器的完整实现
😊3.其他成员属性的实现
🐔1. push_back()的实现
🐔2. 迭代器区间构造实现
🐔3. 拷贝构造及赋值的现代化方法实现(深浅拷贝参考vector)
🐔4. insert()的实现
🐔5. erase()的实现
🐔6. 析构的实现
➡️0. 前言
list 容器,又称双向链表容器,即该容器的底层是以带头双向循环链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。
list链表是序列容器,允许在序列内的任何位置进行常量时间的插入和删除操作,以及两个方向的迭代。
本篇文章暂时不讲解拟制迭代器,将后续进行讲解!
list单独提供了merge归并排序是通过与前面元素的链接和后面元素的链接的每个元素的关联在内部保持的。它们与forward_list非常相似:主要区别在于forward_list对象是单链表,因此它们只能向前迭代,以换取更小和更高效。与其他基本标准序列容器(array、vector和deque)相比,列表在容器内的任何位置插入、提取和移动元素(迭代器已经获得)方面通常表现更好,因此在大量使用列表的算法(如排序算法)中也表现更好。与其他序列容器相比,列表和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从所有已知位置(如开始或结束)迭代到该位置,这需要在这些位置之间的距离上花费线性时间。它们还会消耗一些额外的内存来保存与每个元素相关联的链接信息(对于包含小型元素的大型列表来说,这可能是一个重要因素)。
😊1.简易框架实现
🐔1. list和__list_node分析实现
list容器成员是一个结构体__list_node变量的指针,指向链表的头节点,而结构体__list_node便是链表的结点指向堆区的结构体,包含了模板数据类型的数据,以及前后指针,指向下一个节点!
C++一般很少采用内部类的方式实现,因此再实现list容器时,需要先实现struct __list_node结构体,以及list类,结构体内所有成员变量及成员属性皆为public,便于在list类中访问节点数据。
namespace Thb { template<class T> struct __list_node { T _data; __list_node<T>* _next; __list_node<T>* _prev; }; template<class T> class list { typedef __list_node<T> list_node; public: private: list_node* _head; }; }
🐔2. 无参构造
list为双向带头循环链表,因此在实例化list时,需要先给定头节点,且头节点的_next及_prev都指向自己!而给定头节点,需要 new 一个__list_node对象,因此对两个类都提供无参构造!
1、__list_node的无参构造,采用缺省参数构造,如果有数据,直接将数据初始化
list_node(const T& x = T()) :_data(x) ,_next(nullptr) ,_prev(nullptr) { }
2、list初始化,开辟头节点
public: typedef __list_node<T> list_node; void empty_init() { _head = new list_node; _head->_next = _head; _head->_prev = _head; } private: list() { empty_init(); }
😊2.迭代器实现
🐔1. list普通迭代器面临问题及解决方案
面临问题:
由于list和string、vector存储方式不同,list不是连续的物理空间,因此在实现迭代器时,++并不会跳转到下一个节点的位置,同时解引用操作,也不会获取到数据。
解决方案:
为了实现list非连续物理空间的容器,可以对其迭代器实现为一个对象,调用begin,end等成员方法时,返回一个迭代器对象,而对其所属域的迭代器对象内的操作符及运算符进行重写!
template<class T> struct __list_iterator { typedef __list_node<T> list_node; typedef __list_iterator<T> iterator; list_node* _node; __list_iterator(list_node* node) :_node(node) {} bool operator!=(const iterator& it) const { return _node != it._node; } iterator& operator++() { _node = _node->_next; return *this; } iterator operator++(int) { iterator temp(*this); _node = _node->_next; return *this; } T& operator*() const { return _node->_data; } };
上述迭代器类的大致实现,对迭代器的普遍用法 != 、前置 ++ 、--、后置 ++、--、以及解引用 * 访问数据进行了实现,虽然可以解决普通迭代的问题,但仍然具有很多方面的不足。
🐔2. __list_node\iterator\list三类分析
三个类是如何协调调用?为何iterator默认函数满足使用?
- 可通过代码观察迭代器未在堆区开辟空间,同时使用了list的节点进行构造,因此不需要采用delete析构节点,默认即可,否则源数据空间将会遭到破坏!
- 传递的节点皆是通过指针传递,因此可以访问到底层的数据!
- 尽管iterator可以访问底层数据,但是节点全部都被封装在list里,没有list传递节点,iterator是无法直接访问底层数据的,因此可以体现c++的封装特性!
- list迭代器并不涉及深拷贝问题,而只是获取一个节点的指针,重写++、--等便捷访问前后节点数据,因此使用默认赋值,及默认拷贝通过list传递地址即可!
🐔3. list中const迭代器面临问题及解决方案
面临问题:
虽然上述解决了普通迭代器所面临的问题,但是对于const对象,无法解决一个类模板所实现的迭代器对象,仅仅根据返回值的不同,无法构成函数重载!!!
解决方法:
- 第一种解决方案:重新实现一个对象__const_iterator,复写所有方法,返回const T&(pass原因:不符合高内聚,低耦合。重复代码太多,且累赘!)
- 第二种解决方案:此时就轮到类模板参数发挥作用了,C++模板可以提供多个模板参数,根据参数不同,模板实例化对象便是不同类型对象,而上述方法类模板参数为一个,因此每次使用list在使用iterator实例化的对象都是同类型对象,无法区分!
虽然上述方法,可以很好实现解耦,但是类模板还是不够完善,仍然面临的一些问题!
🐔4. list中模板参数为自定义类型迭代器优化
当list模板参数为自定义类型时,如下代码:
class Point { public: Point() { } Point(int x, int y) :_x(x), _y(y) { } int getx() { return _x; } int gety() { return _y; } private: int _x; int _y; }; void testPoint() { Thb::list<Point> ls; ls.push_back(Point(1, 2)); ls.push_back(Point(2, 3)); ls.push_back(Point(3, 4)); ls.push_back(Point(4, 5)); auto it = ls.begin(); while (it != ls.end()) { cout << "ponit:" << (*it).getx() << "/" << (*it).gety() << endl; it++; } }
此时迭代器要想调用类模板对象public方法和属性,只能先解引用获取到对象,再通过 . 的方式才能进行调用,不便于操作!
因此迭代器需要实现->可以直接进行调用,而如何重写 -> ,就需要分析c++特性,c++对于重写operator->时有特殊的规定:语法为了可读性,编译器对->进行特殊处理,重写时返回值再次使用->,即省略了一个->,相当于返回模板类型对象的地址!
T* operator->() const { return &(operator*()); } T& operator*() const { return _node->_data; } / void testPoint() { Thb::list<Point> ls; ls.push_back(Point(1, 2)); ls.push_back(Point(2, 3)); ls.push_back(Point(3, 4)); ls.push_back(Point(4, 5)); auto it = ls.begin(); while (it != ls.end()) { cout << "ponit:" << it->getx() << "/" << t->getx() << endl; it++; } }
分析代码:先通过重写的 this -> operator*(); 获取node节点的_data,便是获取模板Point实例化对象,再通过取地址&Point,获取实例化数据对象的地址,此时返回point实例对象的地址,便可以再次通过->访问类成员方法,在实际测试中可看出c++省略了一个->。
可以通过迭代器的完善观察,解引用 * 和 -> 访问操作符皆涉及到然会模板数据,当返回类型为 const T* 或者 const T& 时,便说明其是一个 const_iterator ,因此需要增加两个类模板参数!!!
🐔5. list迭代器的完整实现
新增Ref(reference引用),Ptr(pointer指针)两个模板参数,并对其成员函数返回值做出更改,对应模板传类型,而list中
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;展示了类模板传递不同,所实例化不同类型的迭代器对象,进行解耦!
template<class T, class Ref, class Ptr> struct __list_iterator { typedef __list_node<T> list_node; typedef __list_iterator<T, Ref, Ptr> iterator; list_node* _node; __list_iterator(list_node* node) :_node(node) {} bool operator!=(const iterator& it) const { return _node != it._node; } bool operator==(const iterator& it) const { return _node == it._node; } iterator& operator++() { _node = _node->_next; return *this; } iterator operator++(int) { iterator temp(*this); _node = _node->_next; return *this; } iterator& operator--() { _node = _node->_prev; return *this; } iterator operator--(int) { iterator temp(*this); _node = _node->_prev; return *this; } //T* operator->() { Ptr operator->() const { return &(operator*()); } //T& operator*() const { Ref operator*() const { return _node->_data; } }; template<class T> class list { typedef __list_node<T> list_node; void empty_init() { _head = new list_node; _head->_next = _head; _head->_prev = _head; } public: typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator; iterator begin() { return iterator(_head->_next); } iterator end() { return iterator(_head); } const_iterator begin() const { return const_iterator(_head->_next); } const_iterator end() const { return const_iterator(_head); } //默认函数 list() { empty_init(); } private: list_node* _head; }; }
😊3.其他成员属性的实现
🐔1. push_back()的实现
void push_back(const T& value) { list_node* tail = _head->_prev; list_node* newnode = new list_node(value); newnode->_next = _head; newnode->_prev = tail; _head->_prev = newnode; tail->_next = newnode; }
🐔2. 迭代器区间构造实现
template<class InputIterator> list(InputIterator first, InputIterator last) { empty_init(); while (first != last) { push_back(*first); first++; } }
🐔3. 拷贝构造及赋值的现代化方法实现(深浅拷贝参考vector)
list(const list<T>& ls) { empty_init(); list<T> temp(ls.begin(), ls.end()); swap(temp); } list<T>& operator=(list<T> ls) { swap(ls); return *this; } void swap(list<T>& ls) { std::swap(_head, ls._head); }
🐔4. insert()的实现
iterator insert(iterator pos, const T& x) { list_node* newnode = new list_node(x); list_node* pos_node = pos._node; newnode->_next = pos_node; newnode->_prev = pos_node->_prev; pos_node->_prev->_next = newnode; pos_node->_prev = newnode; return iterator(newnode); }
🐔5. erase()的实现
iterator erase(iterator pos) { assert(pos != end()); list_node* dele = pos._node; list_node* next = dele->_next; next->_prev = dele->_prev; dele->_prev->_next = next; delete dele; dele = nullptr; return iterator(next); }
🐔6. 析构的实现
void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } ~list() { clear(); delete _head; }
析构函数复用了clear,clear删除除哨兵位头节点的所有节点!而clear通过复用erase保证删除节点时,其逻辑关系仍然稳定不变!同时使用迭代器,begin记录了头节点的下一个位置,erase返回删除数据的下一个数据的位置的迭代器,进行迭代器it的更新!