【落羽的落羽 C++】list及其模拟实现

文章目录

一、list介绍

list是我们之前学过的带头双向链表的类模板,具有链表的一系列性质,也有多种多样的接口便于使用,使用方法与vector大体相似:

函数 接口说明
list() 构造空的list,只有头结点,头结点的前后指针指向自己
begin 返回第一个元素(即头结点的下一个)的迭代器
end 返回最后一个元素下一个位置(即头结点)的迭代器
empty 判断list是否为空,是返回true,否则返回false
front 返回第一个节点中值的引用
back 返回最后一个节点中值的引用
push_front 头插
push_back 尾插
pop_front 头删
pop_back 尾删
insert 插入
erase 删除

除此之外,list还有几个特殊的接口:

  • unique,删除重复值
    这个接口能删除list中的重复值,但前提是这个list中数据必须是有序的,如果不是有序的则结果会出错。所以一般都要在调用算法库中的sort排序后再用unique。
  • remove,移除指定元素

    很好理解
  • splice,移动

    splice既可以用于不同list间的数据转移,也可以用于一个list中的数据调整位置:

二、list模拟实现

list是一个类模板,那么我们要模拟实现它,显然要先实现出list结点的结构,再实现list类。

除此之外,list的迭代器更加特殊,不像string、vector,由于它们的底层是数组,在内存空间中是连续的,所以它们的迭代器可以用指针直接实现,它们的迭代器可以使用指针的一系列运算符如++、+、*、<等。而list的底层在内存中是不连续的,而且每一个元素都存在各自独立的结点中,如果直接用指针做迭代器,指针的那些操作符就是不合法的。所以我们不能直接用指针做迭代器,但是又想让迭代器有指针的效果便于使用,解决方法就是,用类来封装实现迭代器

1. 节点

在使用list时,用户一般都不会直接接触到它的节点,所以节点的结构没有必要用访问限定符修饰了,直接用struct实现就行。当然,它还是一个模板,因为存储数据类型会多种多样。

cpp 复制代码
template<class T>
struct list_node
{
	list_node* _next;
	list_node* _prev;
	T _data;
	
	list_node(const T& x = T())
		: _next(nullptr)
		, _prev(nullptr)
		, _data(x)
	{ }
};

2. 迭代器

用类封装迭代器的目的是,能重载相关的运算符,便于使用

cpp 复制代码
template<class T>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T> Self; //将迭代器暂且命名为Self便于类内使用
	Node* _node; //这个迭代器指向的节点

	list_iterator(Node* node)
		:_node(node)
	{ }

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

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

	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}
};

这是普通的iterator,但list的迭代器有普通的iterator和const_iterator,前者可以修改引用的内容,后者不可以修改引用的内容。在具体实现上它们区别之一是重载*时,iterator中是T& operator*(),const_iterator中是const T& operator*(),这样解引用const_iterator出的结果就无法修改:

cpp 复制代码
template<class T>
struct list_const_iterator
{
	typedef list_node<T> Node;
	typedef list_const_iterator<T> Self; //将迭代器暂且命名为Self便于类内使用
	Node* _node; //这个迭代器指向的节点

	list_const_iterator(Node* node)
		:_node(node)
	{ }

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

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

	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}
};

虽说可以这样写,但是不觉得代码太冗余了吗?有人已经能察觉到了,这里关于iterator和const_iterator,完全可以利用模板写在一起:我们在模板类型中增加一个Ref,代表这个迭代器是普通或是const版本,然后在重载*处写成Ref operator*(),其余出不用修改。这样,迭代器如果是const版本的,给Ref传const T&类型,是普通版本的则传T&类型,巧妙完成了这个问题:

cpp 复制代码
template<class T, class Ref>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T, Ref> Self; //将迭代器暂且命名为Self便于类内使用
	Node* _node; //这个迭代器指向的节点

	list_iterator(Node* node)
		:_node(node)
	{
	}

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

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

	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}

};

然后,又有一个新的问题:

如果list存的数据是自定义类型,此时我们也会想利用->操作符用迭代器访问到自定义结构中的成员:

cpp 复制代码
struct A
{
	int a1;
	char a2;
};

list<A> lt;
lt.push_back({1, 'a'});
list<A>::iterator it = lt.begin();
cout << it->a1 << it->a2 << endl;

所以,迭代器中也要重载->运算符。但同样的,const版本迭代器不能对指向内容进行修改,还是和上面一样,区分const版本和普通版本迭代器最好用模板来解决,给iterator的模板类型中增加第三个类型Ptr,这样,迭代器如果是const版本的,给Ptr传const T*类型,是普通版本的则传T*类型:

tip:一定不能给const版本迭代器传成T& const或T* const类型,因为迭代器本身一定能改变引用或指向的目标,是能修改的。const T&和const T*才是不能修改引用或指向内容的。

cpp 复制代码
template<class T, class Ref, class Ptr>
struct list_iterator
{
	Ptr operator->()
	{
		return &_node->_data;
	}
	//......
};

此时再返回来看上面的例子:

cpp 复制代码
struct A
{
	int a1;
	char a2;
};

list<A> lt;
lt.push_back({1, 'a'});
list<A>::iterator it = lt.begin();
cout << it->a1 << it->a2 << endl;

却感觉怪怪的,it指向的是一个A类型元素,a1和a2是这个A类型元素的成员,理论上不应该用两个->操作符才能访问到吗。这就是一个特殊处理,写两个->很冗余,于是就规定就这样写一个->,理解就好。不过,如果显式写出运算符重载,就应该写成两个->了:如it.operator->()->a1

综上所述,我们的list的迭代器的最终实现是:

cpp 复制代码
template<class T, class Ref, class Ptr>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T, Ref, Ptr> Self; //将迭代器暂且命名为Self便于类内使用
	Node* _node; //这个迭代器指向的节点

	list_iterator(Node* node)
		:_node(node)
	{
	}

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

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

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

	bool operator!=(const Self& s) const
	{
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}
};

接下来,在下面的list本体中,只需
typedef list_iterator<T, T&, T*> iterator;(普通迭代器)
typedef list_iterator<T, const T&, const T*> const_iterator;(const迭代器)。

3. list

最后需要我们实现的,就剩下list的常用接口了:

cpp 复制代码
	template<class T>
	class list
	{
		typedef list_node<T> Node;

	private:
		Node* _head;
		size_t _size;

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

		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

		//利用{}进行构造
		list(initializer_list<T> il)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto& e : il)
			{
				push_back(e);
			}
		}

		list(list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

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

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

		size_t size()
		{
			return _size;
		}

		//在pos位置前插入一个新结点
		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			
			Node* newnode = new Node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			_size++;
		}

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

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

		//删除pos位置的结点
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			prev->_next = next;
			next->_prev = prev;

			delete cur;
			_size--;

			return next;
			//实际应该是return iterator(next),不过编译器会隐式类型转换
		}

		void pop_back()
		{
			erase(--end());
		}

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

最后,以下是list的完整模拟实现,放在我们自己的list.h中:

cpp 复制代码
#pragma once
#include<assert.h>
#include<iostream>
using namespace std;

namespace lydly
{
	template<class T>
	struct list_node
	{
		list_node* _next;
		list_node* _prev;
		T _data;

		list_node(const T& x = T())
			: _next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{ }
	};

	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> Self; 
		Node* _node; 

		list_iterator(Node* node)
			:_node(node)
		{ }

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

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

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

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};


	template<class T>
	class list
	{
		typedef list_node<T> Node;

	private:
		Node* _head;
		size_t _size;

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

		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

		//利用{}进行构造
		list(initializer_list<T> il)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto& e : il)
			{
				push_back(e);
			}
		}

		list(list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;

			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

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

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

		size_t size()
		{
			return _size;
		}

		//在pos位置前插入一个新结点
		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			
			Node* newnode = new Node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			_size++;
		}

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

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

		//删除pos位置的结点
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			prev->_next = next;
			next->_prev = prev;

			delete cur;
			_size--;

			return next;
			//实际应该是return iterator(next),不过编译器会隐式类型转换
		}

		void pop_back()
		{
			erase(--end());
		}

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

本篇完,感谢阅读

相关推荐
強云几秒前
内存池(C++)
c++
元亓亓亓33 分钟前
Java后端开发day42--IO流(二)--字符集&字符流
java·开发语言
JANYI20181 小时前
在c++中老是碰到string&,这是什么意思?
开发语言·c++
passionSnail2 小时前
《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用声音合成玩音乐:MATLAB电子琴制作(超级趣味实践版)
开发语言·matlab
锦夏挽秋2 小时前
Qt 信号槽机制底层原理学习
c++·qt
shenyan~2 小时前
关于Python:9. 深入理解Python运行机制
开发语言·python
天堂的恶魔9462 小时前
C++ - 仿 RabbitMQ 实现消息队列(1)(环境搭建)
开发语言·c++·rabbitmq
殇淋狱陌3 小时前
【Python】常用命令提示符
开发语言·python·虚拟环境
anqi273 小时前
在sheel中运行Spark
大数据·开发语言·分布式·后端·spark
VB.Net4 小时前
C# 综合示例 库存管理系统20 操作员管理(FormAdmin)
开发语言·数据库·c#