C++:list类(迭代器类)

前言

list是链表的意思

它属于链表中的带头双向循环链表

建议先掌握数据结构中的链表

C数据结构:单链表-CSDN博客

C数据结构:双向链表(带头循环)_c带头双向循环链表-CSDN博客

数据结构

首先我们需要一个链表的节点

cpp 复制代码
template<class T>
struct ListNode
{
	T _data;
	ListNode* _next;
	ListNode* _prev;

	ListNode(const T& data = T())
		:_data(data)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};

因为这个ListNode类节点里面的内容可以是公开的,所以使用了struct默认即为public,当然也可以使用class来定义类,只需注意使用访问限定符即可

双向链表当然需要next和prev两个指针来指向后面和前面

类中还定义了一个默认构造函数用来构造节点

成员变量和成员函数放置的顺序前后都是可以的,但通常会把成员变量放到最下面

cpp 复制代码
template<class T>
class list
{
    typedef ListNode<T> Node;
public:

private:
	Node* _head;
	size_t _size = 0;
};

Node*指针指向链表头节点,我们还可以定义一个size成员函数,这样可以在计算链表大小的时候减少遍历节点次数

push_back

cpp 复制代码
void push_back(const T& data)
{
	Node* newnode = new Node(data);
	Node* tail = _head->prev;

	tail->_next = newnode;
	newnode->_next = _head;
	newnode->_prev = tail;
	_head->_prev = newnode;
	_size++;
}

生成新节点,将它插入到最后面即可

push_front

cpp 复制代码
void push_front(const T& data)
{
	Node* newnode = new Node;
	Node* next = _head->_next;

	_head->_next = newnode;
	newnode->_prev = _head;
	newnode->_next = next;
	next->_prev = newnode;

	insert(begin(), data);
}

生成新节点,插入到_head的下一个位置即可

默认构造函数

cpp 复制代码
list()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

构造出我们的哨兵位头节点,并初始化next和prev指针和size大小

因为这段默认构造函数在其他的成员函数内部也可能会使用,并且默认构造函数不方便显示调用,所以我们可以把这段代码用一个函数来封装起来,方便其他成员函数使用

cpp 复制代码
void EmptyInit()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}

list()
{
	EmptyInit();
}

这样我们把空初始化的逻辑放到EmptyInit函数中即可

拷贝构造函数

cpp 复制代码
list(const list<T>& lt)
	:_head(lt._head)
	,_size(lt._size)
{}

这是经典的错误写法!

这里只是将lt里面的_head按字节拷贝给了this的_head(浅拷贝)

所以最终是两个_head用同一个链表,同一块空间

为了避免这个问题,我们需要重新生成一段空间来完成深拷贝

cpp 复制代码
list(const list<T>& lt)
{
	EmptyInit();

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

复用EmptyInit和push_back的逻辑

因为EmptyInit会初始化出一个哨兵位头节点,我们只需要在这个头节点后面依次插入lt里面的元素即可,因为push_back也是会开新空间的

但是这里的遍历是还完不成的,因为范围for需要有begin,end,重载!=函数,这些函数又需要我们的迭代器,但是我们类中目前是还没有实现的,所以我们还需要先实现完成迭代器和这些成员函数才能完成这个拷贝构造

list迭代器

这里迭代器的实现就没有前面vector和string的那么简单了,因为它们两个的迭代器都可以通过指针的+-来遍历整个容器,但是list由于空间的不连续,所以不能简单的只是使用指针进行+-运算,而是需要我们手动完成对++,--这些运算符的重载操作

结构

cpp 复制代码
template<class T, class Ref, class Ptr>
struct list_iterator
{
	typedef ListNode<T> Node;
	typedef list_iterator<T, Ref, Ptr> Self;

	Node* _node;
};

这里需要有三个模板的支撑,前面的T代表数据类型,后面的Ref和Ptr代表的是引用和指针的意思

为什么要有这两个模板?

迭代器分为普通迭代器和const迭代器,稍后我们实现完普通迭代器后就会发现const迭代器我们需要重新拷贝一份普通迭代器的板子加上const,虽然能完成功能,但是这样代码的观赏性就不是很好,所以加上这两个模板后我们就可以在一个list_iterator类中完成iterator和const_iterator

具体讲解参考下文

构造函数

cpp 复制代码
list_iterator(Node* node)
	:_node(node)
{}

我们可以用一个node的节点来构造我们的list_iterator类

*运算符重载

cpp 复制代码
Ref operator*()
{
	return _node->_data;
}

当我们外面定义的迭代器是it = begin()时,我们*it返回的当然就需要是it里面的数据了,所以我们需要返回节点里的数据

->运算符重载

cpp 复制代码
Ptr operator->()
{
	return &_node->_data;
}

->也是需要返回数据,但这里我们为什么返回的是一个数据的地址?

当我们在外面调用 it->_变量 时,编译器其实调用的是it.operator->()->_变量

这里有两个箭头,第一个箭头就是调用了我们自己写的operator函数,而第二个就是把拿到的这个数据的地址解引用拿到了里面的数据

但是为了可读性,编译器会特殊处理掉这里的一个->,所以我们只需要一个->就可以完成解引用操作

前置++运算符重载

cpp 复制代码
Self& operator++()
{
	_node = _node->_next;

	return *this;
}

++后当然就是往链表里的下一个节点next走,所以我们在operator里面需要让当前iterator里面的成员变量_node指向它的next即可

后置++运算符重载

cpp 复制代码
Self operator++(int)
{
	Self tmp = *this;
	_node = _node->_next;

	return tmp;
}

和前置++一样

但是我们需要提前用一个tmp来保存原来的位置然后返回,这也是后置++自己的特点

前置--运算符重载

cpp 复制代码
Self& operator--()
{
	_node = _node->_prev;
	return *this;
}

--当然就是往前走,也就是使用prev指针往前跳

后置--运算符重载

cpp 复制代码
Self operator--(int)
{
	Self tmp = _node;
	_node = _node->_prev;

	return tmp;
}

和后置++一样的需要tmp保存并返回tmp

!=运算符重载

cpp 复制代码
bool operator!=(const Self& s) const
{
	return _node != s._node;
}

判断两个迭代器不相等的逻辑当然就是比较两个节点是否是同一个

==运算符重载

cpp 复制代码
bool operator==(const Self& s) const
{
	return _node == s._node;
}

和前面的!=运算符重载一样

这样就完成了

当我们在list类中定义迭代器iterator的时候我们是这样定义的

cpp 复制代码
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

这样就完美的完成了iterator和const_iterator,因为iterator和const_iterator的本质就是解引用逻辑后的数据能否改变的问题,所以我们把它们定义成了两个类模板,这样就可以只用一个list_iterator类来完成iterator和const_iterator

否则我们就需要定义两个类,list_iterator和const_list_iterator来分别完成iterator和const_iterator,这样代码的可读性就降低了

list迭代器完整代码

cpp 复制代码
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* node)
		:_node(node)
	{}

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

	Ptr 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 = _node;
		_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;
	}
};

begin和end

cpp 复制代码
iterator begin()
{
	//return iterator(_head->_next);
	return _head->_next;
}

iterator end()
{
	return _head;
}

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

const_iterator end() const
{
	return _head;
}

开始位置当然就是哨兵位头节点的下一个位置了

结束位置当然就是最后一个数据的下一个位置,也就是我们的哨兵位头节点的位置

这里明明返回值是iterator,但为什么我们可以返回一个Node*类型的指针?

C++的单参数构造函数是支持隐式类型转换的

所以我们不仅仅可以使用iterator(_head)这样的模式来构造一个iterator来返回

还可以直接返回_head来隐式类型转换成iterator类

因为这个类的构造函数就是单参数构造函数

swap

cpp 复制代码
void swap(list<T> lt)
{
	std::swap(lt._head, _head);
	std::swap(lt._size, _size);
}

赋值重载函数

cpp 复制代码
list<T>& operator=(list<T> lt)
{
	swap(lt);

	return *this;
}

和前面vector和string的逻辑相同,现代写法

insert

cpp 复制代码
iterator insert(iterator pos, const T& data)
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* newnode = new Node(data);

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

	++_size;
	return newnode;
}

只需要在pos节点位置的前面插入一个节点数据为data的即可,和数据结构中的insert一致

逻辑也很简单

完成了insert之后我们是可以直接复用insert来简单的实现push_back和push_front的

cpp 复制代码
void push_back(const T& data)
{
	insert(end(), data);
}

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

erase

cpp 复制代码
iterator erase(iterator pos)
{
	Node* del = pos._node;
	Node* prev = del->_prev;
	Node* next = del->_next;

	prev->_next = next;
	next->_prev = prev;
	
	--_size;
	delete del;
	return next;
}

erase的逻辑也是和数据结构中的erase一致,思路比较简单

要注意的就是注意释放掉删除的那块空间,防止内存泄漏

所以完成了erase,那么pop_back和pop_front就很容易了

迭代器失效问题

list的insert和erase也是存在迭代器失效的问题的,具体细节可以参考vector中的迭代器失效问题

C++:vector类(default关键字,迭代器失效)-CSDN博客

pop_back

cpp 复制代码
void pop_back()
{
	erase(--end());
}

pop_front

cpp 复制代码
void pop_front()
{
	erase(begin());
}

clear

cpp 复制代码
void clear()
{
	auto it = begin();
	while (it != end())
	{
		it = erase(it);
		// it++ 错误写法:erase后迭代器失效
	}
}

循环遍历整个链表,将除了哨兵位头节点的其他节点全部删除,删除哨兵位头节点的任务当然是交给析构函数了

只需要复用erase删除即可

析构函数

cpp 复制代码
~list()
{
	clear();
	delete _head;
	_head = nullptr;
	_size = 0;
}

首先复用clear将除了哨兵位头节点的全部节点删除

然后手动删除哨兵位头节点即可

size

cpp 复制代码
size_t size() const
{
	return _size;
}

empty

cpp 复制代码
bool empty() const
{
	return _size == 0;
}

完整代码

cpp 复制代码
#pragma once

#include<iostream>
using namespace std;

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

		ListNode(const T& data = T())
			:_data(data)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

	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* node)
			:_node(node)
		{}

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

		Ptr 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 = _node;
			_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 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);
			return _head->_next; // 单参数构造函数支持隐式类型转换
		}

		iterator end()
		{
			return _head;
		}

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

		const_iterator end() const
		{
			return _head;
		}

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

		list()
		{
			EmptyInit();
		}

		list(initializer_list<T> il)
		{
			EmptyInit();
			for (auto& x : il)
			{
				push_back(x);
			}
		}

		// 浅拷贝
		//list(const list<T>& lt)
		//	:_head(lt._head)
		//	,_size(lt._size)
		//{}
		
		list(const list<T>& lt)
		{
			EmptyInit();

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

		list<T>& operator=(list<T> lt)
		{
			swap(lt);

			return *this;
		}

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

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

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
				// it++ 错误写法:erase后迭代器失效
			}
		}

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

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

			++_size;
			return newnode;
		}

		iterator erase(iterator pos)
		{
			Node* del = pos._node;
			Node* prev = del->_prev;
			Node* next = del->_next;

			prev->_next = next;
			next->_prev = prev;
			
			--_size;
			delete del;
			return next;
		}

		void push_back(const T& data)
		{
			//Node* newnode = new Node(data);
			//Node* tail = _head->prev;

			//tail->_next = newnode;
			//newnode->_next = _head;
			//newnode->_prev = tail;
			//_head->_prev = newnode;
			//_size++;

			insert(end(), data);
		}

		void push_front(const T& data)
		{
			//Node* newnode = new Node;
			//Node* next = _head->_next;

			//_head->_next = newnode;
			//newnode->_prev = _head;
			//newnode->_next = next;
			//next->_prev = newnode;

			insert(begin(), data);
		}

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

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

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size = 0;
	};
}

相关推荐
吉大一菜鸡2 分钟前
FPGA学习(基于小梅哥Xilinx FPGA)学习笔记
笔记·学习·fpga开发
晓纪同学1 小时前
QT-简单视觉框架代码
开发语言·qt
威桑1 小时前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
飞飞-躺着更舒服1 小时前
【QT】实现电子飞行显示器(简易版)
开发语言·qt
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 16课题、并发编程
开发语言·青少年编程·并发编程·编程与数学·goweb
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 17课题、静态文件
开发语言·青少年编程·编程与数学·goweb
Java Fans1 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手1 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
Chinese Red Guest2 小时前
python
开发语言·python·pygame
一棵星2 小时前
Java模拟Mqtt客户端连接Mqtt Broker
java·开发语言