list的简单模拟实现

文章目录

目录

文章目录

前言

一、使用list时的注意事项

        1.list不支持std库中的sort排序

2.去重操作

3.splice拼接

二、list的接口实现

        1.源码中的节点

        2.源码中的构造函数

        3.哨兵位头节点

        4.尾插和头插

5.迭代器*

       5.1 迭代器中的operator++和--

          5.2其他迭代器中的接口

       5.3迭代器类的使用

        5.4 前置后置--

        5.5 list不支持重载+和-

        5.6 operator->

6.const迭代器

        6.1写俩个类实现const迭代器

        6.2更简洁的const 迭代器

7.insert插入

8.erase删除节点

9.insert和erase的复用

        9.1尾插        

        9.2尾删

        9.3头插

        9.4头删

        10.析构函数

        11.拷贝构造

        12.operator=赋值操作

13.initializer_list构造

14.反向迭代器


前言

        list是带头双向循环链表。是序列容器,允许在序列中的任何位置进行常数时间的插入和删除操作,并且可以在两个方向上进行迭代。list被实现为双向链表;双向链表可以将其包含的每个元素存储在不同且不相关的存储位置中。通过将每个元素与其前面的元素和后面的元素的链接关联起来,可以在内部保持排序。


一、使用list时的注意事项

        1.list不支持std库中的sort排序

        由于std库中sort内部是快排操作,涉及三数取中操作,需要迭代器可以相减。而由于list不支持迭代器相减操作 ,所以,不能使用std库中的sort排序。因为效率和空间问题,链表的空间不是连续的,实现迭代器相减操作非常影响效率

        list想要进行排序就要使用它专门提供的操作:

默认升序:

#include <iostream>
using namespace std;
#include <list>
int main()
{list<int> lt1 = { 9,8,4,2,1,3 };for (auto e : lt1){cout << e << ' ';}cout << endl;lt1.sort();for (auto e : lt1){cout << e << ' ';}return 0;
}

降序:
使用greater<int>进行排序。也可以直接使用匿名对象(lt1.sort(greater<int>());)

#include <iostream>
using namespace std;
#include <list>
int main()
{list<int> lt1 = { 9,8,4,2,1,3 };//lt1.sort(greater<int>());greater<int> gt;lt1.sort(gt);for (auto e : lt1){cout << e << ' ';}return 0;
}

list中的排序是归并排序。在使用如果使用list排序,它的效率较vector的排序效率较低。所以大量数据时不建议使用list 的排序

2.去重操作

操作中的去重是去掉重复的元素,但是前提是要进行排序

void test_list02()
{list<int> lt1 = { 9,8,4,2,1,3 ,2,1,3};for (auto e : lt1){cout << e << ' ';}cout << endl;//直接调用去重lt1.unique();for (auto e : lt1){cout << e << ' ';}
}

没有进行去重操作无法使得相同元素在一起。调用排序sort:

3.splice拼接

        实际上就是转移另一个链表中的元素到目标链表的某个位置之前,可以转移一个或者整个链表。

注意是将另一个链表中的节点直接拿过来,所以另一个链表中的元素在转移之后要去掉。

也可以将自己的元素转移到自己的某个位置 。

void test_list01()
{list<int> mylist1;for (int i = 1; i <= 4; i++){mylist1.push_back(i);  // 1 2 3 4}for (auto e : mylist1)cout << e << ' ';cout << endl;auto it = std::find(mylist1.begin(), mylist1.end(), 3);//将3转移到头mylist1.splice(mylist1.begin(), mylist1, it);for (auto e : mylist1)cout << e << ' ';
}

二、list的接口实现

        1.源码中的节点

        list一般是带头双向循环链表,所以节点的结构是俩个指针:

源码中用void*指针,在后面使用时都要进行强转成节点类型的指针。

我们在实现过程中不必这样,直接使用模板定下指针的类型:

 // List的节点类template<class T>struct ListNode{ListNode<T>* _prev;ListNode<T>* _next;T _val;};

再看整个list框架,迭代器刚开始看不懂,往下翻发现有个节点的指针:

link_type是什么?可以通过vs中ctrl+F功能进行查找,往上翻:

link_type实际上就是节点的指针

#pragma once
#include <iostream>
using namespace std;namespace mylist
{template<class T>struct ListNode{ListNode<T>* _prev;ListNode<T>* _next;T val;};template<class T>class list{typedef ListNode<T> Node;private:Node* _head;};
}

为什么节点不使用class?原因是因为节点的成员变量和成员函数需要频繁访问,使用public和友元也可以,但是这样实际上和struct一样,并且使用public和友元实际上破坏了封装

        2.源码中的构造函数

                empty_initialize()从字面意思上理解就是空节点初始化

观察:

这个函数就是给出哨兵位

        get_node()函数就是获取节点,观察:

        C++获取节点时,都是从内存池上获取的,内存池就是我们使用空间配置中自己管理的空间

使用内存池的好处就是可以更灵活的利用空间,使得代码空间获取效率提高。由于我们初步接触list,所以我们使用new开辟的就好

         由于内存池的空间是我们自己管理,所以对于自定义类型不能自动的调用构造函数,所以在源码中还有一个creat_node()函数:

        consruct函数调用的是构造函数。对开辟好的内存池进行初始化,也就是定位new的功能。

这里不是本章重点,仅仅了解一下。

        代码实现很简单:

		void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}

        3.哨兵位头节点

       创建节点时,哨兵为的prev和next都应该指向自己:

	template<class T>class list{public:typedef ListNode<T> Node;public:void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}private:Node* _head;};

        写到这时我们实例化一个对象观察是否有错误

void test_mylist01()
{mylist::list<int> lt1;
}

结果:

        由于节点是一个自定义类型,new在对自定义类型开空间时,需要调用相应的默认构造函数.

而Node中没有构造函数,所以我们加上默认构造:

	template<class T>struct ListNode{ListNode<T>* _prev;ListNode<T>* _next;T val;ListNode(const T& data = T()):_prev(nullptr), _next(nullptr), val(data) {}};

        

        4.尾插和头插

        尾插和头插操作源码中调用的是insert():

        观察insert:

在迭代器position位置插入x。 

                先写一个简单的尾插

找尾:

修改指向:

代码:

void push_back(const T& x)
{//创建新节点然后初始化Node* newnode = new Node(x);//找尾Node* ptail = head->prev;//改变新节点指向newnode->_next = head;newnode->_prev = ptail;//改变head和ptail指向ptail->_next = newnode;head->prev = newnode;
}

测试代码:

void test_mylist01()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);
}

        结果:

        已经插入了3个节点然后遍历节点?

遍历节点有很多种方式,最常用的是使用迭代器遍历。接下来我们进入重点。

5.迭代器*

        链表的迭代器实现与vector和string不同,考虑到没有operator[],并且不像vector那样空间连续,使用+=比较麻烦空间不连续。有没有更好的方法?

        迭代器模拟的是指针的行为。

        实际上链表要遍历很简单,因为链表中已经有后继节点和前驱节点了。

        这里不能像vector那样直接typedef一个指针成为迭代器。空间不连续。如何实现一个迭代器,可以实现++到下一个节点、--到前一个节点、解引用*访问节点?

typedef Node* iterator;无法满足我们的行为。

        我们一般会想到函数重载和重载运算符,那么如何将这些函数封装成一个迭代器?答案是--。而++和--等运算符对内置类型可以直接使用,但是对于自定义类型我们需要重载,而重载的条件之一就是必须有一个参数是自定义类型,所以迭代器用类封装再好不过了。

有了类就可以定义迭代器的行为。

template<class T>
class ListIterator
{typedef ListNode<T> Node;Node* _node;
};

由于迭代器实际上是对节点的指针进行操作,所以我们需要指针的成员变量:

迭代器用节点的指针构造。所以在迭代器中还需要构造函数:

	template<class T>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T> Self;//指向迭代器本身的类型重命名public:Node* _node;public:ListIterator(Node* node):_node(node){}};//用迭代器时,要获取指针:iterator(节点的指针);

       5.1 迭代器中的operator++和--

        由于++和--的返回值是迭代器,所以在迭代器中还需要一个指向自己的typedef。

		typedef ListNode<T> Node;typedef ListIterator<T> Self;public:Node* _node;public:ListIterator(Node* node):_node(node){}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}

          5.2其他迭代器中的接口

        接下来我们先写出接口,然后进行分析;注意迭代器中构造函数和其他函数应该是公有的

template<class T>
class ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T> Self;
public:Node* _node;
public:ListIterator(Node* node):_node(node){}Self& operator++()//前置++{_node = _node->_next;return *this;}Self operator++(int)//后置++{Self tmp(*this);_node = _node->_next;return tmp;}T& operator*()//访问数据{return _node->val;}bool operator!=(const Self& it)//比较节点地址{return _node != it._node;}
};

       5.3迭代器类的使用

        我们可以通过迭代器进行修改数据(operator*),也可以进行比较。

        实现迭代器begin()和end()(迭代器的实例化):

	template<class T>class list{public:typedef ListNode<T> Node;typedef ListIterator<T> iterator;public:iterator begin(){return iterator(_head->_next);//匿名对象}iterator end(){return iterator(_head);//末尾就是哨兵位}
//代码....

        使用迭代器遍历链表测试接口:

void test_mylist02()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);mylist::list<int>::iterator it = lt1.begin();while (it != lt1.end()){cout << *it << ' ';++it;}
}

        代码结果:

测试前置后置++:

void test_mylist02()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);//测试前置++mylist::list<int>::iterator it1 = lt1.begin();cout << *(++it1) << endl;cout << *it1 << endl;cout << endl;//测试后置++mylist::list<int>::iterator it2 = lt1.begin();cout << *(it2++) << endl;cout << *it2 << endl;
}

结果:

        5.4 前置后置--

        和++类似找到前一个节点:

Self& operator--()
{_node = _node->_prev;return *this;
}
Self operator--(int)
{Self tmp(*this);_node = _node->_prev;return tmp;
}

        5.5 list不支持重载+和-

        由于链表的空间地址不连续,重载+和-就需要遍历到n次节点,所以效率不高,标准库中未支持+、-。

        迭代器不需要写析构函数,不需要对节点进行释放。节点的释放应该由list来做。

        5.6 operator->

        标准库中的list不仅仅重载了operator*并且重载了operator->:

        图中operator*的返回值是引用(被typedef了),而operator->的返回值是T*的指针即数据的指针。

T& operator*()
{return _node->val;
}
T* operator->()
{return &_node->val;
}

为什么要重载->?观察下面代码:
假设我们需要存储坐标

void test_mylist03()
{struct Pos{int _row;int _col;Pos(int row = 0, int col = 0)//需要默认构造,节点需要对象的默认构造:_row(row),_col(col){}};mylist::list<Pos> lt1;lt1.push_back(Pos(1, 2));lt1.push_back(Pos(3, 4));lt1.push_back(Pos(5, 6));//迭代器遍历数据mylist::list<Pos>::iterator it = lt1.begin();while (it != lt1.end()){cout << *it << ' ';//这样是编译不过的++it;}
}

我们需要访问成员:

	//迭代器遍历数据mylist::list<Pos>::iterator it = lt1.begin();while (it != lt1.end()){cout << (*it)._row << ':' << (*it)._col << endl;++it;}

结果:

那有不有更便捷的方式?如果是Pos*的数据该怎么访问?

所以我们重载了operator->:

T* operator->()
{return &_node->val;
}//迭代器遍历数据mylist::list<Pos>::iterator it = lt1.begin();while (it != lt1.end()){//cout << (*it)._row << ':' << (*it)._col << endl;cout << it->_row << ':' << it->_col << endl;++it;}

结果:

这里就会有疑惑,这个->为什么可以调用成功?在平常使用时不应该需要一个结构体指针才用到 ->吗?

实际上这就是一个结构体指针调用,由于重载的特性,使用->会直接执行迭代器中我们所写的重载函数operator->().

在代码中调用了operator->函数,实际上是俩个对头,为了可读性将第二个->省略了

6.const迭代器

        6.1写俩个类实现const迭代器

        在访问const链表的时,需要const迭代器,如果使用非const迭代器则会报错:

        const迭代器的作用时可以访问,不可修改。

不能在我们实现的迭代器前加const修饰:

所以我们需要自己写一个const迭代器类,如何做到可以遍历,但是不能修改

只需要将访问的函数可以修改的值的函数const修饰,将迭代器指向的内容修饰即可。

即将operator*和operator->进行修饰:
        list类中:

	typedef ListIterator<T> iterator;typedef ConstListIterator<T> const_iterator;

        定义新的const迭代器类模板:

	template<class T>class ConstListIterator{typedef ListNode<T> Node;typedef ConstListIterator<T> Self;public:Node* _node;ConstListIterator(Node* node):_node(node){}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}const T& operator*(){return _node->val;}const T* operator->(){return &_node->val;}bool operator!=(const Self& it){return _node != it._node;}};

        list类模板中定义新的迭代器:

typedef ListNode<T> Node;
typedef ListIterator<T> iterator;
typedef ConstListIterator<T> const_iterator;
iterator begin()
{return iterator(_head->_next);//匿名对象
}
const_iterator begin()const
{return const_iterator(_head->_next);//匿名对象
}
iterator end()
{return iterator(_head);//末尾就是哨兵位
}
const_iterator end() const
{return const_iterator(_head);//末尾就是哨兵位
}

const迭代器:

观察测试:

*it不能修改,it可以修改。


写到这里肯定会觉得写俩个类是不是很重复。能不能像下面这样:

这样是不行的,因为,迭代器中的所有T类型变为了const T类型,除了operator*和operator->符合我们的预期其他的函数,参数全部改变,那么const对象的迭代器不能匹配const迭代器中的其他函数,进而报错。

如list<int>时,const迭代器中的节点都是const int。

迭代器中:

链表中:

节点的类型都不匹配了。

        6.2更简洁的const 迭代器

        增加俩个模板参数,改变opeartor*和operator->:
同一个类模板给不同参数,就是不同类型:
        class Ref表示引用,Ptr表示指针。

template<class T,class Ref, class Ptr>
class ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T,Ref,Ptr> Self;
public:Node* _node;ListIterator(Node* node):_node(node){}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*(){return _node->val;}Ptr operator->(){return &_node->val;}bool operator!=(const Self& it){return _node != it._node;}
};

        list中定义:

	template<class T>class list{public:typedef ListNode<T> Node;typedef ListIterator<T,T&,T*> iterator;//typedef ConstListIterator<T> const_iterator;typedef ListIterator<T,const T&, const T*> const_iterator;

        其实就是写了俩个类,交给编译器完成,让编译器打工。

俩种写法实际上没有区别,但是,减少了我们的代码量

测试:

void Func(const mylist::list<int>& lt)
{mylist::list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';++it;}
}
void test_mylist04()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Func(lt1);
}

结果:

        也可以传递俩个模板参数:

7.insert插入

                源码插入:

        我们实现一个简单的插入;

	void insert(iterator pos, const T& x){//创建新的节点Node* newnode = new Node(x);//新节点Node* cur = pos._node;  //pos节点Node* prev = cur->_prev;//前驱节点newnode->_next = cur;newnode->_prev = cur->_prev;//改变前驱节点的指向//prev newnode curprev->_next = newnode;cur->_prev = newnode;}

思考:list中的迭代器有无迭代器失效?

        链表中的迭代器不会失效,因为它的空间不连续。没有扩容这一说法。但是为了和库保持一致,我们和vector一样,给insert一个返回值:

	iterator insert(iterator pos, const T& x){//代码....return iterator(newnode);}

测试代码:

void test_mylist05()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);for (auto e : lt1){cout << e << ' ';}lt1.insert(lt1.begin(), 5);lt1.insert(lt1.end(),6);cout << endl;for (auto e : lt1){cout << e << ' ';}
}

结果:

8.erase删除节点

        改变前继节点和后继节点的指向。

	#include <assert.h>iterator erase(iterator pos){//不能删除哨兵位assert( pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//prev cur nextprev->_next = next;next->_prev = prev;delete cur;//删除后迭代器失效,因为pos指向的节点已经被释放//需要返回值来获取下一个节点的元素。return iterator(next);}

由于delete释放了pos位置的节点,所以删除有迭代器失效。我们需要迭代器返回。

测试:

void test_mylist06()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);for (auto e : lt1){cout << e << ' ';}lt1.erase(lt1.begin());cout << endl;for (auto e : lt1){cout << e << ' ';}
}

结果:

9.insert和erase的复用

        9.1尾插        
	void push_back(const T& x){创建新节点然后初始化//Node* newnode = new Node(x);找尾//Node* ptail = _head->_prev;改变新节点指向//newnode->_next = _head;//newnode->_prev = ptail;改变head和ptail指向//ptail->_next = newnode;//_head->_prev = newnode;insert(end(), x);}
        9.2尾删
void pop_back()
{insert(--end());
}
        9.3头插
	void push_front(const T& x){insert(begin(), x);}
        9.4头删
	void pop_front(){erase(begin());}

测试:

实际上迭代器我们经常要访问它的成员变量和成员函数,所以迭代器也可以写出strcut 的类。

虽然它公有但是,我们接触的是list而不是迭代器的类模板。

        10.析构函数

        链表的析构需要一个一个节点释放,在观察list等容器的代码会发现,一般的容器都会有一个clear的函数。

        clear函数专门用于清除有效数据的空间,而不清理哨兵位

void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}

        析构函数在这个函数基础上进行空间释放:

~list()
{clear();delete _head;_head = nullptr;
}

        11.拷贝构造

        不写拷贝构造编译器会生成一个,该默认生成的拷贝构造是值拷贝即浅拷贝。使用浅拷贝构造的链表,和原链表指向的是一块空间。

void test_mylist08()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Func(lt1);mylist::list<int> lt2(lt1);lt1.push_back(5);Func(lt2);
}

俩个链表指向同一块空间。浅拷贝会有俩个问题
1.修改lt1,lt2也会跟着改变。

2.析构时会对同一块空间进行释放俩次。

        所以我们需要自己写一份深拷贝:

使用empty_init创建一个新的哨兵位,不指向旧空间。

//lt1(lt2)
list(const list<T>& lt)
{empty_init();for (const auto& e : lt){push_back(e);}
}

        测试代码:

void Func(const mylist::list<int>& lt)
{mylist::list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';++it;}cout << endl;
}
void test_mylist08()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Func(lt1);mylist::list<int> lt2(lt1);lt1.push_back(5);Func(lt1);Func(lt2);
}

        结果:

        12.operator=赋值操作

        赋值操作也需要深拷贝,有了拷贝构造,赋值操作就可以使用现代写法:

        函数的参数ltlt1一份拷贝,然后将拷贝的空间lt2进行交换,lt2指向的空间就是lt的空间,最后出函数作用域对lt空间进行释放。

	list<T>& operator=(list<T> lt){std::swap(_head, lt._head);return *this;}

测试代码:

void test_mylist09()
{mylist::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);Func(lt1);mylist::list<int> lt2;lt2.push_back(1);lt2.push_back(2);Func(lt2);lt2 = lt1;Func(lt2);
}

测试结果:

13.initializer_list构造

        该构造就是支持{}括号构造:

list(initializer_list<T> il)
{empty_init();for (const auto& e : il){push_back(e);}
}

        测试代码:

void test_mylist10()
{mylist::list<int> lt1 = { 1,2,3,4,5 };Func(lt1);
}

14.反向迭代器

        反向迭代器我们需要学会复用iterator:

template <T>
class list
{
public://反向迭代器typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin(){return reverse_iterator(end());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}};

        反向迭代器就是从后开始往前遍历,那么使用普通迭代器。从最后一个元素到哨兵位

	// 适配器 -- 复用template<class Iterator, class Ref, class Ptr>struct Reverse_iterator{typedef Reverse_iterator< Iterator, Ref, Ptr> Self;Iterator _it;Reverse_iterator(const Iterator& it):_it(it){}//Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp(*this);_it--;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp(*this);_it++;return tmp;}Ref operator*(){Iterator tmp(_it);//反向迭代器的rbegin()是正向迭代器end()的--;//有效元素//反向迭代器的rend()是正向迭代器begin()的--;//表示哨兵位位置return *(--tmp);}Ptr operator->(){return  &(operator*());//是访问的地址}bool operator!=(const Self& it){return _it != it._it;//自定义类型比较所以参数需要成员函数}};

测试代码:

void func(const mylist::list<int>& lt)
{mylist::list<int>::const_reverse_iterator it = lt.rbegin();while (it != lt.rend()){cout << *it << ' ';++it;}cout << endl;
}
void test_mylist11()
{mylist::list<int> lt1 = { 1,2,3,4,5 };func(lt1);
}

结果:

三、所有代码:

#pragma once
#include <iostream>
using namespace std;
#include <list>
#include <algorithm>
#include <assert.h>
namespace mylist
{template<class T>struct ListNode{ListNode<T>* _prev;ListNode<T>* _next;T val;ListNode(const T& data = T()):_prev(nullptr), _next(nullptr), val(data) {}};template<class T,class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T,Ref,Ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}Ref operator*(){return _node->val;}Ptr operator->(){return &_node->val;}bool operator!=(const Self& it){return _node != it._node;//内置类型比较}};//template<class T>//class ConstListIterator//{//	typedef ListNode<T> Node;//	typedef ConstListIterator<T> Self;//	Node* _node;//public://	ConstListIterator(Node* node)//		:_node(node)//	{}//	Self& operator++()//	{//		_node = _node->_next;//		return *this;//	}//	Self operator++(int)//	{//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	Self& operator--()//	{//		_node = _node->_prev;//		return *this;//	}//	Self operator--(int)//	{//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	const T& operator*()//	{//		return _node->val;//	}//	const T* operator->()//	{//		return &_node->val;//	}//	bool operator!=(const Self& it)//	{//		return _node != it._node;//	}//};// 适配器 -- 复用template<class Iterator, class Ref, class Ptr>struct Reverse_iterator{typedef Reverse_iterator< Iterator, Ref, Ptr> Self;Iterator _it;Reverse_iterator(const Iterator& it):_it(it){}//Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp(*this);_it--;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp(*this);_it++;return tmp;}Ref operator*(){Iterator tmp(_it);//反向迭代器的rbegin()是正向迭代器end()的--;//有效元素//反向迭代器的rend()是正向迭代器begin()的--;//表示哨兵位位置return *(--tmp);}Ptr operator->(){return  &(operator*());//是访问的地址}bool operator!=(const Self& it){return _it != it._it;//自定义类型比较所以参数需要成员函数}};// vector和list反向迭代器实现template<class T>class list{public:typedef ListNode<T> Node;//普通迭代器typedef ListIterator<T,T&,T*> iterator;//typedef ConstListIterator<T> const_iterator;typedef ListIterator<T,const T&, const T*> const_iterator;iterator begin(){return iterator(_head->_next);//匿名对象}const_iterator begin()const{return const_iterator(_head->_next);//匿名对象}iterator end(){return iterator(_head);//末尾就是哨兵位}const_iterator end() const{return const_iterator(_head);//末尾就是哨兵位}//反向迭代器typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin(){return reverse_iterator(end());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}public:void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){ empty_init();}list(const list<T>& lt){empty_init();for (const auto& e : lt){push_back(e);}}list<T>& operator=(list<T> lt){std::swap(_head, lt._head);return *this;}list(initializer_list<T> il){empty_init();for (const auto& e : il){push_back(e);}}~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}void push_back(const T& x){创建新节点然后初始化//Node* newnode = new Node(x);找尾//Node* ptail = _head->_prev;改变新节点指向//newnode->_next = _head;//newnode->_prev = ptail;改变head和ptail指向//ptail->_next = newnode;//_head->_prev = newnode;insert(end(), x);}void pop_back(){insert(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& x){//创建新的节点Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = cur->_prev;//改变前驱节点的指向//prev newnode curprev->_next = newnode;cur->_prev = newnode;return iterator(newnode);}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//prev cur nextprev->_next = next;next->_prev = prev;delete cur;//删除后迭代器失效,因为pos指向的节点已经被释放//需要返回值来获取下一个节点的元素。return iterator(next);}private:Node* _head;};
}

如果你有所收获,可以留下你的点赞和关注。谢谢你的观看!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/21044.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【气象常用】剖面图

效果图&#xff1a; 主要步骤&#xff1a; 1. 数据准备&#xff1a;我用的era5的散度数据&#xff08;大家替换为自己的就好啦&#xff0c;era5数据下载方法可以看这里【数据下载】ERA5 各高度层月平均数据下载_era5月平均数据-CSDN博客&#xff09; 2. 数据处理&#xff1a…

windows10系统64位安装delphiXE11.2完整教程

windows10系统64位安装delphiXE11.2完整教程 https://altd.embarcadero.com/download/radstudio/11.0/radstudio_11_106491a.iso XE11.1 https://altd.embarcadero.com/download/radstudio/11.0/RADStudio_11_2_10937a.iso XE11.2 关键使用文件在以下内容&#xff1a;windows10…

Java Spring Boot 从必应爬取图片

获取图片主要就是通过必应图片页面控制台的元素&#xff0c;确认图片和标题在哪个类中&#xff08;浏览器 F12&#xff09; 引入依赖 这里需要引入两个依赖 jsoup 和 hutool maven依赖网站地址&#xff1a;Maven Repository: Search/Browse/Explore (mvnrepository.com) 挑选…

极简网络用户手册(1)

极简网络系统处理流程 模块位置&#xff1a;参数平台--专题分析--极简网络分析 步骤&#xff1a; 步骤一&#xff1a;创建精细化场景策略 步骤二&#xff1a;创建任务&#xff0c;主要选择策略&#xff08;包括√配置和距离配置&#xff09;和需要处理的小区清单&#xff08;源…

FS212E 系列PD协议

PD快充协议芯片FS212EL、FS212EH可以智能的识别插入的手机类型&#xff0c;选择最为合适的协议应对手机快充需要。兼容多类USB Type-C协议&#xff0c;包括TypeC协议、TypeC PD2.0、TypeC PD3.0、TypeC PD3.2等协议。集成OPTO输出&#xff0c;通过电阻直驱反馈光耦。FS212E 的调…

PlantUML-使用文本来画时序图

介绍 PlantUML 是一个开源工具&#xff0c;用户可以使用纯文本描述来创建 UML (统一建模语言) 图形。由于它使用文本来描述图形&#xff0c;因此可以很容易地将这些描述与源代码一起存储在版本控制系统中。然后&#xff0c;PlantUML 负责将这些描述转换为图形。 资料 官方文…

杂牌记录仪TS视频流恢复方法

大多数的记录仪都采用了MP4/MOV文件方案&#xff0c;极少数的可能在用AVI文件&#xff0c;极极少数的在用TS文件方案。很多人可能不太解TS文件&#xff0c;这是一种古老的视频文件结构&#xff0c;下边这个案例我们来看下TS视频文件的恢复方法。 故障存储:8G存储卡/fat32文件系…

2-1RT-Thread线程管理-笔记

2-1RT-Thread线程管理-笔记 其中系统线程由内核创建&#xff0c;如main函数和空闲线程都属于系统线程&#xff0c;而用户线程是由应用程序所创建的。 对于资源较大的MCU可以适当设计较大的线程栈&#xff0c;也可以在初始化时设置一个具体的数值&#xff0c;如1K或2K字节。…

索引 ---- mysql

目录 1. 索引 1.1 概念 1.2 作用 1.3 使用场景 1.4 使用 1.4.1查看索引 1.4.2 创建索引 1.4.3 删除索引 1.5 注意事项 1.6 索引底层的数据结构 (面试经典问题) 1. 索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。可以对表中的…

11.3 指针和函数

11.3 指针和函数 本节必须掌握的知识点&#xff1a; 指针作为函数的参数 数组作为函数的参数 指针作为函数的返回值 在C语言中&#xff0c;指针的一个重要作用就是作为函数参数使用&#xff0c;本节将介绍这一重要作用。 11.3.1 指针作为函数的参数 实验一百一十三&#xff…

电磁兼容(EMC):BUCK变换器基本原理及传导辐射分析设计

目录 1. BUCK电路拓扑及原理 2. Buck拓扑电路电磁场分析 3.总结 开关电源替代线性电源&#xff0c;解决了效率和体积问题&#xff0c;但也带来了新的EMI问题。开关电源也是产品内部的强辐射源之一&#xff0c;基于透过现象看本质&#xff0c;将复杂问题简单化&#xff0c;本…

T检验——单样本t检验/两独立样本t检验/配对样本t检验

T检验——单样本t检验/两独立样本t检验/配对样本t检验 1.单样本t检验1.1 适用范围 2. &#xff08; 独立样本t检验&#xff09;两独立样本t检验3.ANOVA多组样本显著性检验&#xff08;2组以上&#xff09;4. 配对样本T检验 1.单样本t检验 1.1 适用范围 单样本t检验:即已知样本…

成功解决“IndexError: deque index out of range”错误的全面指南

成功解决“IndexError: deque index out collections.deque out of range”错误的全面指南 引言 在Python编程中&#xff0c;collections.deque 是一个双端队列&#xff08;double-ended queue&#xff09;&#xff0c;支持从两端快速添加和删除元素。然而&#xff0c;与列表&…

Linux网络-守护进程版字典翻译服务器

文章目录 前言一、pid_t setsid(void);二、守护进程翻译字典服务器&#xff08;守护线程版&#xff09;效果图 前言 根据上章所讲的后台进程组和session会话&#xff0c;我们知道如果可以将一个进程放入一个独立的session&#xff0c;可以一定程度上守护该进程。 一、pid_t se…

mysql(数据库)可视化工具——Navicat Premium

Navicat Premium是一款功能强大的数据库管理工具&#xff0c;它支持多种数据库管理系统&#xff0c;包括MySQL、MariaDB、SQL Server、SQLite、Oracle和PostgreSQL等。Navicat Premium提供了直观的用户界面&#xff0c;使用户能够轻松地管理数据库结构、执行复杂的SQL查询、导入…

CUDA Unity Compute Shader 3

计划 这应该是第3章的读书笔记&#xff0c;但是因为第3章读起来比较困难&#xff0c;所以先看了《CUDA并行程序设计编程指南》的第5章和第6章&#xff0c;感觉读起来顺畅多了&#xff0c;《CUDA并行程序设计编程指南》暂定精读第5、6、7章 1.如何生成ptx文件 属性->CUDA C/…

[QT] MAC使用Qt Creator运行程序如何仅运行一个进程?

大家刚开始使用QtCreator会发现每次run程序&#xff0c;都会出现一个程序进程&#xff0c;使得调试操作增加。如下&#xff0c;每次run都会出现一个demo14的进程。 如何每次run后&#xff0c;就关闭上一次的进程&#xff0c;而重新拉起新进程呢&#xff1f; 看这里 这是默认…

Redis-02

redis安装包位置 /opt/redis-7.2.5 redis默认安装路径&#xff1a; 配置文件路径&#xff1a;/usr/local/bin/redisconfig gcc安装位置 /opt/rhredis启动&#xff1a; 在/usr/local/bin目录下输入redis-server redisconfig/redis.confredis性能测试命令 redis-benchmark [opt…

【WEEK14】 【DAY5】Swagger第三部分【中文版】

2024.5.31 Friday 接上文【WEEK14】 【DAY4】Swagger第二部分【中文版】 目录 16.6.配置API分组16.6.1.修改SwaggerConfig.java16.6.2.重启 16.7.实体配置16.7.1.新建pojo文件夹16.7.2.修改HelloController.java16.7.3.重启 16.8.常用注解16.8.1.Swagger的所有注解定义在io.swa…

NIUSHOP多商户V6版预售背后的前端技术革新

随着电子商务的快速发展&#xff0c;多商户电商平台成为了市场上的热门选择。在这个背景下&#xff0c;NIUSHOP多商户V6版的预售活动引发了广泛关注。本文将从前端技术的角度&#xff0c;探讨NIUSHOP多商户V6版在预售背后所蕴含的技术革新和亮点。 一、引言 NIUSHOP多商户系统…