list模拟实现

目录

一、list节点

二、接口

1、insert

2、erase

3、头插/删、尾插/删

4、clear

三、默认成员函数

1、构造函数

2、拷贝构造

3、赋值重载

4、析构

四、迭代器

1、const迭代器和非const迭代器

2、迭代器中的运算符重载

3、++和--的重载

4、其它运算符重载


一、list节点

list作为一个链表,每个节点中都存储着前一个节点、后一个节点的位置和存储元素。

将list类中的成员变量设置为表示节点的类,再将前后指针以及节点存储的对象设置为节点类的成员变量。这有利于增强代码的健壮性和可读性。

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

template<class T>
class list
{
    typedef ListNode Node;
    private:
    Node* _head;//哨兵位
};

二、接口

1、insert

我们目前只看第一个模板,在指定位置插入元素。

复制代码
void insert(iterator pos, const T& val)
{
	Node* sur = pos._node;
	Node* tmp = new Node(val);
	tmp->_next = sur;
	tmp->_prev = sur->_prev;
	sur->_prev->_next = tmp;
	sur->_prev = tmp;
	_size++;
}

2、erase

同样我们只看第一个模板,删除指定位置的节点。

复制代码
		iterator erase(iterator pos)
		{
			Node* tmp = pos._node;
			Node* p = pos._node->_next;
			tmp->_prev->_next= tmp->_next;
			tmp->_next->_prev = tmp->_prev;
			delete tmp;
			tmp = nullptr;
			_size--;
			return p;
		}

3、头插/删、尾插/删

实现这些插入删除操作我们可以复用之前的insert和erase。

复制代码
		void push_back(const T&x)
		{
			insert(end(), x);
			_size++;
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
			_size++;
		}

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

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

4、clear

list的clear与vector的不一样,list的clear需要将list的内部的元素全部销毁。

复制代码
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it=erase(it);
			}
			_size = 0;
		}

三、默认成员函数

1、构造函数

初始化的时候将节点的前后指针都指向自己。

复制代码
		list()
		{
			_head = new Node;//别忘了new会调用构造函数
			//_node->_data = T();
			_head->_next = _head->_prev = _head;
		}

2、拷贝构造

将被拷贝对象内部元素一个个的push_back过去。

复制代码
		list(const list<T>&x)
		{
           //先初始化一下
			_head = new Node;
			_head->_next = _head->_prev = _head;

			for (const auto& e : x)//范围for里的e是元素值,不是迭代器!
			{
				push_back(e);
			}
		}

3、赋值重载

赋值重载需要注意不要自己给自己赋值。

先将利用被赋值对象创建一个新类,再将新类与赋值对象进行自定义的swap交换。

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

		// 赋值运算符:先拷贝 x 得到临时对象,再与临时对象交换
		list<T>& operator=(const list<T>& x) 
		{  
			if (this != &x) 
			{  
				list<T> tmp(x);  
				swap(tmp);       
			}                   
			_size = x._size;
			return *this;
		}

4、析构

注意销毁哨兵位。

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

四、迭代器

list中的迭代器可能会参与一些运算,这就需要进行运算符重载,所以迭代器和节点一样封装为类。

1、const迭代器和非const迭代器

在const迭代器中,需要迭代器能够实现遍历但又要求指向内容不能够修改。

在之前实现的vector中,迭代器都是使用指针来实现的,而在指针左边进行const修饰时,指针本身可以修改但是指向内容不能修改这一要求已经满足。

但是由于list使用类来封装迭代器就导致经const修饰后迭代器本身不能被修改,这样也就无法进行遍历。

这时候就需要额外实现const迭代器。

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

在上面我们可以看到模板中有三个参数,后面两个参数是用来规范const迭代器中运算符重载的,下面继续说明。

2、迭代器中的运算符重载

先提前说明一下模板参Re数解决const迭代器问题;Ptr 表示list存储类型的模板。

下面看一段代码:

复制代码
		T& operator*()
		{
			return _node->_data;
		}
		Re operator*()const
		{
			return _node->_data;
		}

返回类型为T&的为非const * 运算符重载,返回值为Re的为const * 运算符重载。

再看下面这段代码:

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

->运算符重载后得到的返回类型与list存储类型是否为const类型相关。

现在再来看这两段代码:

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

template<class T,class Re ,class Ptr>
struct _list_iterator
{
	typedef ListNode<T> Node;
	Node* _node;//成员变量
	_list_iterator(Node*node)
		:_node(node)
	{ }

	T& operator*()
	{
		return _node->_data;
	}
	Re operator*()const
	{
		return _node->_data;
	}

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

};

T、Re、Ptr这三个模板参数巧妙的实现const和非const迭代器。

3、++和--的重载

++和--的返回值都是返回迭代器类型,所以我们还需要给迭代器类型起个好用的别名。

复制代码
		typedef _list_iterator<T, Re ,Ptr> MySelf;

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

		MySelf& operator++(int)//后置++
		{
			MySelf old(_node);//拷贝构造        
			_node = _node->_next;
			return old;
		}

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

		MySelf& operator--(int)
		{
			MySelf old(_node);
			_node = _node->_prev;
			return old;
		}

4、其它运算符重载

复制代码
		bool operator!=(const MySelf& it)const
		{
			return _node != it._node;
			//_node是自定义类型,判断是否相等需要看其地址是否相等
			//ListNode结构体也不用额外是实现运算符重载
		}

		bool operator==(const MySelf& it)const
		{
			return _node == it._node;
		}
相关推荐
春花秋月夏海冬雪6 小时前
代码随想录刷题Day47
c++·平衡二叉树的构建·二叉树中序遍历·代码随想录刷题
雨中散步撒哈拉7 小时前
12、做中学 | 初一上期 Golang函数 包 异常
开发语言·后端·golang
柯南二号7 小时前
【Java后端】Spring Boot 全局域名替换
java·开发语言·spring boot
Swift社区7 小时前
Java 常见异常系列:NumberFormatException 数字格式异常
java·开发语言
浅浅一笑^*^8 小时前
ArcGIS 4.x 绘图
开发语言·arcgis·swift
深耕AI8 小时前
【MFC应用创建后核心文件详解】项目名.cpp、项目名.h、项目名Dlg.cpp 和 项目名Dlg.h 的区别与作用
c++·mfc
甄天8 小时前
VisionProC#联合编程相机实战开发
开发语言·数码相机·c#·机器视觉
风和先行8 小时前
MFC应用防止多开
c++·mfc
hansang_IR8 小时前
【题解 | 两种做法】洛谷 P4208 [JSOI2008] 最小生成树计数 [矩阵树/枚举]
c++·算法·dfs·题解·枚举·最小生成树·矩阵树定理