万字带你体验C++泛型之美——list的简单上手和模拟实现

list的简单上手和模拟实现

😊本文为小邢原创,CSDN首发
📅发布时间:2022/4/29
🙌欢迎大家👍点赞❤收藏✨加关注
✒本文大约19000字左右
🙏笔者水平有限,如有错误,还望告诉笔者,万分感谢!
🚩有什么问题也可在评论区一起交流哦!

朋友们大家好,真的是我,我改名了😂
前久博主太忙了,就一直没更新,越不更新就越不想更新,这怎么行呢?还是得坚持,下面来看看博主这一篇博文吧!

一.list简单介绍

以下截自于cplusplus.com - The C++ Resources Network中对list的简单介绍:

里面大致的意思就是list是一个顺序容器,支持在任意位置O(1)的插入删除;实质是一个双向带头循环链表。

二.list的简单上手

1、构造一个list对象

·构造一个空对象
list<int> one;
·用元素来构造

比如,我们用10个1来构造一个list

list<int> two(10, 1);
·用一段迭代器区间构造
list<int> three(two.begin(), two.end());
·用一个list对象来构造
list<int> four(two);

2、push_back、push_front和迭代器

说明: const对象要使用const迭代器,该迭代器不支持修改元素值。

int main()
{
    list<int> one;
   
   //尾插
    one.push_back(1);
    one.push_back(2);
   
   //头插
	one.push_front(3);
	one.push_front(4);
   
   //正向const迭代器
	list<int>::const_iterator cit = one.begin();
	while (cit != one.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;

   //反向const迭代器
	list<int>::const_reverse_iterator crit = one.rbegin();
	while (crit != one.rend())
	{
		cout << *crit << " ";
		crit++;
	}
	cout << endl;

   //正向迭代器
	list<int>::iterator it = one.begin();
	while (it != one.end())
	{
		cout << *it+1 << " ";
		it++;
	}
	cout << endl;

   //反向迭代器
	list<int>::reverse_iterator rit = one.rbegin();
	while (rit != one.rend())
	{
		cout << *rit-1 << " ";
		rit++;
	}
	cout << endl;
}

在这里插入图片描述

3.pop_front、pop_back

头删、尾删~~

int main()
{
	list<int> one;
   one.push_back(1);
	one.push_back(2);
	one.push_back(3);
	one.push_back(4);
	list<int>::const_iterator cit = one.begin();
	while (cit != one.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;
   
   one.pop_back();
	one.pop_front();

	cit = one.begin();
	while (cit != one.end())
	{
		cout << *cit << " ";
		cit++;
	}
	cout << endl;
}

在这里插入图片描述

4、assign

该函数的功能是重置list中的内容,也就是用新的内容替换之前list中的内容,替换后list的大小可能会改变。

int main()
{
   list<int> one;
	one.push_back(1);
	one.push_back(2);
	one.push_back(3);
	one.push_back(4);
	for (auto e : one)
	{
		cout << e << " ";
	}
	cout << endl;
   
   one.assign(6, 1);
	for (auto e : one)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述
同样,assign也支持用一段迭代器区间来替换list中的旧内容,博主在这就不予演示了,大家可自行尝试。

5、reverse

逆置元素~~

int main()
{
	list<int> one;
   one.push_back(1);
	one.push_back(2);
	one.push_back(3);
	one.push_back(4);
	for (auto e : one)
	{
		cout << e << " ";
	}
	cout << endl;
   cout << "After reverse:" << endl;
	one.reverse();
	for (auto e : one)
	{
		cout << e << " ";
	}
	cout << endl;

}

在这里插入图片描述

6、sort

排升序~~

int main()
{
	list<int> first;
	for (size_t i = 0; i < 10; i++)
	{
		int random = rand() % 10;
		first.push_back(random);
		cout << random << " ";
	}
	cout << endl;
	first.sort();
	cout << "After sort:" << endl;
	for (auto e : first)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

以上仅为list的一些基本接口,更多的接口大家可以去网站cplusplus.com - The C++ Resources Network中了解,博主仅在此做基本介绍,对于list的大量细节会在list的模拟实现中介绍。

三.list模拟实现

[声明:模拟实现参考SGI版本]

1、基本结构

为了区分库里的list,我们在List的命名空间里模拟list。list的成员变量是一个节点类型的指向头节点的指针。对于节点类型,他最开始是一个类模板,因为还不知道节点存放元素的类型,当节点实例化后,它里面有一个数值数据和两个指针。

namespace List
{
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;
	};

	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		//成员函数
	private:
		Node* _head;
	};
}

对于list的模拟实现来说,与string和list最不同的就是list的迭代器,为了模拟迭代器,我们先把list的构造函数和push_back模拟实现了,其他的接口放在迭代器之后实现。

2、构造函数 and push_back

这里我们先实现默认的构造函数。

list()
{
		_head = new Node();//这里的Node会调用默认的构造函数。
		_head->_next = _head;
		_head->_prev = _head;
}
void push_back(const T& x)
{
		Node* tail = _head->_prev;
		Node* newnode = new Node(x);
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
}

在这里插入图片描述

这里需要注意的是,如果我们直接运行这段代码会报一个错误,原因是我们没有显式书写Node的构造函数,在new Node(x)这里会出错。

template<class T>
struct ListNode
{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;

		ListNode(const T& val = T())
			:_next(nullptr), _prev(nullptr), _val(val)
		{}
};

3、迭代器1.0

思考:对与一个迭代器,其最重要的是能支持遍历,在遍历的过程中,要能支持解引用(*)和 ++两个基本运算符操作。而我们知道,list在物理空间上是不连续的,故不能直接对迭代器++,那有什么办法能帮助我们解决该问题呢?

我们知道,对于链表来说,要访问前一个节点或是下一个节点,无非是得到前一个节点或是下一个节点的地址:

cur=cur->next;
cur=cur->prev;

回顾之前所学的知识,我们想到了可以用运算符重载来实现我们的需求,我们可以重载++,将上面访问节点的操作封装到运算符重载函数里去,当我们使用的时候,就真的好像在使用一段连续的空间似的。不光有++操作,还有像 *等其他的操作,为了更好的维护迭代器的有关操作,我们将这些操作封装到一个类里面去,自此list的迭代器就成为了一个类,而类里面的成员变量,是指向节点的指针,所以我们说,list的迭代器本质还是一个指针,只是我们把一些对指针的操作和该指针一起封装到一个类里面去了,这充分的体现了c++封装的特点。

·成员变量
template<class T>
struct __list_iterator
{
		typedef ListNode<T> Node;
		typedef __list_iterator<T> self;
		Node* _node;
};

为了代码的简洁,我们给一些复杂的类型名起了别名。

·构造函数
__list_iterator(Node* pnode)//注意,这里不能加const,因为_node可能不是const指针
		:_node(pnode)
	{}
· operator*

这里我们使用引用返回,不仅是因为但我们函数调用完后,空间还在,更重要的是,这里还要支持值的修改。

T& operator*()
{
		return _node->_val;
}
·前置++ and 后置++
//++it
self operator++()
{
		_node = _node->_next;
		return *this;
}

//it++
self operator++(int)//占位参数
{
		self tmp(*this);//默认构造函数
		_node = _node->_next;
		return tmp;
}

//我们可以发现,前置++比后置++开销要小一些。

这里要补充一点,对于list的迭代器,我们用不着显式的实现拷贝构造函数和析构函数,因为迭代器本质的一个指针,当我们用一个已有的迭代器拷贝构造一个迭代器时,是又创建了一个指针指向同一个节点,所以做的是浅拷贝,系统默认的拷贝构造就能满足需要。而对于析构函数,因为迭代器中没有申请额外的空间资源,所以用不着显式的定义析构函数,系统默认的即可。

有朋友会问,list的每一个节点是申请的空间呀,为什么不需要析构函数?举个例子,当我们遍历完list,迭代器用完了,调用析构函数的时候难道我们要把list销毁吗?更形象的说,list的空间归list管,不归迭代器管,list的空间释放归list来管理。

· operator!= and operator==
bool operator!=(const self& it) const
{
		return _node != it._node;
}

到这里,一个简单的正向迭代器就实现了,让我们来测试一下:

namespace List
{
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;

		ListNode(const T& val = T())
			:_next(nullptr), _prev(nullptr), _val(val)
		{}
	};

	template<class T>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T> self;
		Node* _node;

		__list_iterator(Node* pnode)
			:_node(pnode)
		{}

		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;
		}

	};

	template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		typedef __list_iterator<T> iterator;
	
		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		void push_back(const T& x)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}
	private:
		Node* _head;
	};
   
   void test()
   {
      	list<int> lt;
			for (size_t i = 1; i <= 4; i++)
			{
				lt.push_back(i);// 1 2 3 4
			}
			list<int>::iterator it = lt.begin();
			while (it != lt.end())
			{
				cout << *it << " ";
				++it;
			}
			cout << endl;
   } 
}

int main()
{
   	List::test();
}

在这里插入图片描述
成功遍历!

Node* pnode = _head->_next;
iterator it = _head->_next;

*pnode, *it;
++pnode, ++it;

另外,我们以该注意到,对于Node*和迭代器对象来说,他们俩占用的空间大小相同,都是4byte,并且值都相同,但是他们使用运算符的意义和结果是不相同的。

4、迭代器2.0

实现了正向的非const迭代器,接下来我们要实现正向的const迭代器!与刚才实现的非const迭代器相比,const的迭代器唯一的区别就在于operator*()的返回值上,一个是T&,一个是const T&。有的朋友说,这不简单,再写一个返回值为const T&版本的不就行了!

T& operator*()
{
		return _node->_val;
}
//1.
const T& operator*()
{
		return _node->_val;
}
//2.
const T& operator*()const
{
		return _node->_val;
}

如上,一定是一些朋友们的想法,但其实这是有错的。首先对于第一种写法,虽然返回值改成了const T&,但是这与返回值为T&的operator*构成不了运算符重载,因为函数参数相同。

那改为第二种写法呢?虽然参数不同了,可以构成函数重载了,但是还有问题。对于一个list来说,我们可以用iterator遍历,遍历过程中可以修改list的值,我们亦可以使用const_iterator来遍历,遍历过程中不能对list进行修改。但是抱歉的是,对于第二种的写法,const_iterator是可以修改list的值的。原因我们来一张图:

在这里插入图片描述

我们可以看到,两个operator*函数的this指针的类型是不同的。而我们知道,当我们定义一个迭代器时,定义的都是非const的迭代器,要不然我们怎么遍历?所以在我们调用operator *()时,无论const_iterator还是Iterator调用的都是第一个operator *(),因为他参数最匹配。

这样一来,用const_iterator遍历list时,也能修改list的值了。

随机又出现了一个更致命的问题,对于const修饰的list,本应该只能const_iterator来遍历,这下好了,iterator也能遍历了。

在这里插入图片描述

怎么办,这也不行,那也不行,这里究竟要怎么设计才能满足我们的需求呢?

有朋友可能会说,在实现一个const_iterator类不就行了,这确实能实现,但是两段代码就operator*那里不一样,代码冗余,不符合C++的风格。

当我们实在想不通的时候,让我们看看STL源码是怎样做的。上源码!!

在这里插入图片描述

可以看到,源码在实现上对于迭代器类模板有三个模板参数,相信朋友们第一次看到这里和我一样,都是迷糊的!为什么有三个模板参数?当我看到list中的代码时,我恍然大悟!

在这里插入图片描述

[朋友们,你恍然大悟了吗?]

让我们先来解释前两个模板参数:

当我们是iterator时,我们在list里定义类型,传给模板参数的类型是<T, T&>,那迭代器那边在推演类型的时候,Ref就是T&;而当我们是const_iterator是,我们在list里定义类型,传给模板参数的类型是<T, const T&>,那迭代器那边在推演的时候,Ref就是const T&。Ref是什么?就是operator*的返回值呀!这样就很好的解决了问题。不得不说,STL更像是一个艺术品!

明白了这一点后,让我们来想一想第三个模板参数是干啥的?

我们知道,每一个容器都不知是存放内置类型,还可能存放自定义类型。对于迭代器,我们可以看作是一个指针,指针要访问自定义类型里面的元素,我们要用到的运算符是 ->,所以我们需要重载运算符 ->。

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

老问题,如果是const迭代器呢? 所以,就引出了第三个模板参数。

注意:我们这样实现 -> 重载,再用的时候本来应该是 it->->elem,但是这样代码的可读性较差,所以编译器在这里做了优化,省略了一个 -> ,且对于所有的->重载都会做这一优化。

明白了这两点,让我们浅浅的敲一遍代码吧~~ (以下代码添加了几个接口)

template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		__list_iterator(Node* pnode)
			:_node(pnode)
		{}

		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)const
		{
			return _node == it._node;
		}

		bool operator!=(const self& it)const
		{
			return !(*this == it);
		}
	};

template<class T>
	class list
	{
		typedef ListNode<T> Node;
	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);
		}
   }

5、迭代器3.0

实现了正向的迭代器,让我们进一步完善迭代器,实现一下反向的迭代器。对于反向迭代器,与正向迭代器的区别就在于++和–的方向不一样,所以我们可以用复用正向迭代器来实现反向迭代器。

但是,通过阅读源码,我发现源码中,使用了一个一劳永逸的方法——适配器。

什么是适配器呢?也就是,当一个容器的正向迭代器实现后,会自动适配出相应的反向迭代器。下面是适配器的实现:

namespace List
{
	template<class Iterator, class Ref, class Ptr>
	class reverse_iterator
	{
		typedef reverse_iterator<Iterator,Ref,Ptr> self;
	public:
		reverse_iterator(Iterator it)
			:_it(it)
		{}
		self& operator++()
		{
			--_it;
			return *this;
		}
		self operator++(int)
		{
			self tmp(_it);
			--_it;
			return tmp;
		}
		self& operator--()
		{
			++_it;
			return *this;
		}
		self operator--(int)
		{
			self tmp(_it);
			++_it;
			return tmp;
		}
		Ref operator*()
		{
			Iterator prev = _it;
			return *--prev;//--优先级更高
		}

		Ptr operator->()
		{
			return &operator*();
		}

		bool operator==(const self& it)
		{
			return _it == it._it;
		}

		bool operator!=(const self& it)
		{
			return !(_it == it._it);
		}
	private:
		Iterator _it;
	};
}

class list
	{
		typedef ListNode<T> Node;
	public:
      typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
      typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
       
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(end());
		}

		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(begin());
		}
}

这里的实现,与正向迭代器类似,只是这里的成员变量变成了一个迭代器模板,可以推演出传过来的迭代器。

此外,这里反向迭代的rbegin()和rend()与我们所想像的不同:

在这里插入图片描述

这里我们看到,STL可能相与正向迭代器来一个对称之美,所以正向迭代器的开始,就是反向迭代器的结束,正向迭代器的结束就是反向迭代器的开始。所以反向迭代器中的operator*中实现的时候要先 – 在 *。

6、迭代器4.0

上面是我们实现的反向迭代器,但是源代码里面反向迭代器模板参数只有一个迭代器模板。让我们一起来简单学习一下大佬的设计:

在这里插入图片描述

namespace List
{
	template<class Iterator>
	class reverse_iterator
	{
			typedef reverse_iterator<Iterator> self;
			typedef typename Iterator::reference Ref;
			typedef typename Iterator::pointer Ptr;
	public:
			Ref operator*()
			{
				Iterator prev = _it;
				return *--prev;//--优先级更高
			}

			Ptr operator->()
			{
				return &operator*();
			}
	private:
			Iterator _it;
	};
   
   template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef Ref reference;
		typedef Ptr pointer;

		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		Ref operator*()
		{
			return _node->_val;
		}

		Ptr operator->()
		{
			return &_node->_val;
		}
	};

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

        //typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
        //typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef reverse_iterator<const_iterator> const_reverse_iterator;
		typedef reverse_iterator<iterator> reverse_iterator;
   }
}

这里主要做了这几个工作:一是在struct _ _list _iterator中定义了内嵌类型,主要是因为我们要拿到模板参数中的Ref和Ptr,而对于模板参数我们不能直接拿到,所以我们要定义内嵌类型。之后,我们在反向迭代器类模板中,再通过Iterator取出内嵌类型。我们会发现,有一个typename?他是干什么的?

由于该反向迭代器只有一个模板参数,实现的operator*等函数的返回值类型要去Iterator里面取。但是编译会出错,因为Iterator在未实例化之前,是一个类模板,所以取不到里面的内嵌类型。typename的作用就是当编译器编译到这里时,将这个类模板记下来,先跳过,当他实例化之后,再去取里面的内嵌类型。

在这里插入图片描述

但是,这样实现有一个致命的缺陷,就是若该代码用于vector等迭代器是原生指针的容器会出错,因为原生指针里面没有内置类型。

如何解决呢?STL里面用的时迭代器的萃取技术和模板特化技术,这两个技术比较复杂,感兴趣的朋友可以自行研究。

到此,list的迭代器简单的模拟出来了。

7、insert

这里的我们模拟的insert是在一个迭代器位置前面插入一个元素.

void insert(iterator pos, const T& val)
{
		Node* newnode = new Node(val);
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		prev->_next = newnode;
		newnode->_prev = prev;
		cur->_prev = newnode;
		newnode->_next = cur;
}

8、push_back/push_front

对于这两个接口我们可以复用insert来实现。

void push_back(const T& val)
{
		insert(end(), val);
}
void push_front(const T& val)
{
		insert(begin(), val);
}

9、erase

iterator erase(iterator pos)
{
		assert(pos != end());
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;
		delete cur;
		prev->_next = next;
		next->_prev = prev;
		return iterator(next);
}

这里需要说明的是,erase是在一个迭代器位置删除一个元素后,要返回下一位置的迭代器,是因为这里存在迭代器失效的问题。迭代器失效及迭代器所指向的位置不存在或是意义改变了。显然,当我们删除一个节点后,该节点所占用的内存空间被释放,即迭代器所指向的空间失效了,所以我们为了避免迭代器失效,我们在删除节点后,要返回下一节点的迭代器。

10、pop_back/pop_front

同样,我们可以复用erase来实现pop_back和pop_front。

void pop_back()
{
		iterator tmp = end();
		erase(--tmp);
}

void pop_front()
{
		erase(begin());
}

11、clear

该函数的功能是清空list,也就是除了头节点全部删除。该函数也可用erase来实现。

void clear()
{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);//这里要用it来接收,不然迭代器会失效
		}
}

12、析构函数

析构函数就是将所有空间释放,包括头节点。

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

13、拷贝构造传统写法

list(const list<T>& lt)
{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
		for (auto e : lt)
		{
			push_back(e);
		}
}

14、赋值运算符重载传统写法

list<T>& operator=(const list<T>& lt)
{
		if (this != &lt)
		{
			 clear();
			 for (auto e : lt)
			 {
					push_back(e);
			 }
		}
		return *this;
}

15、迭代器区间拷贝构造

template<class InputIterator>//命名暗示可以用任意迭代器访问
list(InputIterator first, InputIterator last)
{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
		while (first != last)
		{
			push_back(*first);
		}
}

这里有一个恶心的问题,就是该函数会和STL里另一个函数产生冲突:

list(size_t n, const T& val = T())
{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
}

比如,我现在要用5个1来构造一个list对象,程序实际上调用的是第一个函数,也就是迭代器区间拷贝构造函数,且会给你报一个错。

list<int> lt(5, 1);

在这里插入图片描述

让我们来比较一下两个函数:

在这里插入图片描述

对于5来说,他是int类型的,当调用的函数是左边的函数时,模板参数会推演为int类型,而右边5传过去后还要隐式类型转换为size_t类型。在编译期看来,左边的函数更匹配,所以调用左边的。

如何解决呢?

我们可以重载一个参数为int的用n个值构造list的构造函数:

list(int n, const T& val = T())
{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
}

由此,我们得出结论,在调用函数时,编译器会去寻找最匹配的函数:有类型转换的,编译器会去调用没有类型转换的;有现成的类型,不去调用需要类型推演的函数。

16、拷贝构造和赋值运算符现代写法

有了迭代其区间的拷贝构造,我们就可以来实现一下有一个对象来拷贝构造的现代写法。

list(const list<T>& lt)
{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
   
		list<T> tmp(lt.begin(), lt.end());//tmp生命周期结束会自动调用析构函数
		std::swap(_head, lt._head);//交换指针
}

list<T>& operator=(list<T> lt)//这里是传值传参,函数调用完lt会自动调用析构函数。
{
		std::swap(_head, lt._head);
}

现代写法相对于传统写法确实要简洁一些,现代写法的核心思想就是“找人帮自己办事”。

其他的接口我们就不一一模拟实现了,我们只是将常用到的函数带大家来模拟实现以下,剩下的函数还有很多,大家可自行去查阅学习。

四.总结

本篇博文主要带大家来上手了list的使用,并把常用的几个接口模拟实现了一遍。其中最精彩的莫过于迭代器的模拟实现了,他充分的体现了C++的泛型编程之美,下望大家能反复体会。另外,对于迭代器的模拟实现与STL源码中的任有一些不同,因为STL源码中还用到了一些较为复杂的技术,比如迭代器萃取或是模板特化技术。由于较为复杂,就没有将其放在本文中介绍,想要挑战自己的朋友们可以自己去了解。还有就是,代码当中仍有一些不足的地方,比如我们的const_iteartor只能遍历const的容器,而STL中的const_iterator不仅可以遍历const容器,也可以遍历非const的容器,这是显而易见的事。所以我们的代码仍有改进优化的空间。

以下是完整的代码:

#pragma once
#include<iostream>
#include<list>
#include<assert.h>
#include"reverse_iterator.h"
using namespace std;

namespace List
{
	void test2();
	void test3();
	void test4();
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;

		ListNode(const T& val = T())
			:_next(nullptr), _prev(nullptr), _val(val)
		{}
	};

	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef Ref reference;
		typedef Ptr pointer;

		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		__list_iterator(Node* pnode)
			:_node(pnode)
		{}

		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)const
		{
			return _node == it._node;
		}

		bool operator!=(const self& it)const
		{
			return !(*this == it);
		}
	};

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

        //typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
        //typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef reverse_iterator<const_iterator> const_reverse_iterator;
		typedef reverse_iterator<iterator> reverse_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);
		}

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(end());
		}

		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(begin());
		}

		list()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}
      
		list(int n, const T& val = T())
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
      
		list(size_t n, const T& val = T())
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
      }

		void insert(iterator pos, const T& val)
		{
			Node* newnode = new Node(val);
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}

		void push_back(const T& val)
		{
			insert(end(), val);
		}

		void push_front(const T& val)
		{
			insert(begin(), val);
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			delete cur;
			prev->_next = next;
			next->_prev = prev;
			return iterator(next);
		}

		void pop_back()
		{
			iterator tmp = end();
			erase(--tmp);
		}

		void pop_front()
		{
			erase(begin());
		}

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

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

      //传统写法
		/*list(const list<T>& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			for (auto e : lt)
			{
				push_back(e);
			}
		}

		list<T>& operator=(const list<T>& lt)
		{
			if (this != &lt)
			{
				clear();
				for (auto e : lt)
				{
					push_back(e);
				}
			}
			return *this;
		}*/

      //现代写法
		list(const list<T>& lt)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			list<T> tmp(lt.begin(), lt.end());
			std::swap(_head, lt._head);
		}

		list<T>& operator=(list<T> lt)
		{
			std::swap(_head, lt._head);
		}
		template<class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
			while (first != last)
			{
				push_back(*first);
			}
		}
	private:
		Node* _head;
	};
}

好啦,如果你认真的读到了这里,还望给UP来个一键三联,你的支持就是UP最大的动力。
【本文为作者原创,未经允许禁止私自转载,抄袭,一经发现,将会受法律责任】

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的小邢~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值