List的底层原理和模拟实现

目录

前言

1、List的使用

1.1 构造和初始化

1.1.1 构造空类

1.1.2 构造List中包含n个值为val的元素

1.1.3 拷贝构造

1.1.4 用first和last区间中构造List

1.2 迭代器

1.2.1 正向迭代器

1.2.2 反向迭代器

1,3 容量操作

1.4 元素访问

1.5 数据修改

2、List的模拟实现

2.1 构造实现

2.2 默认成员函数

2.2.1构造函数

2.2.2拷贝构造

2.2.3赋值重载

2.2.4析构函数

2.3 数据操作实现

2.3.1 push_back()

2.3.2 pop_back()

2.3.3 push_front()

2.3.4 pop_front()

2.4 迭代器实现

2.5 clear清理和析构函数实现

2.5.1 实现clear()

2.5.2实现析构函数

2.6 erase和insert实现头尾数据的删除

2.6.1 实现erase()

2.6.2  实现insert()

2.7 List的最终完全实现

前言

 在我们今日List的学习之前我们再温习一下vector和string叭,我们说List和vector的底层原理都是顺序表,那List呢?通过我们对数据结构中链表的学习,我们大概了解到List中很多知识点都和带头双向循环链表 的知识点契合,今天我们还是和之前一样,先进性使用List然后模拟其底层逻辑来实现List。

1、List的使用

 我们List使用主要包括数据操作、构造、迭代、元素访问。

1.1 构造和初始化

构造函数主要有下面四个。

构造成员函数接口说明                            
List()List的空类
List(size_type n,const value _type  &val=value_type())构造List中包含n个值为val的元素

List(const &list x)

拷贝构造
List(inputitertaror  first,inputitertator last)用first和last区间中构造List

1.1.1 构造空类

	list<int> l1;//构造空类

1.1.2 构造List中包含n个值为val的元素

	list<int> l2(4, 80);//l2中放4个80

1.1.3 拷贝构造

	list<int>l4(l3);//拷贝构造

1.1.4 用first和last区间中构造List

	list<int>l3(l2.begin(), l2.end());//迭代器访问

上面我们可以使用一个范围for来遍历输出一下List。

void print(const list<int>& x)
{
	for (auto& e : x) {
		cout << e << " ";
	}
	cout << endl;
}

调用函数输出结果为 :

1.2 迭代器

此处,大家还是可暂时 将迭代器理解成一个指针,该指针指向 list 中的某个节点,
函数声明接口说明
begin+end
返回第一个元素的迭代器 + 返回最后一个元素下一个位置的迭代器
rbegin+rend
返回第一个元素的 reverse_iterator, end 位置 返回最后一个元素下一个位置的 reverse_iterator, begin 位置

1.2.1 正向迭代器

list<int>::iterator ite = l2.begin();
	while (ite != l2.end())
	{
		cout << *ite << " ";
		++ite;
	}
	cout << endl;

1.2.2 反向迭代器

list<int>::reverse_iterator rite = l2.rbegin();
	while (rite!=l2.rend())
	{
		cout << *rite << " ";
		++rite;
	}
	cout << endl;

1,3 容量操作

容量主要有两个接口size()和capacity()。

int num[5] = { 1,2,3,4,5 };
	list<int> li(num, num + 5);
	list<int> li1;
	if (li.empty())
	{
		cout << "list没有数据" << endl;
	}
	else
	{
		cout << "list有" << li.size() << "个元素" << endl;
	}

	if (li1.empty())
	{
		cout << "list1没有数据" << endl;
	}
	else
	{
		cout << "list1有" << li1.size() << "个元素" << endl;
	}

1.4 元素访问

C++为List提供了两个元素访问的函数接口front()和back()。front表示获取头元素,back表示获取尾元素。

//list li的值为1 2 3  4 5
	cout << li.front() << endl;
	cout <<  li.back() << endl;

 

1.5 数据修改

 List是双向循环链表,对于修改数据非常方便,这里先介绍经常使用的8个接口。

函数声明接口说明
push_back()尾插
pop_back()尾删
push_front头插
pop_front头删
insert插入
erase清楚数据
swap交换数据
clear清空
int nums[] = { 1,2,3,4,5 };
	list<int>l1(nums, nums + 5);
	//头插和尾插
	l1.push_front(7);
	print(l1);
	l1.push_back(6);
	print(l1);
	//删除头部和尾部的节点
	l1.pop_back();
	print(l1);
	l1.pop_front();
	print(l1);
	//插入数据insert
	auto pos = ++l1.begin();
	cout << "pos的值为" << *pos << endl;

	l1.insert(pos, 2);
	print(l1);
	l1.insert(pos, 5,2);
	print(l1);
	vector<int>l{ 1 ,2, 3 };
	l1.insert(pos, l.begin(), l.end());
	print(l1);
	//删除erase
	l1.erase(pos);
	l1.erase(l1.begin(), l1.end());

2、List的模拟实现

既然我们都了解了List的用法,接下来我们来想一下到底是怎么实现的,为什么这样来使用语法,我们去探讨一下底层逻辑,来试着实现一个简单的List吧。                 

2.1 构造实现

List类似与双向循环链表的结构,我们要先创建节点,然后定义List的结构体。接下来我们来实现一下吧,先定一个一个List.h 的头文件。头文件里面有节点和List模版。

template<class T>
	struct __list_node
	{
		__list_node<T>* _next;
		__list_node<T>* _prev;
		T _data;


		__list_node(const T&x=T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};
	template<class T>
	class list
	{
	public:

	private:
		Node* _head;

	};

2.2 默认成员函数

2.2.1构造函数

 这里介绍两种构造函数,一个是默认构造函数,另外一个是迭代器的构造函数,这两种经常使用。 

list()
		{//带头双向循环
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
template <class InputIterator, class OutputIterator>
		list(InputIterator input, OutputIterator output)
		{
			_head = new Node;  // 给头结点开辟一块空间.
			_head->_next = _head;
			_head->_prev = _head;
			while (input != output)
			{
				push_back(*input);
				input++;
			}
		}

2.2.2拷贝构造

 拷贝构造我们使用push_back和迭代器来实现。看着简介方便,这里介绍一下实现细节,具体的实现函数我们会在接下的内容介绍。

//拷贝构造
		list(const list<T>& lt)
		{
			_head = new Node;    // 这里必须先开个头结点空间哦,因为这是拷贝构造,不是赋值
			_head->_next = _head;
			_head->_prev = _head;
			for (const auto& e : lt)
			{
				push_back(e);//这个函数后面会实现
			}
		}

2.2.3赋值重载

	//赋值重载
		list<T>& operator=(const list<T> lt)  
	//注意哦,这里的参数故意没有使用 引用.这样lt就是通过拷贝构造获取了值
		{
			swap(_head, lt._head);   //然后交换两个链表的头结点
		

2.2.4析构函数

 这里先要释放List里面的数据,接下在释放头结点。

	//析构函数
		~list()
		{
			clear();    //一定要先释放头结点后面的数据
			delete _head;
			_head = nullptr;
		}

2.3 数据操作实现

数据操作主要是增删查改,分别是push_back(),pop_back(),push_front(),pop_front()  

我们接下来一一实现这些函数。

2.3.1 push_back()

List尾插,主要分为四个步骤

  • 给数据新建一个结点
  • 保存尾结点
  • 尾结点和新建节点连接
  • 新建节点和头结点连接
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;
		}

使用迭代器遍历输出:

 

2.3.2 pop_back()

list尾删步骤;

  • 保存尾结点的前一个结点
  • 释放尾结点
  • 头结点和保存的结点连接
void pop_back()
		{
			assert(_head != _head->_next);   //如果数据为空,不可删除

			Node * oldtail = _head->_prev;      //提取旧尾巴结点
			Node * newtail = oldtail->_prev;    //保存旧尾巴结点的前一个结点
			delete oldtail;
			oldtail = nullptr;
			_head->_prev = newtail;
			newtail->_next = _head;
		}

 

2.3.3 push_front()

 list头插的步骤:

  • 给新数据新建一个结点
  • 保存头结点下一个结点
  • 头结点和新数据结点连接
  • 新结点和保存结点连接
void push_front(const T& val)
		{
			Node* tmp = new Node(val);     //给新数据新建一个结点
			Node* next = _head->_next;     //保存头结点下一个结点
			_head->_next = tmp;
			tmp->_prev = _head;            //头结点和新数据结点连接
			tmp->_next = next;
			next->_prev = tmp;             //新结点和保存结点连接
		}

 

2.3.4 pop_front()

 List头删的步骤:

  • 保存头结点下两个结点
  • 释放头结点下一个结点
  • 头结点和保存结点连接
void pop_front()
		{
			assert(_head != _head->_next);//如果数据为空,不可删除
			Node * dnext = _head->_next->_next;
			delete _head->_next;
			_head->_next = dnext;
			dnext->_prev = _head;
		}

 

2.4 迭代器实现

这里我们要思考一个问题,我们这里的迭代器是不是还是可以想string和vector一样直接就可以当做指针处理,对数据进行访问呢?我们在这里如果对结点指针进行加减是没有意义的,我们需要的是*()里面的数据,这个时候我们就需要对数据结点指针进行封装,重载运算符,来实现迭代器的功能。

实现迭代器需要的运算符操作如下:

  • ++ 代表走向数据的下一个位置,博主这里只实现前置++
  • - - 代表走向数据的上一个位置,博主这里只实现前置- -
  • * (解引用)代表获取该迭代器位置的元素
  • != 判断两个迭代器的位置是否不一样
  • == 判断两个迭代器的位置是否一样

到这里我们可以实现一个简单的迭代器,用于读取List里面的数据,来实现打印输出。

 

	//迭代器
	template <class T>
	struct __list_iterator
	{
		typedef __list_iterator<T> iterator;
		typedef __list_node<T> Node;
		Node* _node;
		__list_iterator(Node* node)
			
			:_node(node)
		{}
		//*it
		T& operator*()
		{
			return _node->_data;
		}
		//++it
		iterator& operator ++()
		{
			_node = _node->_next;
			return *this;
		}
		//--it
		iterator& operator --()
		{
			_node = _node->_prev;
			return *this;
		}
		//!=
		bool operator!=(const iterator& it) const
		{
			return _node != it._node;
		}
		//==
		bool operator==(const iterator& it) const
		{
			return _node == it._node;
		}
	};

这里的迭代器可以对上面普通的数据操作,但是我们如果想输出Date类,还有不可修改的常迭代器,这个时候对于迭代器的参数要求就会更多,我们来实现一下吧。 

//迭代器
	template <class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef __list_iterator<T, T&, T*> iterator;              //普通迭代器
		//typedef __list_iterator<T> iterator;
		typedef __list_node<T> Node;
		Node* _node;
		__list_iterator(Node* node)
			
			:_node(node)
		{}
		//*it
		/*T& operator*()
		{
			return _node->_data;
		}*/
		//
		Ref operator*() //迭代器使用*的意思是 获取结点值,所以就直接解引用_node,然后返回
		{
			return _node->_data;
		}
		//
		Ptr operator->()
		{
			return &(_node->_data);
		}
		//++it
		iterator& operator ++()
		{
			_node = _node->_next;
			return *this;
		}
		//--it
		iterator& operator --()
		{
			_node = _node->_prev;
			return *this;
		}
		//!=
		bool operator!=(const iterator& it) const
		{
			return _node != it._node;
		}
		//==
		bool operator==(const iterator& it) const
		{
			return _node == it._node;
		}
	};

 我们迭代器的常用接口也实现一下把。放在List里面,以便于调用。

iterator begin() { return iterator(_head->_next); }
iterator end() { return iterator(_head); }

const_iterator begin() const { return iterator(_head->_next); }
const_iterator end() const { return iterator(_head); }

2.5 clear清理和析构函数实现

2.5.1 实现clear()

 这里的clear会将头结点以后得数据全部清除掉,但是不会释放头结点。

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

2.5.2实现析构函数

 上面介绍析构函数的时候就实现了一下。这里再实现一下。

//析构函数
		~list()
		{
			clear(); //一定要先释放头结点后面的数据
			delete _head;
			_head = nullptr;
		}

2.6 erase和insert实现头尾数据的删除

我们可以通过erase和insert发现实现数据操作和这两有很多重复的代码,我们可以直接调用简化这里的代码,最终来实现一个完整的List。 

2.6.1 实现erase()

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

2.6.2  实现insert()

//insert()
		iterator 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;

			return iterator(newnode);
		}

2.7 List的最终完全实现

这里将我们实现的List代码,整体观察一下,List的底层原理就介绍到这啦


namespace zb
{
	template<class T>
	struct __list_node
	{
		__list_node<T>* _next;
		__list_node<T>* _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_iterator<T, T&, T*> iterator;              //普通迭代器
		//typedef __list_iterator<T> iterator;
		typedef __list_node<T> Node;
		Node* _node;
		__list_iterator(Node* node)
			
			:_node(node)
		{}
		//*it
		/*T& operator*()
		{
			return _node->_data;
		}*/
		//
		Ref operator*() //迭代器使用*的意思是 获取结点值,所以就直接解引用_node,然后返回
		{
			return _node->_data;
		}
		//
		Ptr operator->()
		{
			return &(_node->_data);
		}
		//++it
		iterator& operator ++()
		{
			_node = _node->_next;
			return *this;
		}
		//--it
		iterator& operator --()
		{
			_node = _node->_prev;
			return *this;
		}
		//!=
		bool operator!=(const iterator& it) const
		{
			return _node != it._node;
		}
		//==
		bool operator==(const iterator& it) const
		{
			return _node == it._node;
		}
	};
	struct Date
	{
		int _year;
		int _month;
	};
	template<class T>
	class list
	{
		typedef __list_node<T> Node;
	public:
		//typedef __list_iterator<T> iterator;
		//typedef __list_iterator<T, T&> iterator;              //普通迭代器
		//typedef __list_iterator<T, const T&> const_iterator;  //常迭代器
		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 iterator(_head->_next); }
		const_iterator end() const { return iterator(_head); }

		list()
		{//带头双向循环
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		//迭代器构造函数
		//template <class InputIterator, class OutputIterator>
		//list(InputIterator input, OutputIterator output)
		//{
		//	_head = new Node;  // 给头结点开辟一块空间.
		//	_head->_next = _head;
		//	_head->_prev = _head;
		//	while (input != output)
		//	{
		//		push_back(*input); //这个函数是用来实现尾插数据的,博主在下面进行讲解
		//		input++;
		//	}
		//}
		//拷贝构造
		list(const list<T>& lt)
		{
			_head = new Node;    // 这里必须先开个头结点空间哦,因为这是拷贝构造,不是赋值
			_head->_next = _head;
			_head->_prev = _head;
			for (const auto& e : lt)
			{
				push_back(e);//这个函数后面会实现
			}
		}
		//赋值重载
		list<T>& operator=(const list<T> lt)  
	//注意哦,这里的参数故意没有使用 引用.这样lt就是通过拷贝构造获取了值
		{
			swap(_head, lt._head);   //然后交换两个链表的头结点
			return *this;
		}
		//clear()
			void clear()
			{
				iterator it = begin();
				while (it != end())
				{
					it = erase(it);
				}
			}	
		//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);
		}
		//insert()
		iterator 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;

			return iterator(newnode);
		}
		//析构函数
		~list()
		{
			clear(); //一定要先释放头结点后面的数据
			delete _head;
			_head = nullptr;
		}
		尾插
		//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;
		//}
		尾删
		//void pop_back()
		//{
		//	assert(_head != _head->_next);   //如果数据为空,不可删除

		//	Node * oldtail = _head->_prev;      //提取旧尾巴结点
		//	Node * newtail = oldtail->_prev;    //保存旧尾巴结点的前一个结点
		//	delete oldtail;
		//	oldtail = nullptr;
		//	_head->_prev = newtail;
		//	newtail->_next = _head;
		//}
		头插
		//void push_front(const T& val)
		//{
		//	Node* tmp = new Node(val);     //给新数据新建一个结点
		//	Node* next = _head->_next;     //保存头结点下一个结点
		//	_head->_next = tmp;
		//	tmp->_prev = _head;            //头结点和新数据结点连接
		//	tmp->_next = next;
		//	next->_prev = tmp;             //新结点和保存结点连接
		//}
		头删
		//void pop_front()
		//{
		//	assert(_head != _head->_next);//如果数据为空,不可删除
		//	Node * dnext = _head->_next->_next;
		//	delete _head->_next;
		//	_head->_next = dnext;
		//	dnext->_prev = _head;
		//}
		void push_back(const T& x) { insert(end(), x); }
		void push_front(const T& x) { insert(begin(), x); }
		void pop_back() { erase(--end()); }
		void pop_front() { erase(begin()); }
	private:
		Node* _head;
	};

	void test1()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);

		l1.push_back(3);
		l1.push_back(4);
		l1.pop_back();
		l1.push_front(5);
		l1.pop_front();


		/*list<int>l2(l1.begin(), l1.end());*/

		list<int>::iterator it = l1.begin();
		while (it != l1.end())
		{
			//*it *= 2;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
//void test2()
//{
//	Date date[10];
//	zb::list<Date> li(date, date + 10);
//
//}
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值