c++----list模拟实现

目录

[1. list的基本介绍](#1. list的基本介绍)

[2. list的基本使用](#2. list的基本使用)

[2.1 list的构造](#2.1 list的构造)

用法示例

[2.2 list迭代器](#2.2 list迭代器)

用法示例

[2.3. list容量(capacity)与访问(access)](#2.3. list容量(capacity)与访问(access))

用法示例

[2.4 list modifiers](#2.4 list modifiers)

用法示例

[2.5 list的迭代器失效](#2.5 list的迭代器失效)

3.list的模拟实现

[3.1 构造、析构](#3.1 构造、析构)

[3.2 迭代器](#3.2 迭代器)

[3.3 list modifiers](#3.3 list modifiers)

[4. list与vector的区别](#4. list与vector的区别)

5.完整代码


1. list的基本介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

  2. list的底层是双向带头循环链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。

  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝后迭代。

  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。

  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

下图是list的底层结构。

从图中可以看出,list底层由一个双向带头循环链表构成。假设有一链表list<int> lt, 头节点为_head则lt.begin()指向_head->next, lt.end()指向_head。

2. list的基本使用

2.1 list的构造

|-----------------------------------------------------------|-----------------------------|
| 构造函数( (constructor)) | 接口说明 |
| list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
| list() | 构造空的list |
| list (const list& x) | 拷贝构造函数 |
| list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |

用法示例

cpp 复制代码
void list_test1()
{
	list<string> lt(5, "xy");//n val构造
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<string> llt(lt);//拷贝构造
	for (auto e : llt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<string> lltt(++lt.begin(), --lt.end());//迭代器区间构造
	for (auto e : lltt)
	{
		cout << e << " ";
	}
}

2.2 list迭代器

|---------------|--------------------------------------------------------------------------|
| 函数声明 | 接口说明 |
| begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
| rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的 reverse_iterator,即begin位置 |

用法示例

cpp 复制代码
void list_test2()
{
	list<int> lt{ 1,2,3,4,5};//这样构造很奇特
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>::iterator it = lt.begin();//正向迭代器
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	list<int>::reverse_iterator  rit = lt.rbegin();//反向迭代器
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}

那么大家肯定会有一个疑问,之前的string,vector迭代器可以++,是因为他们的底层物理空间是连续的,但list底层可是双向带头循环链表啊,它的结构物理空间可是不连续的,我们++,不就错了吗?

不急,我们后面模拟实现的时候细讲。

2.3. list容量(capacity)与访问(access)

|-------|------------------------------|
| 函数声明 | 接口说明 |
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |

|-------|--------------------|
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |

用法示例

cpp 复制代码
void list_test3()
{
	list<int> lt{ 1,2,3,4,5 };//这样构造很奇特
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "链表是否为空:   ";
	if (lt.empty())
		cout << "true" << endl;
	else
		cout << "false" << endl;

	cout << "size: " << lt.size() << endl;

	cout << "Front Element: " << lt.front() << endl;

	cout << "Back Element: " << lt.back() << endl;

	lt.clear();
	cout << "已执行链表清空操作,链表是否为空:   ";
	if (lt.empty())
		cout << "true" << endl;
	else
		cout << "false" << endl;

}

2.4 list modifiers

|------------|------------------------------|
| 函数声明 | 接口说明 |
| push_front | 在list首元素前插入值为val的元素 |
| pop_front | 删除list中第一个元素 |
| push_back | 在list尾部插入值为val的元素 |
| pop_back | 删除list中最后一个元素 |
| insert | 在list position 位置中插入值为val的元素 |
| erase | 删除list position位置的元素 |
| swap | 交换两个list中的元素 |
| clear | 清空list中的有效元素 |

用法示例

cpp 复制代码
void list_test4()
{
	list<int> lt{ 1,2,3,4,5 };//这样构造很奇特
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.push_back(0);
	lt.push_back(0);
	lt.push_front(9);
	lt.push_front(9);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	lt.pop_back();
	lt.pop_front();
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;

	list<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		if (*it == 5)
		{
			it = lt.erase(it);
			break;
		}
			
		it++;
	}
	lt.insert(it, 999);
	for (auto e : lt)
	{
		cout << e << " ";
	}
	cout << endl;
}

2.5 list的迭代器失效

这里我们将迭代器理解为指针 ,list的迭代器失效是因为指针所指向的空间被销毁,导致指针变为野指针。list的底层是双向带头循环链表,因此list不存在插入导致迭代器失效 的问题。list的迭代器失效出现在erase内,如果该节点被删除,空间已被销毁,那么就必须更新迭代器,否则迭代器失效。

3.list的模拟实现

list的模拟实现存在几个难点,首先是使用了大量的typedef,这会让人头晕眼花,其次是模板迭代器的构建。我们快来看看吧。

注意:list的迭代器由于list底层空间不连续的问题,因此要实现和vector一样的迭代器效果(即支持*it,it++)需要对操作符进行重载,所以要封装Node*。

list需要创建一个类内类Node,用list访问Node类的变量。

下面是ListNode类,类内包含双链表节点的三要素。

cpp 复制代码
template<class T>
struct ListNode//节点的创建
{
	T _Date;
	ListNode<T>* _next;
	ListNode<T>* _prev;

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

3.1 构造、析构

这里的构造函数为简易版本,只支持创建空链表。

cpp 复制代码
void list_init()//初始化每一个节点
{
	_head = new Node;//new调用Node的默认构造,不传参
	_head->_next = _head;
	_head->_prev = _head;
}
list()
{
	list_init();
}
list(list<T>& lt)//拷贝构造
{
	list_init();//创建头节点

	for (const auto& e : lt)
	{
		push_back(e);
	}
}

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

//list& operator=(list lt)
list<T>& operator=(list<T> lt)
{
	swap(lt);
	return *this;
}
void clear()
{
	list<T>::iterator it = this->begin();
	while (it != end())
	{
		it = erase(it);
	}
}
~list()
{
	delete _head;
	_head = nullptr;
}

3.2 迭代器

前面说,要对++等操作符进行重载,这时需要重新定义iterator类封装节点的指针,通过指针对节点进行操作。且iterator类需要支持普通对象与const对象的访问,因此需要定义成模板类。

模板类iterator

cpp 复制代码
template<class T,class Ref,class Ptr>
struct _list_iterator//迭代器本质就是指针,这里封装Node*,对Node的指针进行操作,重载++等操作符
{
	typedef ListNode<T> Node;
	typedef _list_iterator<T,Ref,Ptr> self;
	//typedef _list_iterator<T> self;
	Node* _node;//成员变量

public:
	_list_iterator(Node* node)//构造函数,直接用node构建
		:_node(node)
	{}
	//没有参数int ,前置++
	self& operator++()//重载++
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)//后置++
	{
		Node* tmp = _node;
		_node = _node->_next;
		return tmp;
	}


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

	//返回临时对象,出函数即销毁,不可传引用
	self operator--(int)//后置--
	{
		Node* tmp = _node;
		_node = _node->_prev;
		return tmp;
	}
	Ptr operator->()
	{
		return &_node->_Date;
	}
	Ref operator*()
	{
		return _node->_Date;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

list类内的迭代器函数

cpp 复制代码
typedef _list_iterator<T,T&,T*> iterator; //普通对象的迭代器
typedef _list_iterator<T,const T&,const T*> const_iterator;  //const对象的迭代器
iterator begin()//指向头节点的下一个
{
	
	return _head->_next;
}

iterator end()//指向头节点
{
	return _head;
}

const_iterator begin() const//指向头节点的下一个
{

	return _head->_next;
}

const_iterator end() const//指向头节点
{
	return _head;
}

这里模板参数有三个,分别是T,T&,T*,这是为什么呢?

T&:因为重载普通迭代器的*时,返回值需要设为引用类型,以便于外界可以更改。

T*:重载->时,由于list的->逻辑不能自洽,list的重载->需要返回数据的地址
这里需要着重解释一下,如果是list<int>,那么重载->完全没有问题,但如果是list<Date>呢?

Node的数据域是自定义类型,又该怎么办呢?

因此我们返回Node.Date的地址,然后再解引用,如果是内置类型不受影响,如果是自定义类型,稍加控制就ok了。

我们来看看示例:

cpp 复制代码
struct AA
{
	int _a1;
	int _a2;

	AA(int a1 = 1, int a2 = 1)
		:_a1(a1)
		, _a2(a2)
	{}
};
void test_list5()
{
	list<AA> lt;
	AA aa1;
	lt.push_back(aa1);

	lt.push_back(AA());

	AA aa2(2, 2);
	lt.push_back(aa2);

	lt.push_back(AA(2, 2));

	list<AA>::iterator it = lt.begin();
	while (it != lt.end())
	{
		std::cout << (*it)._a1 << ":" << (*it)._a2 << std::endl;
		std::cout << it.operator*()._a1 << ":" << it.operator*()._a2 << std::endl;

		std::cout << it->_a1 << ":" << it->_a2 << std::endl;
		std::cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << std::endl;

		++it;
	}
	std::cout << std::endl;
}

Node的数据域存放的是AA类对象,Node* ptr,ptr->Date就是AA类的实例化对象,此时返回AA对象的地址,也就是AA* p,p->就可以访问AA类对象的成员了。

3.3 list modifiers

这里需要注意erase的迭代器失效问题。

cpp 复制代码
void push_back(const T& x=T())
{
	Node* node = new Node;
	Node* tail = _head->_prev;
	node->_Date = x;

	tail->_next = node;
	node->_next = _head;
	_head->_prev = node;
	node->_prev = tail;
}

iterator insert(iterator pos,const T& x = T())//不存在迭代器失效问题
{
	Node* node = new Node;
	node->_Date = x;
	Node* cur = pos._node;
	Node* prev = cur->_prev;

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

	return node;
}

iterator erase(iterator pos)//这里的pos有可能会产生迭代器失效问题
{
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;

	prev->_next = next;
	next->_prev = prev;
	delete cur;
	cur = nullptr;
	return next;//更新pos
}

4. list与vector的区别

list与vector都是c++STL序列容器的重要组成部分,由于底层结构的不同,造成了他们的功能和特性有所不同,详见下表。

|-----------|------------------------------------------------------------------------|---------------------------------------------|
| vector | list |
| 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素 效率O(N) |
| 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂 度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1) |
| 空 间 利 用 率 | 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 | 底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低, 缓存利用率低 |
| 迭 代 器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
| 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响 |
| 使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随 机访问 |

5.完整代码

cpp 复制代码
template<class T>
struct ListNode//节点的创建
{
	T _Date;
	ListNode<T>* _next;
	ListNode<T>* _prev;

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

template<class T,class Ref,class Ptr>
struct _list_iterator//迭代器本质就是指针,这里封装Node*,对Node的指针进行操作,重载++等操作符
{
	typedef ListNode<T> Node;
	typedef _list_iterator<T,Ref,Ptr> self;
	//typedef _list_iterator<T> self;
	Node* _node;//成员变量

public:
	_list_iterator(Node* node)//构造函数,直接用node构建
		:_node(node)
	{}
	//没有参数int ,前置++
	self& operator++()//重载++
	{
		_node = _node->_next;
		return *this;
	}

	self operator++(int)//后置++
	{
		Node* tmp = _node;
		_node = _node->_next;
		return tmp;
	}


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

	//返回临时对象,出函数即销毁,不可传引用
	self operator--(int)//后置--
	{
		Node* tmp = _node;
		_node = _node->_prev;
		return tmp;
	}
	Ptr operator->()
	{
		return &_node->_Date;
	}
	Ref operator*()
	{
		return _node->_Date;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

template<class T>
class list
{
	typedef ListNode<T> Node;  //typedef
public:
	typedef _list_iterator<T,T&,T*> iterator; //普通对象的迭代器
	typedef _list_iterator<T,const T&,const T*> const_iterator;  //const对象的迭代器
	iterator begin()//指向头节点的下一个
	{
		
		return _head->_next;
	}

	iterator end()//指向头节点
	{
		return _head;
	}

	const_iterator begin() const//指向头节点的下一个
	{

		return _head->_next;
	}

	const_iterator end() const//指向头节点
	{
		return _head;
	}
	void list_init()//初始化每一个节点
	{
		_head = new Node;//new调用Node的默认构造,不传参
		_head->_next = _head;
		_head->_prev = _head;
	}
	list()
	{
		list_init();
	}
	list(list<T>& lt)//拷贝构造
	{
		list_init();//创建头节点

		for (const auto& e : lt)
		{
			push_back(e);
		}
	}

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

	//list& operator=(list lt)
	list<T>& operator=(list<T> lt)
	{
		swap(lt);
		return *this;
	}
	void clear()
	{
		list<T>::iterator it = this->begin();
		while (it != end())
		{
			it = erase(it);
		}
	}
	~list()
	{
		delete _head;
		_head = nullptr;
	}
	void push_back(const T& x=T())
	{
		Node* node = new Node;
		Node* tail = _head->_prev;
		node->_Date = x;

		tail->_next = node;
		node->_next = _head;
		_head->_prev = node;
		node->_prev = tail;
	}

	iterator insert(iterator pos,const T& x = T())//不存在迭代器失效问题
	{
		Node* node = new Node;
		node->_Date = x;
		Node* cur = pos._node;
		Node* prev = cur->_prev;

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

		return node;
	}

	iterator erase(iterator pos)//这里的pos有可能会产生迭代器失效问题
	{
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

		prev->_next = next;
		next->_prev = prev;
		delete cur;
		cur = nullptr;
		return next;//更新pos
	}

	size_t size()const//返回size
	{
		int cnt = 0;
		list<int>::const_iterator it = begin();
		while (it != end())
		{
			cnt++;
			it++;
		};
		return cnt;
	}
	bool empty()const//判空
	{
		return _head ->_next== _head;
	}


private:
	Node* _head;//成员变量为ListNode* 即访问节点的指针
};
相关推荐
瞌睡不来42 分钟前
(刷题记录5)盛最多水的容器
c++·笔记·学习·题目记录
凯子坚持 c43 分钟前
C语言复习概要(四)
c语言·开发语言
何陈陈1 小时前
【Linux】线程池
linux·服务器·开发语言·c++
清风玉骨1 小时前
Qt-QHBoxLayout布局类控件(42)
开发语言·qt
夏旭泽1 小时前
C-include
开发语言·c++
通信仿真实验室1 小时前
MATLAB使用眼图分析QPSK通信系统接收端匹配滤波后的信号
开发语言·算法·matlab
感谢地心引力1 小时前
【Qt】Qt安装(2024-10,QT6.7.3,Windows,Qt Creator 、Visual Studio、Pycharm 示例)
c++·windows·python·qt·visual studio
通信仿真实验室2 小时前
(15)衰落信道模型作用于信号是相乘还是卷积
开发语言·人工智能·算法·matlab
远望樱花兔2 小时前
【d59】【Java】【力扣】146.LRU缓存
java·开发语言·算法
Bruce_Liuxiaowei2 小时前
Python小示例——质地不均匀的硬币概率统计
开发语言·python·概率统计