C++初阶编程:list容器的简单模拟实现

大家好啊,今天给大家带来的是我们C++编程中,stl库里的重要角色--list的简单的模拟实现,希望通过这篇小博客,对大家更加深入理解list容器有所帮助。

前言:

在C++标准库中,list是一种双向链表容器。

这里简单提一下双向链表——什么是双向链表呢?

双向链表是一种链式数据结构,其中每个节点包含三个部分:

  • 一个存储数据的字段。(我们通常用_data表示)
  • 一个指向前驱节点的指针。(我们通常用_prev表示)
  • 一个指向后继节点的指针。(我们通常用_next表示)

 这样,每个节点都知道它的前一个节点和后一个节点,从而支持在常数时间内进行插入和删除操作。

一:节点结构的定义

在实现list之前,我们要先定义一下这个链表的节点结构。一个链表是有多个节点链接组成,所以节点自然是重中之重。

我们在头文件定义一下节点结构,上文讲到,一个节点包括_data、_prev、_next三个部分:

template <class T>
struct ListNode
{
	ListNode(const T& data = T())//给的缺省值是我们想要存储的数据类型的一个匿名对象(自定义类型需要构造函数)
		:_data(data)
		, _next(nullptr)
		, _prev(nullptr)
	{

	}

	T _data;
	ListNode<T>* _next;//指向下一个节点
	ListNode<T>* _prev;//指向上一个节点
};

当然,也要记得写这个模板类的默认构造,我们用想存储的数据的一个匿名对象来完成对_data的初始化。 

二、list类的默认构造,成员变量

我们用_count来记录链表的节点数目,_head记录双向带头链表的头节点。

为了简写代码,方便我们操作,我们顺便把ListNode<T>重命名为Node。

随后书写list的默认构造函数,就是赋予头节点空间,将头节点的_prev与_next指向自己,还有把_count初始化为0。

template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	list()
	{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
		_count = 0;
	}
private:
	Node* _head;
	size_t _count;
};

三、list:用类实现迭代器的思想

在实现我们list的各种接口之前,我们首先要来实现一下list容器的迭代器。

这属于是list内容的重中之重,不同于vector,我们返回一个指针作为迭代器,指针的++就可以让迭代器向后移动到另一个元素。我们的list是由许多个节点链接而成,又怎么可以使用各种++操作符呢?

在这个基础上,我们想到了,可以是先用一个类,在这个类中重载各种++,--运算符,手动实现迭代器的向后移动(向前移动)。

那我们又如何实现const的迭代器呢?难不成学vector在前面加个const就可以了吗?

显而易见,由于底层结构的不同,const迭代器的实现原理也有所不同。

其实很简单,要想实现const迭代器的作用,我们只需要在迭代器类的各种内置函数的返回参数的类型做手脚就行。函数返回类型变成了const,自然就是const迭代器的作用。这样的代价就是我们要写两个迭代器模板。

但为了减少我们的麻烦,我们可以在写迭代器的时候在tmplete后面多加几个类型参数就行,这样就把我们的麻烦当做礼物送给了编译器(编译器:听我说,谢谢你)。

template<class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;//节点类型
	typedef ListIterator<T, Ref, Ptr> Self;//代表自己这个类型

	Node _node;
	ListIterator(Node node = nullptr)//缺省为nullptr的默认构造函数
		:_node(node)
	{

	}
	ListIterator(const Self& l)//传一个迭代器类型(在实现后置++,后置--可以用得到)
		:_node(l._node)
	{

	}

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

	Ptr operator->()
	{
		return &_node->_data;//Ptr是指针类型,模拟实现->时应该返回的是地址,即返回一个指向当前值的指针
	}

	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& l)
	{
		return _node != l._node;
	}

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

四、反向迭代器的实现

与我们刚刚实现正向迭代器一样的思想,反向迭代器也需要用模板类的思想来解决。

在实现反向迭代器的时候,我们核心仍然通过正向迭代器去运转,只不过在反向迭代器类中再次重载了各种操作符。例如,反向迭代器的前置++,我们只需要在实现的时候,在函数内调用正向迭代器的前置--就行。 

template<class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
	typedef typedef Reverse_iterator Self;
	Iterator _it;

	Reverse_iterator(Iterator it)
		:_it(it)
	{

	}

	Ref operator*()//因为反向迭代器的首位置是由正向迭代器的end()初始化的,在最后一个元素的下一个位置,
		// 解引用时要先创建一个临时变量,使其向前移动一步,才是正确的返回位置(避免改变反向迭代器位置)
	{
		Iterator tmp = _it;
		return *(--tmp);
	}

	Ptr operator->()
	{
		Iterator tmp = _it;
		--tmp;
		return &(*tmp);
	}

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

	bool operator!=(const Self& l)
	{
		return _it != l._it;
	}

	bool operator==(const Self& l)
	{
		return _it == l._it;
	}

};

五、list迭代接口的实现

终于把list的迭代器给实现了,紧接着,我们就应该在list类里实现迭代器的各种接口。

先来重命名迭代器:

typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_iterator<const_iterator,const T&,const T*> const_reverse_iterator;

大家请看上面四行代码,我们通过传递的不同类型的参数类型,实例化出四种不同的迭代器,我们想使用const迭代器时就在重命名const_iterator时传递const T&,const T*就行。

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

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

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

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

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

	const_iterator end()const
	{
		return const_iterator(_head);
	}

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

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

六、list插入函数以及删除函数的实现

list的插入主要是push_back,insert,push_front三个函数,删除操作主要是pop_back(),erase,pop_front三个函数。

//不涉及释放空间,不存在迭代器失效
// 在pos位置前插入值为val的节点

iterator insert(iterator pos, const T& val)
{

	Node* cur = pos._node;
	Node* newnode = new Node(val);
	Node* prev = cur->_prev;

	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	prev->_next = newnode;

	_count++;

	return iterator(newnode);//返回由Node类型的newnode初始化完成的匿名对象
}

// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)//存在迭代器失效
{
	assert(pos != end());//防止把头节点给删了
	Node* node = pos._node;
	Node* pnext = node->_next;
	Node* prev = node->_prev;

	prev->_next = pnext;
	pnext->_prev = prev;

	delete node;
	_count--;

	return iterator(pnext);
}

随后,我们就可以让push_back()和push_front调用insert函数实现复用,让pop_back()和pop_front调用erase实现复用,减少我们所需要写的代码:

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

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

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

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

七、list各种默认函数的实现

我们需要实现各种其他构造函数,方便list类的各种构造,例如(int n, const T& value = T())//传n个元素,每个元素默认为T(),又或者是根据迭代器区间默认构造。
 我们不难想到,由于要实现这么多的构造函数,这么多的构造函数,如果在构造前都要现在函数里初始化一遍,破坏了封装性,所以我们可以把初始化这个功能写成一个新的函数,随后,在各种构造函数里调用就行:

void empty_init()
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;
	_count = 0;
}

list()//更改默认构造函数
{
	empty_init();
}

  各种构造函数实现:

list(int n, const T& value = T())
{
	empty_init();
	for (int i = 0; i < n; ++i)
	{
		push_back(value);
	}
}

template <class Iterator>//适应各个迭代器的构造
list(Iterator first, Iterator last)
{
	empty_init();

	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

list(const list<T>& l)//拷贝构造
{
	empty_init();
	for (const auto& it : l)
	{
		push_back(it);
	}
}

list<T>& operator=(const list<T> l)//赋值重载
{
	swap(l);

	return *this;
}

~list()
{
	clear();

	delete _head;
	_head = nullptr;
}

当然,在赋值重载中,我们仍然选择了现代写法,于是我们就要实现一个swap接口,在析构函数中,我们也选择用一个clear来删除除了头结点之外的所有节点,所以我们也需要实现一个clear:

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

void clear(list<T>& l)
{
	auto it = begin();

	while (it != end())
	{
		it = erase(it);//小心迭代器失效
	}
}

八、其他接口的实现:

我们想要看这个链表多长,就需要调用我们的size接口,想知道最后或者第一个元素的值,也需要调用back(),front()等接口。

以下是该接口的实现:

	T& front()
	{
		assert(_count > 0);
		return _head->_next->_data;
	}

	const T& front()const
	{
		assert(_count > 0);
		return _head->_next->_data;
	}

	T& back()
	{
		assert(_count > 0);
		return _head->_prev->_data;
	}

	const T& back()const
	{
		assert(_count > 0);
		return _head->_prev->_data;
	}

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

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

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

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

九、总代码

​
#pragma once
#include<assert.h>

namespace bit
{

	template <class T>
	struct ListNode
	{
		ListNode(const T& data = T())//给的缺省值是我们想要存储的数据类型的一个匿名对象(自定义类型需要构造函数)
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{

		}

		T _data;
		ListNode<T>* _next;//指向下一个节点
		ListNode<T>* _prev;//指向上一个节点
	};

	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T>* Node;//节点类型
		typedef ListIterator<T, Ref, Ptr> Self;//代表自己这个类型

		Node _node;
		ListIterator(Node node = nullptr)//缺省为nullptr的默认构造函数
			:_node(node)
		{

		}
		ListIterator(const Self& l)//传一个迭代器类型(在实现后置++,后置--可以用得到)
			:_node(l._node)
		{

		}

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

		Ptr operator->()
		{
			return &_node->_data;//Ptr是指针类型,模拟实现->时应该返回的是地址,即返回一个指向当前值的指针
		}

		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& l)
		{
			return _node != l._node;
		}

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


	template<class Iterator, class Ref, class Ptr>
	struct Reverse_iterator
	{
		typedef Reverse_iterator Self;
		Iterator _it;

		Reverse_iterator(Iterator it)
			:_it(it)
		{

		}

		Ref operator*()//因为反向迭代器的首位置是由正向迭代器的end()初始化的,在最后一个元素的下一个位置,
			// 解引用时要先创建一个临时变量,使其向前移动一步,才是正确的返回位置(避免改变反向迭代器位置)
		{
			Iterator tmp = _it;
			return *(--tmp);
		}

		Ptr operator->()
		{
			Iterator tmp = _it;
			--tmp;
			return &(*tmp);
		}

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

		bool operator!=(const Self& l)
		{
			return _it != l._it;
		}

		bool operator==(const Self& l)
		{
			return _it == l._it;
		}
	};

template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&, const T*> const_iterator;
	typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
	typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

	void empty_init()
	{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
		_count = 0;
	}

	list()
	{
		empty_init();
	}

	list(int n, const T& value = T())
	{
		empty_init();
		for (int i = 0; i < n; ++i)
		{
			push_back(value);
		}
	}

	template <class Iterator>//适应各个迭代器的构造
	list(Iterator first, Iterator last)
	{
		empty_init();

		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

	list(const list<T>& l)//拷贝构造
	{
		empty_init();
		for (const auto& it : l)
		{
			push_back(it);
		}
	}

	list<T>& operator=(const list<T> l)//赋值重载
	{
		swap(l);

		return *this;
	}

	~list()
	{
		clear();

		delete _head;
		_head = nullptr;
	}

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

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

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

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

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

	const_iterator end()const
	{
		return const_iterator(_head);
	}

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

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

	size_t size()const
	{
		return _count;
	}

	bool empty()const
	{
		return _count == 0;
	}

	T& front()
	{
		assert(_count > 0);
		return _head->_next->_data;
	}

	const T& front()const
	{
		assert(_count > 0);
		return _head->_next->_data;
	}

	T& back()
	{
		assert(_count > 0);
		return _head->_prev->_data;
	}

	const T& back()const
	{
		assert(_count > 0);
		return _head->_prev->_data;
	}

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

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

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

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

	//不涉及释放空间,不存在迭代器失效
	// 在pos位置前插入值为val的节点

	iterator insert(iterator pos, const T& val)
	{

		Node* cur = pos._node;
		Node* newnode = new Node(val);
		Node* prev = cur->_prev;

		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;
		prev->_next = newnode;

		_count++;

		return iterator(newnode);//返回由Node类型的newnode初始化完成的匿名对象
	}

	// 删除pos位置的节点,返回该节点的下一个位置
	iterator erase(iterator pos)//存在迭代器失效
	{
		assert(pos != end());//防止把头节点给删了
		Node* node = pos._node;
		Node* pnext = node->_next;
		Node* prev = node->_prev;

		prev->_next = pnext;
		pnext->_prev = prev;

		delete node;
		_count--;

		return iterator(pnext);
	}

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

	void clear()
	{
		auto it = begin();

		while (it != end())
		{
			it = erase(it);
		}
	}

	private:
		Node* _head;
		size_t _count;
	};
}#pragma once
#include<assert.h>

namespace bit
{

	template <class T>
	struct ListNode
	{
		ListNode(const T& data = T())//给的缺省值是我们想要存储的数据类型的一个匿名对象(自定义类型需要构造函数)
			:_data(data)
			, _next(nullptr)
			, _prev(nullptr)
		{

		}

		T _data;
		ListNode<T>* _next;//指向下一个节点
		ListNode<T>* _prev;//指向上一个节点
	};

	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListNode<T>* Node;//节点类型
		typedef ListIterator<T, Ref, Ptr> Self;//代表自己这个类型

		Node _node;
		ListIterator(Node node = nullptr)//缺省为nullptr的默认构造函数
			:_node(node)
		{

		}
		ListIterator(const Self& l)//传一个迭代器类型(在实现后置++,后置--可以用得到)
			:_node(l._node)
		{

		}

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

		Ptr operator->()
		{
			return &_node->_data;//Ptr是指针类型,模拟实现->时应该返回的是地址,即返回一个指向当前值的指针
		}

		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& l)
		{
			return _node != l._node;
		}

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


	template<class Iterator, class Ref, class Ptr>
	struct Reverse_iterator
	{
		typedef Reverse_iterator Self;
		Iterator _it;

		Reverse_iterator(Iterator it)
			:_it(it)
		{

		}

		Ref operator*()//因为反向迭代器的首位置是由正向迭代器的end()初始化的,在最后一个元素的下一个位置,
			// 解引用时要先创建一个临时变量,使其向前移动一步,才是正确的返回位置(避免改变反向迭代器位置)
		{
			Iterator tmp = _it;
			return *(--tmp);
		}

		Ptr operator->()
		{
			Iterator tmp = _it;
			--tmp;
			return &(*tmp);
		}

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

		bool operator!=(const Self& l)
		{
			return _it != l._it;
		}

		bool operator==(const Self& l)
		{
			return _it == l._it;
		}
	};

template<class T>
class list
{
	typedef ListNode<T> Node;
public:
	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T, const T&, const T*> const_iterator;
	typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
	typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

	void empty_init()
	{
		_head = new Node();
		_head->_next = _head;
		_head->_prev = _head;
		_count = 0;
	}

	list()
	{
		empty_init();
	}

	list(int n, const T& value = T())
	{
		empty_init();
		for (int i = 0; i < n; ++i)
		{
			push_back(value);
		}
	}

	template <class Iterator>//适应各个迭代器的构造
	list(Iterator first, Iterator last)
	{
		empty_init();

		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

	list(const list<T>& l)//拷贝构造
	{
		empty_init();
		for (const auto& it : l)
		{
			push_back(it);
		}
	}

	list<T>& operator=(const list<T> l)//赋值重载
	{
		swap(l);

		return *this;
	}

	~list()
	{
		clear();

		delete _head;
		_head = nullptr;
	}

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

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

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

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

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

	const_iterator end()const
	{
		return const_iterator(_head);
	}

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

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

	size_t size()const
	{
		return _count;
	}

	bool empty()const
	{
		return _count == 0;
	}

	T& front()
	{
		assert(_count > 0);
		return _head->_next->_data;
	}

	const T& front()const
	{
		assert(_count > 0);
		return _head->_next->_data;
	}

	T& back()
	{
		assert(_count > 0);
		return _head->_prev->_data;
	}

	const T& back()const
	{
		assert(_count > 0);
		return _head->_prev->_data;
	}

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

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

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

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

	//不涉及释放空间,不存在迭代器失效
	// 在pos位置前插入值为val的节点

	iterator insert(iterator pos, const T& val)
	{

		Node* cur = pos._node;
		Node* newnode = new Node(val);
		Node* prev = cur->_prev;

		newnode->_prev = prev;
		newnode->_next = cur;
		cur->_prev = newnode;
		prev->_next = newnode;

		_count++;

		return iterator(newnode);//返回由Node类型的newnode初始化完成的匿名对象
	}

	// 删除pos位置的节点,返回该节点的下一个位置
	iterator erase(iterator pos)//存在迭代器失效
	{
		assert(pos != end());//防止把头节点给删了
		Node* node = pos._node;
		Node* pnext = node->_next;
		Node* prev = node->_prev;

		prev->_next = pnext;
		pnext->_prev = prev;

		delete node;
		_count--;

		return iterator(pnext);
	}

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

	void clear()
	{
		auto it = begin();

		while (it != end())
		{
			it = erase(it);
		}
	}

	private:
		Node* _head;
		size_t _count;
	};
}

​

十、总结:

本文实现了一个简化版的双向链表模板类 list,主要包括以下内容:

  1. ListNode 结构体

    • 表示链表的节点,包含数据 _data 和指向前一个节点 _prev、后一个节点 _next 的指针。
  2. ListIterator 结构体

    • 实现了链表的迭代器,支持前向和后向遍历,以及解引用操作符 * 和箭头操作符 ->
  3. list 类

    • 使用 ListNode 实现了双向链表的基本操作,包括插入、删除、遍历等功能。
    • 提供了正向迭代器 iterator 和反向迭代器 reverse_iterator
  4. 功能实现

    • 支持构造函数、拷贝构造函数和赋值运算符重载。
    • 实现了 begin()end()rbegin()rend() 等方法,用于迭代器的初始化和操作。
    • 提供了 push_back()pop_back()push_front()pop_front() 等方法,支持双向链表的节点插入和删除。
    • 包含 size()empty()front()back() 等方法,用于获取链表大小和访问首尾元素。

通过这些实现,希望大家可以更加深入理解了双向链表的底层实现原理,并且掌握如何使用迭代器来操作链表元素等方法。

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

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

相关文章

昇思MindSpore学习笔记3--张量 Tensor

一、张量Tensor概念 矢量、标量和其他张量的计算函数&#xff0c;有内积、外积、线性映射以及笛卡儿积等 张量坐标在 n 维空间内&#xff0c;有 nr 个分量 每个分量都是坐标的函数,变换时每个坐标分量都按规则作线性变换 张量是一种特殊的数据结构&#xff0c;类似于数组和…

npm安装包报错解决

目录 一&#xff1a;问题回顾 二:问题分析 三&#xff1a;npm降级或者升级 四&#xff1a;npm和node js 关系 一&#xff1a;问题回顾 今天在本地部署一个vue开发的项目&#xff0c;需要在本地看下运行情况&#xff0c;按照常规的操作就是在网站根目录运行npm install 安装…

如何制作鼠标悬浮后伸缩的搜索框

引言 许多博客都在使用的伸缩搜索框制作教程 成品展示&#xff08;颜色自行搭配&#xff09; 初步布局 居中盒子&&初始化样式 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewpo…

Nuxt3 的生命周期和钩子函数(五)

title: Nuxt3 的生命周期和钩子函数&#xff08;五&#xff09; date: 2024/6/29 updated: 2024/6/29 author: cmdragon excerpt: 摘要&#xff1a;本文详细介绍了Nuxt3中的六个核心生命周期钩子及其用法&#xff0c;包括build:done、build:manifest、builder:generateApp、…

[oeasy]python021_赛博宝剑铭文大赏_宝剑上的铭文_特殊符号和宝物

继续运行 &#x1f94b; 回忆上次内容 上次修改了 程序 将 石中剑变成了 红色 爱之大剑 可以 让宝剑 具有 更多铭文符号 和 颜色 吗&#xff1f;&#x1f914; 铭文 亚瑟王 从石头中 取得宝剑 说明 不列颠科技从石器时代 进入了 青铜时代 第一把 Caliburn 断裂 第二把 湖中仙…

恢复的实现技术-日志和数据转储

一、引言 在系统正常运行的情况下&#xff0c;事务处理的恢复机制应采取某些技术措施为恢复做好相应的准备&#xff0c;保证在系统发生故障后&#xff0c;能将数据库从一个不一致的错误状态恢复到一个一致性状态 恢复技术主要包括 生成一个数据库日志&#xff0c;来记录系统中…

iOS开发中用到的自定义UI库

文章目录 前言cell 左右滑动菜单日历组件仿QQ 侧滑抽屉仿探探、陌陌的卡牌滑动库头部缩放视图自定义UITabbar刮刮乐广告横幅 前言 本文中的UI组件&#xff0c;是作者在移动应用开发中都用到过的。 确实&#xff0c;找到对的三方库可以快速帮助我们构建App, 极大程度上提高了生…

ESP32-C2模组数据透传模式配置详细教程

文章目录 1. 背景2. 关键步骤2.1 烧录AT指令固件2.2 配置透传模式2.3 如何退出透传模式重新配置3. 思考1. 背景 最近做的项目中,有蓝牙+WIFI的数据透传的需求,即系统A和系统B之间的通讯通过无线的方式,其实在实际项目中有很多这种场景比如无线调试手柄、无线数据终端、无线…

c进阶篇(一):数据的存储

1.数据类型介绍 char // 字符数据类型 short // 短整型 int // 整形 long // 长整型 long long // 更长的整形 float // 单精度浮点数 double // 双精度浮点数 1.1整形家族&#xff1a; char unsigned char signed char …

Linux 生产消费者模型

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 前言 1. 生产消费者模型 1.1 什么是生产消…

stm32学习笔记---ADC模数转换器(代码部分)AD单通道/多通道

目录 第一个代码&#xff1a;AD单通道 ADC初始化步骤 ADC相关的库函数 RCC_ADCCLKConfig 三个初始化相关函数 ADC_Cmd ADC_DMACmd ADC_ITConfig 四个校准相关函数 ADC_SoftwareStartConvCmd ADC_GetSoftwareStartConvStatus ADC_GetFlagStatus ADC_RegularChannel…

探索 Electron:将 Web 技术带入桌面应用

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

iOS17系统适配

iOS17 新功能 文章目录 iOS17 新功能iOS17支持哪几款机型Xcode15新特性iOS17-开发适配指南 横屏待机 在iOS 17中&#xff0c;还带来了横屏待机功能&#xff0c;苹果将这个新功能命名为“Standby”模式&#xff0c;为 iPhone 带来了全新的玩法。iPhone启用之后&#xff0c;默认情…

文件加密|电脑文件夹怎么设置密码?5个文件加密软件,新手必看!

电脑文件夹怎么设置密码&#xff1f;您是否希望更好地在电脑上保护您的个人或敏感文件&#xff1f;设置电脑文件夹密码是一种简单而有效的方式来确保你的隐私不被侵犯。通过使用文件加密软件&#xff0c;您可以轻松地为您的文件和文件夹设置密码保护。因此&#xff0c;本文将介…

快速应用开发(RAD):加速软件开发的关键方法

目录 前言1. 快速应用开发的概念1.1 什么是快速应用开发&#xff1f;1.2 RAD与传统开发方法的对比 2. 快速应用开发的实施步骤2.1 需求分析与规划2.2 快速原型开发2.3 用户评估与反馈2.4 迭代开发与改进2.5 最终交付与维护 3. 快速应用开发的优点与应用场景3.1 优点3.2 应用场景…

Python逻辑控制语句 之 判断语句--if elif else 结构(多重判断)

1.if elif else 的介绍 # if elif else 如果 ... 如果 ... 否则 .... # 多个如果之间存在关系 应用场景&#xff1a;在判断条件时, 需要判断 多个条件, 并且对应不同条件要执行 不同的代码 2.if elif else 的语法 if 判断条件1: 判断条件1成立&#xff0c;执行的代码 elif 判…

React@16.x(44)路由v5.x(9)源码(1)- path-to-regexp

目录 1&#xff0c;作用2&#xff0c;实现获取 match 对象2.1&#xff0c;match 对象的内容2.2&#xff0c;注意点2.3&#xff0c;实现 1&#xff0c;作用 之前在介绍 2.3 match 对象 时&#xff0c;提到了 react-router 使用第3方库 path-to-regexp 来匹配路径正则。 我们也…

【漏洞复现】科立讯通信有限公司指挥调度管理平台uploadgps.php存在SQL注入

0x01 产品简介 科立讯通信指挥调度管理平台是一个专门针对通信行业的管理平台。该产品旨在提供高效的指挥调度和管理解决方案&#xff0c;以帮助通信运营商或相关机构实现更好的运营效率和服务质量。该平台提供强大的指挥调度功能&#xff0c;可以实时监控和管理通信网络设备、…

【Android面试八股文】请描述一下Service的生命周期是什么样的?

文章目录 一、Service的生命周期是什么样的?1.1 通过 `startService` 启动的 Service 生命周期:1.1.1 相关方法说明1.1.2 流程1.1.3 总结1.2 通过 bindService 启动的 Service 生命周期1.2.1 相关方法说明1.2.2 流程1.3 生命周期调用1.4 总结一、Service的生命周期是什么样的…

20240629在飞凌开发板OK3588-C上使用Rockchip原厂的SDK跑通I2C扩展GPIO芯片TCA6424ARGJRR

20240629在飞凌开发板OK3588-C上使用Rockchip原厂的SDK跑通I2C扩展GPIO芯片TCA6424ARGJRR 2024/6/29 18:02 1、替换DTS了&#xff1a; Z:\repo_RK3588_Buildroot20240508\kernel\arch\arm64\boot\dts\rockchip viewproviewpro-ThinkBook-16-G5-IRH:~/repo_RK3588_Buildroot2024…