list的模拟实现

一、list的简单介绍

学习过数据结构中的带头双向循环链表的铁子,对于理解list应该不难,list就是一个带头双向循环的链表

image-20241104151042709

list和vector、string的使用是差不多的,都可以使用迭代器,尾插,尾删这些,但是list不支持[],也就是随机访问,这也好理解,list的空间并不是连续的。

二、list模拟实现的三个类

1、节点类

list是由一个一个节点连接起来的,一个节点包括一个数据段,一个前驱指针和一个后驱指针。(注意链表中叫节点,结点都是可以的,随你喜欢)

image-20241104151611970

template <class T>
struct list_node
{
   
   
	T _data;            // 三个成员变量
	list_node<T>* _next;
	list_node<T>* _prev;

	list_node(const T& x = T()) 
    // 节点的构造,直接给值就行,这里给的是缺省函数,匿名对象,另外两个指针给空就行
		:_data(x)
		,_next(nullptr)
		,_prev(nullptr)
	{
   
   }
};

2、迭代器类

我们知道迭代器分为普通迭代器和const迭代器,我们在模拟实现string和vector的使用,为什么没有实现迭代器类呢?

这是因为string和vector的空间是连续的,它们的迭代器是原生指针,本身就可以进行++,–,解引用操作。

但是list的空间并不是连续的,要访问下一个节点,需要使用node = node -> _next,其他操作类似,所以list的迭代器没法像vector迭代器那样直接使用,所以需要我们自己封装一下迭代器,这里就会使用到各种赋值运算符重载,可见赋值运算符重载的地位举足轻重。

下面的迭代器类是普通迭代器的:

template<class T>
struct __list_iterator
{
   
   
	typedef list_node<T> Node;
	typedef __list_iterator<T> self;     // 这里简化写法,我们使用typedef
	Node* _node;        

	__list_iterator(Node* node)   // 迭代器只需要一个节点指针来构造即可
		:_node(node)
	{
   
   }
	self& operator++();
	self& operator--();
	self operator++(int);
	self& operator+(size_t x);
	self operator--(int);
	T& operator*();
	T* operator->();
	bool operator!=(const self& s);
	bool operator==(const self& s);
};

3、链表类

template <class T>
class list
{
   
   
	typedef list_node<T> Node;
public:
	typedef __list_iterator<T> iterator;

	// 迭代器相关函数
	iterator begin();
	iterator end();

	// 默认成员函数
	void empty_init();
	list()
	{
   
   
		empty_init()}
	list(list<T>& lt)       // 拷贝构造函数
	~list();          // 析构函数会将整个链表清理干净,包括头节点

	// 其他函数
	void swap(list<T>& lt);
	list<T>& operator=(list<T>& lt);
	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);   // 因为执行了delete删除该节点,所以当前迭代器失效了,需要有返回值
	size_t size(); 
	void clear();                 // 除了头节点全部释放
private:
	Node* _head;                  // 哨兵位的头节点
	size_t _size;                 // 记录节点的个数
};

三、迭代器类的模拟实现

1、普通迭代器类

1.1、operator++()和operator++(int)

为了区分前置++和后置++,所以在参数的位置加上一个int来区分,operator++()是前置++,而operator++(int)是后置++,前置++是先++后使用,而后置++是先试用后++

operator++():前置++

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

operator++(int):后置++

self operator++(int)   
{
   
   
	self tmp(*this);        // 先使用临时变量拷贝下来
	_node = _node->_next;   // 再到下一个节点
	return tmp;             // 最后再返回该临时变量
}

注意这里的返回值不能使用&返回,因为tmp是一个临时变量,函数结束就释放掉了。

1.2、operator–()和operator–(int)

后置–和前置–其实和前置的是一样的,只不过使用_prev而已。

self& operator--()      // 前置--
{
   
   
	_node = _node->_prev;
	return *this;
}
self operator--(int)    // 后置--
{
   
   
	self tmp(*this);
	_node = _node->_prev;
	return tmp;
}
1.3、operator*()

*是解引用的符号,也就是访问节点的数据段。

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

->是得到节点数据段位置的地址。

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

这里重载->是有一点讲究的。举个例子,日期类的实现来说:

class Date
{
   
   
public:
	int _year;
	int _month;
	int _day;
	Date(int year = 0, int month = 0, int day = 0)
		:_year(year)
		,_month(month)
		,_day(day)
	{
   
   }
};
int main(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值