【C++】--list的使用和模拟实现

一、list的介绍和使用

1、认识list

List和vector一样也是C++标准模板库中的一个容器,其中文名就是我们前面数据结构学习的链表,那么其和我们前面学习的string类和vector类最大的不同就是其物理结构是不连续的,其是一个一个的节点构成的,然后list是一个双向带头循环链表:

上面就是list的简单图示了,可以看到其一个结点4主要是三个部分,一个是存储的数据,然后一个是指向后一个节点,一个是指向前一个节点,所以我们也说在逻辑上我们的list是连续的。

下面我们来看看其使用

2、list的使用

(1)list对象的实例化

其语法和vector的一样:list<类型>+对象名。

其实数据类型可以是内置类型也可以是自定义类型

(2)list对象的初始化

这个是库中的构造函数

第一个就是默认构造一个空的list,然后其有一个默认值。

第二个就是给构造的list对象构造为n给第二个参数的值。

第三个构造就是赋值运算符重载构造

第四个构造就是利用我们的迭代器的方式进行构造

第五个是拷贝构造了

可以看到list不仅支持迭代器,还支持其他类型数据的迭代器进行初始化

那么说完数据的初始化,我们的list主要使用:增删改查这四种功能

(3)尾插元素

其尾插函数的使用和前面学习的string和vector的尾插是一样的,也是使用push_back函数进行尾插:

如上,这是其库中的原型。

(4)尾删

我们的list的尾删函数和前面学习的vector和string类也是一样的名称:

(5)获取list中的元素

前面我们学习string和vector容器的时候,因为这两个容器的物理结构都是连续的,那么其就支持[]访问元素的方式,那么我们的list的结构是不连续的,所以就没有支持这种方式进行访问元素,我们前面学习数据结构的时候,我们对于元素的访问都是通过指向下一个结点的指针进行访问。

那么我们在list中是否也只能这样进行数据的访问呢?

其实不然,我们的C++容器,大部分都支持使用迭代器的方式进行数据的访问,所以list中其也支持这个方式:

那么我们就可以使用++的方式对list的元素进行访问了。

那么支持迭代器就支持范围for:

那么就有同学会很疑惑,lits的空间都不是连续的,那么其怎么通过++就可以找到下一个结点的位置的呢?

这个我们后面模拟实现的时候会进行讲解。

(6)指定结点前插入

这个功能在vector和string类中也都有提供

(7)删除指定位置的元素

我们的list中也提供了指定位置删除的操作:

可以看到其还支持迭代器的方式进行数据的删除。

然后就是对于我们要连续对数据进行删除,然后使用迭代器的问题,前面我们学习vector类的时候讲过迭代器失效的问题,如下面这种情况就会出现:

如果是上面的情况,没有使用接收值来接收返回值,那么就会造成迭代器失效,所以当我们要使用迭代器进行连续的删除的时候,为了避免迭代器失效的问题,我们要使用一个来接收其返回值。

(8)排序sort

在我们的库中还提供了排序的接口,我们可以使用其对数据进行排序

然后我们对于list的排序的话,在数据量少的时候没啥影响,但是当数据很多的时候,其效率就不是很高了,虽然其时间复杂度是O(n log n)。

上面这个是升序的排法,然后我们的sort还支持降序的排序:

(9)链接

这个的话就是将两个原本独立的list拼在一起成为一个新的list,不过其支持很多种情况。

第一个就是两个list进行拼接,然后就是单个元素进行拼接,指定范围拼接等

下面我演示一下整体拼接和单个元素拼接:

(10)去重

要注意的是去重的话要注意我们的链表是有序的:

(11)逆置

这个接口我们在string和vector的时候也遇到了:

注意:

我们前面已经提到了,我们的list链表其在逻辑上是连续的,但是其实际物理结构上不是连续的,所以我们在使用上是不可以通过+n的方式寻找元素。

二、list的模拟实现

(1)整体实现结构

首先我们知道的是我们的库中的list是双向循环带头链表,然后其是由一个一个的结点组成的,然后我们的结点里是由一个存储数据,一个指向下一个结点,一个指向前一个结点的指针,一共三个成员组成的。

然后和string和vector类的实现一样,为了避免和库中的起冲突,我们将其放置在一个命名空间中。

这个时候就会有同学会发出疑问了,为啥我们这个时候去使用struct来定义list类结点,这是因为我们的链表三个成员都要经常被进行访问,然后要是我们使用class进行定义,然后其被私有,那么我们就还是要将其友元化,那么我们不如直接将其公开即可。

然后我们的list是带头的链表,那么我们还需要一个头结点

那么上面就是我们整个list的初始实现的结构。

(2)尾插

尾插就是在链表的尾部插入一个结点,那么我们就要先找到当前链表的尾结点,那么我们头结点的前一个结点就是当前链表的尾结点,那么我们就可以进行插入了,不过要注意的是我们要先将插入前的尾结点的地址记录下来,然后再进行修改头结点指向前一个结点的指针,将其指向我们要插入的结点,然后我们插入的新的结点的指向下一个结点的指针指向头结点,然后原来的尾部结点的指向下一个结点的指针指向新插入的结点。

(3)尾删

尾删很简单的,就找到尾部结点然后删除即可,但是也要注意的是,当我们这个list只有头结点的时候是不可以进行删除操作的。

(4)构造函数

首先就是我们普通结点的构造:

然后我们的头结点部分也要写一下其构造函数:

(5)运算符重载

这个我们是实现的迭代器上的。

1、解引用

解引用就是返回这个位置存储的数据即可:

2、->运算符重载

其写法如下:

3、前置++

这个就是将我们的迭代器移动到下一个结点,那么我们就可以使用++的方式进行数据的遍历和访问了:

4、后置++

后置++的话,其返回的还是当前的位置,但是使用完后,指针的要指向下一个结点的位置,那么我们可以使用一个临时变量存储当前的位置,然后再将指针 往下一个结点移动,然后返回记录好的原来的结点的位置:

这里我们会发现我们此时没有使用引用返回,这是因为我们的tmp是函数中创建的一个临时变量,那么出了这个函数后,这个变量就会被销毁了,那么就会造成野引用的问题。

5、前置--

我们的list库中,其是一个双向的迭代器,那么其就允许向前--的操作:

6、后置--

这个和前面的后置++一个道理:

7、==

这个就很简单了:

8、!=

(6)const迭代器

我们上面对于迭代器的内容的实现都是对于普通的变量进行的,那么对于const对象,其和普通对象的迭代器的区别就是,要求其指向的内容不可以被进行修改,而迭代器本身是可以进行修改的。

所以我们有的同学一开始会想到使用const iterationor的方式来表示const迭代器,这个const是修饰迭代器本身的。所以我们针对这个情况,我们可以单独实现一个const迭代器的类。

我们会发现,除了解引用和->的部分,其他的代码和普通迭代器没有区别。

这样我们的代码的复用率就比较低了,所以我们考虑另外一种方式来实现。

我们采用模板来进行优化,提高代码的复用率:

由于普通迭代器和const的迭代器其实际上只是返回类型不同,那么我们不妨增加两个模板参数

(7)迭代器begin和end

这两个就很简单了,就是其有两个重载,一个是普通的迭代器,一个是const对象的迭代器:

(8)链表的其他操作

1、insert

这个函数的作用是在pos的位置之前插入一个结点:

2、push_back

这个函数我们上面是按照尾插的逻辑写的,然后我们也可以和下面这种方式写:

3、push_front

这个函数是使用头插的,那么我们和上面的push_back一样复用insert即可:

4、erase

这个函数是删除指定位置的结点:

上面是按照arase的逻辑来实现的,不过我们前面学习vector提到的迭代器失效问题,其主要是出现在删除数据移动数据和扩容上,我们这边删除数据,那么就会使得原来的迭代器变成了一个野指针,那么后面我们进行访问数据的操作的时候 ,编译器就会报错。

所以我们这边对于areas的话,我们对其弄一个返回值,然后对原来的迭代器进行赋值,那么就不会出现这个问题了。

5、swap

这个函数的作用是交换两个链表,然后我们只需要将链表中的成员变量进行交换即可,我们在函数中调用库中的swap函数对其一个一个进行交换即可:

(9)容量

我们在设计链表的时候可以添加一个成员变量,用来记录结点个数,所以我们这个函数直接返回这个成员变量即可:

(10)clear

这个函数的作用是用来直接清空链表的结点的:

(11)拷贝构造

我们的拷贝构造主要是两个,一个是普通的拷贝构造,然后还有一个就是赋值运算符拷贝:

(12)析构函数

这个我们可以先清空链表,然后将头结点释放:

三、完整代码

复制代码
#pragma once
#include<assert.h>
using namespace std;
namespace cyy 
{
	//链表结点
   template<class T>
   struct list_node
   {
	   list_node(const T &x= T())
		   :_next(nullptr)
		   ,_prev(nullptr)
		   ,_date(x)
	   {
	   }

	   //节点成员
	   list_node<T> _date;
	   list_node<T>* _next;
	   list_node<T>* _prev;
   };

   //迭代器
   template<class T, class Ref, class Ptr >
   struct _list_iterator
   {
	   typedef list_node<T> Node;
	   typedef list_iterator<T,Ref,Ptr> Self;
	   Node* _node;

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

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

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

	   bool operator!=(const Self& s)const
	   {
		   return _node != s._node;
	   }
   };
	   //const迭代器
 //template<class T>
	//struct __list_const_iterator
	//{
	//	typedef list_node<T> Node;
	//	Node* _node;

	//	__list_const_iterator(Node* node)
	//		:_node(node)
	//	{}

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

	//	__list_const_iterator<T>& operator++()
	//	{
	//		_node = _node->_next;
	//		return *this;
	//	}

	//	__list_const_iterator<T> operator++(int)
	//	{
	//		__list_iterator<T> tmp(*this);
	//		_node = _node->_next;
	//		return tmp;
	//	}

	//	__list_const_iterator<T>& operator--()
	//	{
	//		_node = _node->_prev;
	//		return *this;
	//	}

	//	__list_const_iterator<T> operator--(int)
	//	{
	//		__list_const_iterator<T> tmp(*this);
	//		_node = _node->_prev;
	//		return tmp;
	//	}

	//	bool operator!=(const __list_const_iterator<T>& it) const
	//	{
	//		return _node != it._node;
	//	}

	//	bool operator==(const __list_const_iterator<T>& it) const
	//	{
	//		return _node == it._node;
	//	}
	//};
   //链表
   template<class T>
   class list
   {
	   typedef list_node<T>	Node;
	   
   public:
	   typedef _list_iterator<T, T&, T*> iterator;
	   typedef _list_iterator<T, const T&, const T*> const_iterator;
	   list()
		   :_head(nullptr) 
	   {
		   Head = new Node;
		   Head->_next = _head;
		   Head->_prev = _head;
	    }
	   void empty_init()
	   {
		   _head = new Node;
		   _head->_next = _head;
		   _head->_prev = _head;
	   }

	   list(const list<T>& it) 
	   {
		   empty_init();

		   for (const auto& e : it)
		   {
			   push_back(e);
		   }
	   }
	   list<T>& operator=(list<T>& it) 
	   {
		   swap(it);
		   return *this;
	   }

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

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

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

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


 	  

	   void pop_back() 
	   {
		   assert(_head->next != Head);

		   Node* pp = _head->_prev;
		   Node* pur = pp->_prev;
		
		   head->_prev = pur;
		   pur->_next = _head;

		   delete pp;

	   }

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

		   ++_size;
	   }

	   // void push_back(const T& _date) 
	   //{
		  // //开辟结点空间
		  // Node* tmp = new Node(_date);
		  // Node* pp = _head->_prev;

		  // tmp->_date = _date;
		  // tmp->_next = _head;
		  // tmp->_prev = pp;
		  // pp->_next = tmp; 
		  // _head->_prev = tmp;

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

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

	   iterator erase(iterator pos, const T& x)
	   {
		   assert(pos != end());//方式删了哨兵位头结点

		   Node* cur = pos._node;
		   Node* next = cur->_next;
		   Node* prev = cur->_prev;

		   prev->_next = next;
		   next->_prev = prev;

		   delete cur;
		   --_size;

		   return iterator(next);

	   }

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

	   size_t size()
	   {
		   return _size;
	   }

	   void clear()
	   {
		   auto it = begin();
		   while (it != end())
		   {
			   it = erase(it);
		   }
		   _size = 0;
	   }

   private:
	   Node* _head;
	   size_t _size = 0;
   };
}
相关推荐
程序员大雄学编程4 小时前
「用Python来学微积分」18. 微分
开发语言·python·数学·微积分
十五年专注C++开发4 小时前
qtmqtt: 一个开源且好用的mqtt开源客户端
c++·qt·mqtt·开源
我命由我123454 小时前
PDFBox - PDF 页面坐标系、PDF 页面尺寸获取、PDF 页面位置计算
java·服务器·开发语言·笔记·后端·java-ee·pdf
小苏兮4 小时前
【数据结构】二叉搜索树
开发语言·数据结构·c++·学习·1024程序员节
腾昵猫4 小时前
程序员的自我修养(三)
c++
ᐇ9594 小时前
Java 程序运行原理与内存模型解析
java·开发语言
晨曦(zxr_0102)4 小时前
CSP-X 2024 复赛编程题全解(B4104+B4105+B4106+B4107)
数据结构·c++·算法
ai安歌4 小时前
【Rust编程:从新手到大师】 Rust 控制流深度详解
开发语言·算法·rust
·白小白4 小时前
力扣(LeetCode) ——15.三数之和(C++)
c++·算法·leetcode