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 cur
		prev->_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 next
		prev->_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 cur
			prev->_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 next
			prev->_next = next;
			next->_prev = prev;
			delete cur;
			//删除后迭代器失效,因为pos指向的节点已经被释放
			//需要返回值来获取下一个节点的元素。
			return iterator(next);
		}
	private:
		Node* _head;
		};
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nginx源码编译安装

Nginx NginxNginx的特点Nginx的使用场景Nginx 有哪些进程 使用源码编译安装Nginx准备工作安装依赖包编译安装Nginx检查、启动、重启、停止 nginx服务配置 Nginx 系统服务方法一&#xff1a;方法二&#xff1a; 访问Nginx页面 升级Nginx准备工作编译安装新版本Nginx验证 Nginx N…

顶底背离的终极猜想和运用

这几天圈内都在传底蓓离什么的。作为严肃的量化自媒体&#xff0c;我们就不跟着吃这波瓜了。不过&#xff0c;我一直很关注技术指标的顶背离和底背离&#xff0c;一直在追问它的成因如何&#xff0c;以及如何预测。 底蓓离把我目光再次吸引到这个领域来&#xff0c;于是突然有…

Kubernetes-使用集群CA证书给用户颁发客户端证书访问Api-Server

一、官网地址 证书和证书签名请求 | Kubernetes 二、Demo 一、创建测试文件夹 cd ~ mkdir add_k8s_user_demo cd add_k8s_user_demo 二、创建符合X509标准的证书 openssl genrsa -out myuser.key 2048 openssl req -new -key myuser.key -out myuser.csr -subj "/CNmy…

【30天精通Prometheus:一站式监控实战指南】第14天:jmx_exporter从入门到实战:安装、配置详解与生产环境搭建指南,超详细

亲爱的读者们&#x1f44b;   欢迎加入【30天精通Prometheus】专栏&#xff01;&#x1f4da; 在这里&#xff0c;我们将探索Prometheus的强大功能&#xff0c;并将其应用于实际监控中。这个专栏都将为你提供宝贵的实战经验。&#x1f680;   Prometheus是云原生和DevOps的…

mybatis增删改查模板设置及设置调用

mybatis增删改查模板设置 系统配置文件完成以及连接好数据之后&#xff0c;就可以用这个mybatis了&#xff0c;首先写这个数据库的增删改查模板StashMapper.xml&#xff0c;这个东西是要放在DAO层中的奥&#xff0c;切记。 1.编写mybatis对应数据库的增删改查模板 在我的Sta…

[Qt学习笔记]Qtxlsx在Qt下的配置和调用

背景分析 Qt操作Excel文件一般有QAxObject和QtXlsx两种方法&#xff0c;前者需要调用wps或office组件进行读写操作&#xff0c;具有一定的局限性&#xff0c;下面列出两种方法的优缺点对比 QAxObject&#xff1a; 优点&#xff1a;支持xls和xlsx等版本。office组件读写速度快&…

面试题:useEffect的Clean Up 什么时候触发?

​ useEffect作为做常用的Hook&#xff0c;以下三个知识点你有必要了解下~ 防止写出奇怪的代码祸害队友&#xff0c;而我不幸就是这个受害者&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; useEffect的依赖项为空 useEffect的dependencyList作为一个可选参数…

LLaMA-Factory推理实践

运行成功的记录 平台&#xff1a;带有GPU的服务器 运行的命令 git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory/ conda create -n py310 python3.10 conda activate py310由于服务器不能直接从huggingface上下载Qwen1.5-0.5B&#xff0c;但本地可…

轻松拿捏C语言——【文件操作】

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f389;创作不易&#xff0c;请多多支持&#x1f389; &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正 目录 &#x1f…

Python高阶学习记录

文章导读 阅读本文需要一定的python基础&#xff0c;部分知识点是对python入门篇学习记录和python并发编程学习记录的深入探究&#xff0c;本文记录的Python知识点包括函数式编程&#xff0c;装饰器&#xff0c;生成器&#xff0c;迭代器&#xff0c;正则表达式&#xff0c;内存…

HTML蓝色爱心

目录 写在前面 HTML入门 完整代码 代码分析 运行结果 系列推荐 写在后面 写在前面 最近好冷吖&#xff0c;小编给大家准备了一个超级炫酷的爱心&#xff0c;一起来看看吧&#xff01; HTML入门 HTML全称为HyperText Markup Language&#xff0c;是一种标记语言&#…

最小时间差

首先可以想到&#xff0c;可以计算出任意两个时间之间的差值&#xff0c;然后比较出最小的&#xff0c;不过这种蛮力方法时间复杂度是O(n^2)。而先将时间列表排序&#xff0c;再计算相邻两个时间的差值&#xff0c;就只需要计算n个差值&#xff0c;而排序阶段时间复杂度通常为O…

Docker成功启动Rabbitmq却访问不了管理页面问题解决

目录 启动步骤&#xff1a; 无法访问问题总结&#xff1a; 启动步骤&#xff1a; 拉取镜像&#xff1a; docker pull rabbitmq 运行&#xff1a; docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq进入容器&#xff1a; docker exec -it 容器id /bin/…

C++ C (1152) : 循环赛日程表

文章目录 一、题目描述二、参考代码 一、题目描述 二、参考代码 #include<iostream> #include<vector> #include<cstdlib> using namespace std;void generateSchedule(vector< vector<int> >& table, int numPlayers, int rounds) {// 生…

模拟通讯录(详解通讯录排序qsort,strcmp)

前言&#xff1a; 学习了C语言结构体、联合体、枚举等&#xff0c;就可以写一个通讯录来强化自己对结构体的理解学习。顺便提升大家的基本功&#xff01;&#xff01; 通讯录菜单的打印&#xff1a; 关于菜单的打印在之前写游戏的时候写过多次&#xff0c;大家可以参照之前的改…

2024后端服务架构升级

文章目录 原因改造方案新架构图技术选型思考 服务拆分公共组件设计自部署算法服务排期计划 全球多活改造 原因 背景&#xff1a; 1、xx业务经过多轮的业务决策和调整&#xff0c;存在非常多技术包袱&#xff0c;带了不好的用户体验和极高的维护成本 2、多套机房部署&#xf…

大创项目推荐 深度学习的口罩佩戴检测 - opencv 卷积神经网络 机器视觉 深度学习

文章目录 0 简介1 课题背景&#x1f6a9; 2 口罩佩戴算法实现2.1 YOLO 模型概览2.2 YOLOv32.3 YOLO 口罩佩戴检测实现数据集 2.4 实现代码2.5 检测效果 3 口罩佩戴检测算法评价指标3.1 准确率&#xff08;Accuracy&#xff09;3.2 精确率(Precision)和召回率(Recall)3.3 平均精…

数据仓库核心:维度表设计的艺术与实践

文章目录 1. 引言1.1基本概念1.2 维度表定义 2. 设计方法2.1 选择或新建维度2.2 确定维度主维表2.3 确定相关维表2.14 确定维度属性 3. 维度的层次结构3.1 举个例子3.2 什么是数据钻取&#xff1f;3.3 常见的维度层次结构 4. 高级维度策略4.1 维度整合维度整合&#xff1a;构建…

c++程序员为什么要做自己的底层库

五一期间&#xff0c;在家里翻到之前上学时候用的电脑和工作日志&#xff0c;粗略浏览一番&#xff0c;感慨10年岁月蹉跎&#xff0c;仍然没有找到自己技术方向的“道”。遂有感而发&#xff0c;写下此文。 算起来&#xff0c;接触软件开发也有10年时间了&#xff0c;最开始是…

06C内存分配

C零碎语法 目录 文章目录 C零碎语法1.内存布局2. 内存对齐2.1结构体内存对齐2.1应用 1.内存布局 2. 内存对齐 2.1结构体内存对齐 三条原则&#xff1a; &#xff08;1&#xff09;结构体变量的 起始地址能够被其最宽的成员大小整除。 &#xff08;2&#xff09;结构体每个…