【STL】list模拟实现(画图万字解析+代码)

news2024/11/28 22:36:00

list模拟实现

  • 1 模块分析
    • 1.1 list的结构
    • 1.2 ListNode的结构
    • 1.3 迭代器类
  • 2 ListNode节点设计
  • 3 迭代器类设计
    • 3.1 迭代器类框架
    • 3.2 模板设计
    • 3.3 operator++()前置++和后置++
    • 3.4 operator--()前置--和后置--
    • 3.4 operator*()
    • 3.5 operator->()
    • 3.6 operator!=() 和 operator==()
    • 3.7 迭代器类全部代码
  • 4 list类实现
    • 4.1 list 类框架
    • 4.2 构造函数
      • 4.2.1 无参构造list()
      • 4.2.2 迭代器构造 list(iterator first, iterator last)
      • 4.2.3 拷贝构造
      • 4.2.4 n个值的构造 list(size_t n, const T& x)
    • 4.3 赋值运算符重载
    • 4.4 析构函数 ~list()
    • 4.5迭代器相关函数
    • 4.6访问容器相关函数
    • 4.7 容量相关函数
      • 4.7.1 size()
      • 4.7.2 empty()
      • 4.7.3 resize(size_t n, const T& x = T())
    • 4.8 增删查改相关的函数
      • 4.8.1 Insert(iterator pos, const T& x)
      • 4.8.2 Erase(iterator pos)
      • 5.8.3 push_back 和 push_front
      • 5.8.4 pop_back 和 pop_front
  • 6总结
    • 6.1 list.h
    • 6.2 list.cpp
    • 6.3 test_list.cpp

1 模块分析

1.1 list的结构

在这里插入图片描述
list是一个带头双向循环链表的结构,每一个节点是ListNode

ListNode的结构是有两个指针,分别是_prev_next,还有一个存储节点值的_data

1.2 ListNode的结构

在这里插入图片描述
ListNode的成员有三个,分别是:
1. 存储前一个结点的 _prev
2. 存储 后一个结点的_next
3. 存储 值的_data

1.3 迭代器类

1. 为什么要添加一个迭代器类?为什么vector不需要添加?

首先回答为什么vector不需要添加,因为 vector的空间是连续的。当vector访问下一个元素的时候,直接++就能找到下一个元素。 而list的空间是不连续的,所以++是找不到下一个元素的,必须使用node = node->next这样的方式找到下一个元素的位置。

为什么要添加迭代器类?其实是可以不添加的,在list遍历的时候,直接使用node = node->next也可以,但是 为了STL的容器的操作方式统一,就有必要添加一个迭代器类来封装一下接口。让list也能使用++来实现访问下一个元素。

2 ListNode节点设计

在这里插入图片描述

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

	//构造函数
	ListNode(const T& x = T())
		:_prev(nullptr)
		,_next(nullptr)
		,_data(x)
	{}
};

3 迭代器类设计

3.1 迭代器类框架

先了解迭代器类的框架,后续会做详细解释。

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

		__list_iterator(Node* node = nullptr)
			:_node(node)
		{}
	public:
		//前置++   --> 返回++之后的内容
		self& operator++()
		{}

		//后置++  --> 返回++之前的内容
		self operator++(int)
		{}

		self& operator--()
		{}

		self operator--(int)
		{}

		Ref operator*()
		{}

		Ptr operator->()
		{}

		bool operator!=(const self& s)	const
		{}

		bool operator==(const self& s)	const
		{}

	private:
		Node* _node;
	};

3.2 模板设计

template <class T, class Ref, class Ptr>

对迭代器的模板设置了三个模板参数。
2. 为什么要设置三个模板参数?
在下面list中要定义迭代器类型,分别是普通迭代器iterator和const迭代器const_iterator

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<const T, const T&, const T*> const_iterator;

迭代器类的模板参数列表当中的Ref Ptr 分别代表的是 引用类型(T&) 指针类型(T *)

当我们使用 普通迭代器时,编译器就会实例化出一个 普通迭代器对象;当我们使用 const迭代器时,编译器就会实例化出一个 const迭代器对象

若该迭代器类不设计三个模板参数,那么就不能很好的区分 – 普通迭代器 – const迭代器

因为加上模板之后,名称就太长了,所以我们可以使用typedef来解决这个问题

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

3.3 operator++()前置++和后置++

3. 前置++和后置++有什么区别?

前置++返回 ++之后的值
后置++返回 ++之前的值

无论是前置++还是后置++,返回的都是迭代器本身。在代码中,应该返回的是 __list_iterator<T, Ref, Ptr>,但是我们typedef过了,所以返回的应该是self

4. 代码中如何区分前置++和后置++?

//前置++
self& operator++()
//后置++
self operator++ (int)

区分就是 后置++在()中添加一个int作为占位符

5. 为什么前置++返回self&而后置++返回self

因为前置++返回的是++之后的值,就可以返回this,而this是一直存在的,不会出栈帧之后销毁,函数结束后依然存在。返回引用不会出现问题。返回引用可以减少拷贝次数。

后置++返回的是++之前的值,返回的是一个++之前的 局部变量,出了栈帧会销毁。如果返回引用就会出现问题。只能使用值拷贝。

结合前面几个问题,前置++和后置++的代码如下:

//前置++   --> 返回++之后的内容
self& operator++()
{
	_node = _node->_next;
	return *this;   //this表示self*类型,所以要对this解引用
}

//后置++  --> 返回++之前的内容
self operator++(int)
{
	self tmp(*this);
	_node = _node->_next;
	return tmp;
}

3.4 operator–()前置–和后置–

operator–()和operator++()类似。唯一的不同是_node = _node->_prev;
代码如下:

self& operator--()
{
	_node = _node->_prev;
	return *this;
}

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

3.4 operator*()

解引用就是直接获取该位置的数据值。

但是我们可能会对该位置的数据值进行修改,因此需要返回的是引用。因此返回Ref

Ref对应的就是T&

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

3.5 operator->()

->返回的是指针。.因此需要返回Ptr

ptr对应的就是T*

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

3.6 operator!=() 和 operator==()

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

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

6. 为什么传入的参数是const self& s ?

首先,这是比较的函数,所以应该传递是相同的类型。调用operator==()的就是self类型,因此传递的也必须是self类型.

其次, 为了减少拷贝,可以传递引用。但是如果只是传递self&,那么const类型的参数无法传递过来。 因为const类型转化为非const类型,属于权限的放大,这是不允许的因此需要加上const,这样非const类型和const类型都可以传递

3.7 迭代器类全部代码

	template <class T, class Ref, class Ptr>
	class __list_iterator
	{
	public:
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		__list_iterator(Node* node = nullptr)
			:_node(node)
		{}
	public:
		//前置++   --> 返回++之后的内容
		self& operator++()
		{
			_node = _node->_next;
			return *this;   //this表示self*类型,所以要对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->_data;
		}

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

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

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

		Node* _node;
	};

4 list类实现

4.1 list 类框架

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

	public:
		
		//迭代器
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;


		
		//构造函数
		list();
		list(size_t n, const T& x = T());
		list(iterator first, iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		~list();
		//拷贝构造
		list(list<T>& lt);
		
		//初始化一个循环链表
		void empty_init();

		//赋值构造
		list<T>& operator=(list<T>& lt);

		
		//容量相关的函数
		size_t size();
		bool Empty();
		void clear();
		void resize(size_t n, const T& x = T());
		
		//访问容器相关的函数
		T& front();
		T& back();
		const T& front() const;
		const T& back()	const;

		
		//修改相关的函数
		void push_back(const T& x);
		void pop_back();
		void push_front(const T& x);
		void pop_front();
		iterator Insert(iterator pos, const T& x);
		iterator Erase(iterator pos);
		void swap(list<T>& tmp);
	private:
		Node* _head;
	};
};

4.2 构造函数

4.2.1 无参构造list()

头结点的结构如下:
在这里插入图片描述
因此,构造函数就是new一个结点,然后_prev指向头结点,_next指向头结点

template<class T>
zyy::list<T>::list()
{
	_head = new Node;
	_head->_prev = _head; //指向自己
	_head->_next = _head; //指向自己
}

因为后续一直要用到这个创建头结点的操作,因此可以单独增加一个函数来完成这个操作。

template <class T>
void zyy::list<T>::empty_init()
{
	_head = new Node;
	_head->_prev = _head; //指向自己
	_head->_next = _head; //指向自己
}

4.2.2 迭代器构造 list(iterator first, iterator last)

迭代器构造传入两个迭代器,然后将内容从first一直push_back,直到last结束。

list(iterator first, iterator last)
{
	empty_init();//创建一个头结点
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

4.2.3 拷贝构造

拷贝构造函数的步骤:
1.构造一个头结点lt2
2.调用迭代器构造构造出tmp,tmp和lt1完全相同
3.交换lt2和tmp的头结点
4.tmp最后会自动调用析构函数销毁

在这里插入图片描述
在这里插入图片描述

template <class T>
zyy::list<T>::list(list<T>& lt)
{
	//1.构造一个头结点
	empty_init();
	//2.迭代器构造一个tmp,内容和lt相同
	list<T> tmp(lt.begin(), lt.end());
	//3.交换tmp的头结点和头结点
	std::swap(tmp._head, _head);
}

4.2.4 n个值的构造 list(size_t n, const T& x)

template<class T>
zyy::list<T>::list(size_t n, const T& x)
{
	//1.创造一个头结点
	empty_init();
	//2.将n个x尾插进来
	for (int i = 0; i < n; ++i)
	{
		push_back(x);
	}
}

4.3 赋值运算符重载

步骤:
1.将原来的内容清空
2.将传递进来的list的内容都push_back到原来的结点中
3.返回*this

template<class T>
zyy::list<T>& zyy::list<T>::operator=(list<T>& lt)
{
	//将原来的内容清空
	clear();
	if (this != &lt) //避免自己复制自己
	{
		for (auto e : lt)
		{
			push_back(e);
		}
	}

	return *this;
}

7. 为什么要传引用 ?

传递引用可以不用拷贝,减少开销

8. 返回值类型为什么是list<T>& ?

1.实现连续赋值:
允许连续赋值操作,例如a = b = c如果返回值不是引用,那么a = b会返回一个临时对象,再用这个临时对象去赋值给c会导致一个新的临时对象的创建和赋值,效率较低且不符合通常的连续赋值语义。而返回引用可以直接返回当前对象本身,使得连续赋值能够正确地进行。

2.避免不必要的拷贝:
返回引用避免了返回值的拷贝构造,提高了效率。如果返回值不是引用而是一个值类型,那么每次赋值操作都可能涉及到对象的拷贝构造,对于大型复杂对象来说,这可能是非常昂贵的操作。

除了上面这种实现方式,还有另外一种更高效的实现方式。

将赋值运算符重载的参数定义为list 类型的对象 而不是对象的引用,传参时会发生值拷贝。

因此我们可以把 list对象 的 this指针 和 拷贝出来的参数 L 指向头结点的指针交换,这样 this指针 就直接指向了拷贝出来的L的头结点。L则指向了list对象的头结点,在函数结束后,作为局部对象的L将被销毁,它指向的空间也会被释放。

template<class T>
void  zyy::list<T>::swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);
}
template<class T>
zyy::list<T>& zyy::list<T>::operator=(list<T> lt)
{
	swap(lt);

	return *this;
}

4.4 析构函数 ~list()

先将内容清空,再delete 掉头结点,最后将头结点释放

清空函数:

template<class T>
void zyy::list<T>::clear()
{
	auto it = begin();
	while (it != end())
	{
		it = Erase(it);
	}
}

析构函数:

template<class T>
zyy::list<T>::~list()
{
	//1.清空
	clear();
	//2.delete掉头结点
	delete _head;
	//3.置空
	_head = nullptr;
}

4.5迭代器相关函数

下面是list中迭代器的位置示意图:
在这里插入图片描述

begin 函数返回的是第一个有效数据的迭代器, end函数返回的是最后一个有效数据的下一个位置的迭代器

对于 list 这个 带头双向循环链表 来说, 其第一个有效数据的迭代器就是使用头结点后一个结点的地址构造出来的迭代器而其最后一个有效数据的下一个位置的迭代器就是使用头结点的地址构造出来的迭代器。(最后一个结点的下一个结点就是头结点)

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::begin()
{
	return iterator(_head->_next);
}

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::end()
{
	return iterator(_head);
}

const类型

template<class T>
typename zyy::list<T>::const_iterator zyy::list<T>::begin() const
{
	return const_iterator(_head->_next);
}

template<class T>
typename zyy::list<T>::const_iterator zyy::list<T>::end() const
{
	return const_iterator(_head);
}

4.6访问容器相关函数

在这里插入图片描述

template<class T>
T& zyy::list<T>::front()
{
	return *begin();
}

template<class T>
T& zyy::list<T>::back()
{
	return *(--end());
}

const类型

template<class T>
const T& zyy::list<T>::front()	const
{
	return *begin();
}

template<class T>
const T& zyy::list<T>::back()	const
{
	return *(--end());
}

4.7 容量相关函数

4.7.1 size()

遍历全部的结点,每遍历一个,n += 1

		size_t size()
		{
			int n = 0;
			auto it = begin();
			while (it != end())
			{
				++it;
				++n;
			}

			return n;
		}

4.7.2 empty()

当头结点和尾结点相等时,则为空

bool Empty()
{
	return begin() == end();
}

4.7.3 resize(size_t n, const T& x = T())

resize()函数扩容的规则:

  1. 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
  2. 若当前容器的size大于所给n,则只保留前n个有效数据。
void resize(size_t n, const T& val = T())
{
	iterator i = begin(); //获取第一个有效数据的迭代器
	size_t len = 0; //记录当前所遍历的数据个数
	while (len < n && i != end())
	{
		len++;
		i++;
	}
	if (len == n) //说明容器当中的有效数据个数大于或是等于n
	{
		while (i != end()) //只保留前n个有效数据
		{
			i = erase(i); //每次删除后接收下一个数据的迭代器
		}
	}
	else //说明容器当中的有效数据个数小于n
	{
		while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n
		{
			push_back(val);
			len++;
		}
	}
}

4.8 增删查改相关的函数

4.8.1 Insert(iterator pos, const T& x)

在这里插入图片描述
在这里插入图片描述

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::Insert(iterator pos, const T& x)
{
	assert(pos._node);

	//1.找到cur和prev
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	//2.创建一个新的结点newnode
	Node* newnode = new Node(x);

	//3.建立newnode和prev的双向关系
	prev->_next = newnode;
	newnode->_prev = prev;

	//4.建立newnode和cur的双向关系
	cur->_prev = newnode;
	newnode->_next = cur;

	return iterator(newnode);
}

4.8.2 Erase(iterator pos)

在这里插入图片描述

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::Erase(iterator pos)
{
	assert(pos._node); //检测pos的合法性
	assert(pos != end()); //删除的结点不能是头结点

	Node* cur = pos._node; //迭代器pos处的结点指针
	Node* prev = cur->_prev; //迭代器pos前一个位置的结点指针
	Node* next = cur->_next; //迭代器pos后一个位置的结点指针

	delete cur; //释放cur结点

	//建立prev与next之间的双向关系
	prev->_next = next;
	next->_prev = prev;

	return iterator(next); //返回所给迭代器pos的下一个迭代器
}

5.8.3 push_back 和 push_front

可以复用Insert()函数

push_back是在头结点前插入一个结点.

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

push_front函数就是在第一个有效结点前插入结点

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

5.8.4 pop_back 和 pop_front

可以复用Erase()函数

pop_back就是删除头结点的前一个结点

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

pop_front就是删除第一个有效结点。

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

6总结

6.1 list.h

#pragma once

#include<iostream>
#include<assert.h>
#include<string>

using std::istream;
using std::ostream;
using std::endl;
using std::cout;

namespace zyy
{
	template <class T>
	struct ListNode
	{
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;

		//构造函数
		ListNode(const T& x = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{}
	};

	template <class T, class Ref, class Ptr>
	class __list_iterator
	{
	public:
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;

		__list_iterator(Node* node = nullptr)
			:_node(node)
		{}
	public:
		//前置++   --> 返回++之后的内容
		self& operator++()
		{
			_node = _node->_next;
			return *this;   //this表示self*类型,所以要对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->_data;
		}

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

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

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

		Node* _node;
	};
	

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

	public:
		
		//迭代器
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;


		
		//构造函数
		list()
		{
			_head = new Node;
			_head->_prev = _head; //指向自己
			_head->_next = _head; //指向自己
		}
		list(size_t n, const T& x = T());
		list(iterator first, iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		~list()
		{
			//1.清空
			clear();
			//2.delete掉头结点
			delete _head;
			//3.置空
			_head = nullptr;
		}
		//拷贝构造
		list(list<T>& lt);
		
		//初始化一个循环链表
		void empty_init();

		//list<T>& operator=(list<T>& lt);
		//赋值构造   -- 传递值,而不是引用
		list<T>& operator=(list<T> lt);

		
		//容量相关的函数
		size_t size()
		{
			int n = 0;
			auto it = begin();
			while (it != end())
			{
				++it;
				++n;
			}

			return n;
		}

		bool Empty()
		{
			return begin() == end();
		}
		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = Erase(it);
			}
		}
		void resize(size_t n, const T& val = T())
		{
			iterator i = begin(); //获取第一个有效数据的迭代器
			size_t len = 0; //记录当前所遍历的数据个数
			while (len < n && i != end())
			{
				len++;
				i++;
			}
			if (len == n) //说明容器当中的有效数据个数大于或是等于n
			{
				while (i != end()) //只保留前n个有效数据
				{
					i = Erase(i); //每次删除后接收下一个数据的迭代器
				}
			}
			else //说明容器当中的有效数据个数小于n
			{
				while (len < n) //尾插数据为val的结点,直到容器当中的有效数据个数为n
				{
					push_back(val);
					len++;
				}
			}
		}
		
		//访问容器相关的函数
		T& front();
		T& back();
		const T& front()	const;
		const T& back()	const;

		
		//修改相关的函数
		void push_back(const T& x)
		{
			Insert(end(), x);
		}
		void pop_back()
		{
			Erase(--end());
		}
		void push_front(const T& x)
		{
			Insert(begin(), x);
		}
		void pop_front()
		{
			Erase(begin());
		}
		iterator Insert(iterator pos, const T& x);
		iterator Erase(iterator pos);
		void swap(list<T>& tmp);
		void print_list()
		{
			auto it = begin();
			while (it != end())
			{
				cout << *it << " ";
				++it;
			}
			cout << endl;
		}
	private:
		Node* _head;
	};
};

6.2 list.cpp

#include "list.h"
//template<class T>
//zyy::list<T>::list()
//{
//	_head = new Node;
//	_head->_prev = _head; //指向自己
//	_head->_next = _head; //指向自己
//}

template <class T>
void zyy::list<T>::empty_init()
{
	_head = new Node;
	_head->_prev = _head; //指向自己
	_head->_next = _head; //指向自己
}

template <class T>
zyy::list<T>::list(list<T>& lt)
{
	//1.构造一个头结点
	empty_init();
	//2.迭代器构造一个tmp,内容和lt相同
	list<T> tmp(lt.begin(), lt.end());
	//3.交换tmp的头结点和头结点
	std::swap(tmp._head, _head);
}

template<class T>
zyy::list<T>::list(size_t n, const T& x)
{
	//1.创造一个头结点
	empty_init();
	//2.将n个x尾插进来
	for (int i = 0; i < n; ++i)
	{
		push_back(x);
	}
}

//template<class T>
//zyy::list<T>& zyy::list<T>::operator=(list<T>& lt)
//{
//	//将原来的内容清空
//	clear();
//	if (this != &lt) //避免自己复制自己
//	{
//		for (auto e : lt)
//		{
//			push_back(e);
//		}
//	}
//
//	return *this;
//}

template<class T>
void  zyy::list<T>::swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);
}

template<class T>
zyy::list<T>& zyy::list<T>::operator=(list<T> lt)
{
	swap(lt);

	return *this;
}

//template<class T>
//void zyy::list<T>::clear()
//{
//	auto it = begin();
//	while (it != end())
//	{
//		it = Erase(it);
//	}
//}

//template<class T>
//zyy::list<T>::~list()
//{
//	//1.清空
//	clear();
//	//2.delete掉头结点
//	delete _head;
//	//3.置空
//	_head = nullptr;
//}

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::begin()
{
	return iterator(_head->_next);
}

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::end()
{
	return iterator(_head);
}

template<class T>
typename zyy::list<T>::const_iterator zyy::list<T>::begin() const
{
	return const_iterator(_head->_next);
}

template<class T>
typename zyy::list<T>::const_iterator zyy::list<T>::end() const
{
	return const_iterator(_head);
}

template<class T>
T& zyy::list<T>::front()
{
	return *begin();
}

template<class T>
T& zyy::list<T>::back()
{
	return *(--end());
}

template<class T>
const T& zyy::list<T>::front()	const
{
	return *begin();
}

template<class T>
const T& zyy::list<T>::back()	const
{
	return *(--end());
}

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::Insert(iterator pos, const T& x)
{
	assert(pos._node);

	//1.找到cur和prev
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	//2.创建一个新的结点newnode
	Node* newnode = new Node(x);

	//3.建立newnode和prev的双向关系
	prev->_next = newnode;
	newnode->_prev = prev;

	//4.建立newnode和cur的双向关系
	cur->_prev = newnode;
	newnode->_next = cur;

	return iterator(newnode);
}

template<class T>
typename zyy::list<T>::iterator zyy::list<T>::Erase(iterator pos)
{
	assert(pos._node); //检测pos的合法性
	assert(pos != end()); //删除的结点不能是头结点

	Node* cur = pos._node; //迭代器pos处的结点指针
	Node* prev = cur->_prev; //迭代器pos前一个位置的结点指针
	Node* next = cur->_next; //迭代器pos后一个位置的结点指针

	delete cur; //释放cur结点

	//建立prev与next之间的双向关系
	prev->_next = next;
	next->_prev = prev;

	return iterator(next); //返回所给迭代器pos的下一个迭代器
}

6.3 test_list.cpp

#include "list.cpp"
using namespace zyy;


void test1()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.print_list();
}

void test2()
{
	list<int> lt;
	lt.push_front(1);
	lt.push_front(2);
	lt.push_front(3);
	lt.push_front(4);
	lt.push_front(5);
	lt.Insert(++lt.begin(), 10);
	lt.print_list();
}

void test3()
{
	list<int> lt;
	lt.push_front(1);
	lt.push_front(2);
	lt.push_front(3);
	lt.push_front(4);
	lt.push_front(5);
	lt.pop_back();
	lt.pop_front();
	lt.print_list();
}

void test4()
{
	list<int> lt;
	lt.push_front(1);
	lt.push_front(2);
	lt.push_front(3);
	lt.push_front(4);
	lt.push_front(5);
	lt.Erase(++lt.begin());
	lt.print_list();

	cout << lt.size() << endl;
	if (lt.Empty())
	{
		cout << "lt是空的" << endl;
	}
	else
	{
		cout << "lt不是空的" << endl;
	}
	lt.resize(10, 10);
	lt.print_list();

	cout << "lt.front(): " << lt.front() << endl;
	cout << "lt.back(): " << lt.back() << endl;
}

int main()
{
	test4();
	return 0;
}

在这里插入图片描述

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

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

相关文章

一张照片变换古风写真,Flux如何做到?

前言 解锁图像创作新体验&#xff1a;ComfyUI指南 在AI图像生成领域&#xff0c;ComfyUI 已成为不可忽视的力量。它是基于Stable Diffusion的图像生成工具&#xff0c;提供了一个节点式图形用户界面&#xff08;GUI&#xff09;&#xff0c;让用户可以通过简单的拖拽与配置来…

睡眠对于生活的重要性

在快节奏的现代生活中&#xff0c;健康养生不再是遥不可及的概念&#xff0c;而是融入日常每一刻的必需。其中&#xff0c;睡眠作为生命不可或缺的环节&#xff0c;其重要性往往被忽视&#xff0c;实则它是身体修复、能量积蓄的黄金时段。今天&#xff0c;让我们深入探讨“健康…

【橙子老哥】.NetCore 管道模型源码深度解读

hello&#xff0c;大家好&#xff0c;今天又是橙子老哥的分享时间&#xff0c;希望大家一起学习&#xff0c;一起进步。 欢迎加入.net意社区&#xff0c;第一时间了解我们的动态&#xff0c;地址&#xff1a;ccnetcore.com 最近遇到很多小伙伴们问我&#xff0c;自己会.netfr…

【电力系统】Matlab|含风电-光伏-光热电站电力系统N-k安全优化调度模型

摘要 本文提出了一种结合风电、光伏与光热电站的电力系统N-k安全优化调度模型。通过在电力系统中集成多种可再生能源发电技术&#xff0c;优化不同类型电源的调度策略&#xff0c;确保在N-k故障情景下系统的稳定运行。基于Matlab仿真&#xff0c;本文分析了可再生能源发电的功…

路由:ReactRouter

概述 一个路径path对应一个组件component 当我们在浏览器中访问一个path的时候&#xff0c;path对应的组件会在页面中进行渲染。 使用 快速开始 安装依赖 npm i react-router-dom基本使用 import { createBrowserRouter, RouterProvider } from react-router-domconst ro…

【JavaEE初阶】多线程案列之定时器的使用和内部原码模拟

前言&#xff1a; &#x1f308;上期博客&#xff1a;【JavaEE初阶】深入理解多线程阻塞队列的原理&#xff0c;如何实现生产者-消费者模型&#xff0c;以及服务器崩掉原因&#xff01;&#xff01;&#xff01;-CSDN博客 &#x1f525;感兴趣的小伙伴看一看小编主页&#xff1…

房地产销售|基于springBoot的房地产销售管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 社会和科技的不断进步带来更便利的生活&#xff0c;计算机技术也越来…

fiddler抓包18-2_导出jmeter、postman脚本(带请求头)

课程大纲 1. Fiddler导出请求为curl脚本 选中请求&#xff0c;“文件” - “导出会话” - “选中的会话” - “cURL Script”。 2. 导入jmeter ① 复制curl脚本。 ② 打开jmeter&#xff0c;“工具” - “import from cURL”&#xff0c;粘贴脚本&#xff0c;勾选“Add cooki…

二分查找一>寻找峰值

1.题目&#xff1a; 2.解析&#xff1a; 暴力遍历代码&#xff1a;O(N),由于该题数据很少所以可以通过 暴力遍历&#xff1a;O(N),由于该题数据很少所以可以通过int index 0;for(int i 1; i < nums.length-1; i) {//某段区域内一直递增&#xff0c;更新就indexif(nums[i]…

codetop标签树刷题(三)!!暴打面试官!!!!

用于个人复习 1.子结构判断2.寻找重复的子树3.相同的树4.平衡二叉树5.二叉树展开为链表6.将二叉搜索树转化为排序的双向链表7.验证二叉搜索树8.二叉树的完全性检验9.完成二叉树的节点个数10.删除二叉搜索树中的节点11.寻找二叉树中的目标节点 1.子结构判断 给定两棵二叉树 tre…

Libtorch学习之Libtorch-VS2019-图像分割程序

文章目录 环境说明Pytorch 序列化Libtorch 下载VS配置主程序可能遇到的问题参考 环境说明 win10 VS2019 OPENCV4.7.0 Litorch1.13 Pytorch 1.12.1 Pytorch 序列化 import torch from torchvision.models import resnet50 net resnet50(pretrainedTrue) net net.cuda() net…

提升开机速度:有效管理Windows电脑自启动项,打开、关闭自启动项教程分享

日常使用Windows电脑时&#xff0c;总会需要下载各种各样的办公软件。部分软件会默认开机自启功能&#xff0c;开机启动项是指那些在电脑启动时自动运行的程序和服务。电脑开机自启太多的情况下会导致电脑卡顿&#xff0c;开机慢&#xff0c;运行不流畅的情况出现&#xff0c;而…

如何从计算机的硬盘中恢复照片 - 成功

如何从计算机硬盘恢复图片&#xff1f; 与所有电子和机械设备一样&#xff0c;硬盘驱动器也可能由于任何原因而死机。如果您的系统硬盘驱动器已停止工作或在启动系统时听到振动声&#xff0c;则它有可能已死机。如果是这样的话&#xff0c;上面的数据呢&#xff1f; 不要惊慌…

十二、血条UI

一、制作血条UI 注&#xff1a;一般不用Slider制作血条&#xff1b;而是用两个Image制作&#xff0c;选择为填充 使用Slider滑动条制作UI 人物血条&#xff1a;背景深绿色&#xff1b;滑条浅绿色 在场景中的画布选择为OverLay 敌人血条&#xff1a; 在预制体里面制作&#x…

自动驾驶系列—自动驾驶背后的数据通道:通信总线技术详解与应用场景分析

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

最具有世界影响力的人颜廷利:全球著名哲学家思想家起名大师

颜廷利教授&#xff0c;这位源自济南唐王镇的杰出人物&#xff0c;不仅是中国当代最杰出的国学大师之一&#xff0c;更是将传统文化与现代科技巧妙结合的先锋。他积极推崇以人工智能技术为辅助的国学研究方法&#xff0c;为这一古老领域注入了新的活力和时代表达。 除了在学术…

【LeetCode】每日一题 2024_10_6 加油站(贪心)

前言 每天和你一起刷 LeetCode 每日一题~ 大家国庆节快乐呀~ LeetCode 启动&#xff01; 国庆第 6 天&#xff0c;在加油站 . . . 题目&#xff1a;加油站 代码与解题思路 今天这道题目是力扣上的经典贪心&#xff08;第 134 题&#xff09; func canCompleteCircuit(gas…

springboot中配置优先级

先来看在idea当中运行程序时&#xff0c;如何来指定Java系统属性和命令行参数。 系统属性 1、右键启动类&#xff0c;点击Edit Configuration 点击Modify options 选择Add VM options&#xff0c;就是系统属性 选择Program arguements&#xff0c;就是命令行参数 总结&#…

排查和解决JVM OOM实战

JVM OOM介绍 Java内存区域布局 下面的分析中都是基于JDK 8开始的。关于JMM不过多介绍每个区域的作用。OOM不单只会发生在堆内存&#xff0c;也可能是因为元空间或直接内存泄漏导致OOM&#xff0c;此时在OOM的详细信息中会有不同体现。 Java OOM的类别 java.lang.OutOfMemory…

CSS 布局——清除浮动 (二)

目录 1. 清除浮动 2. 清除浮动本质 3. 清除浮动 4. 清除浮动方法 4.1 额外标签法 4.1.1 总结 4.2 父级添加 overflow 4.3 after 伪元素法 4.4 双伪元素清除浮动 5 总结 1. 清除浮动 这是上面的源代码&#xff1a; <!DOCTYPE html> <html lang"en"&…