C++ STL list

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++之 STL list介绍和模拟实现
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:上次我们详细的介绍了vector,今天我们继续来介绍一下TSTL中的另外一个容器list。list在基础的功能和结构上就是一个双向带头的循环链表,实现起来基本不难,但是list迭代器的封装是非常值得学习的。

一.认识list

list - C++ Reference

  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的使用非常简单,有了我们之前vector和string的基础。上手list基本就是小菜一碟。

1.构造函数

构造函数( (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

2.增删查改

	list<int> li;//尾插li.push_back(10);//头插li.push_front(20);//尾删li.pop_back();//头删li.pop_front();//迭代器位置插入数据li.insert(li.begin(), 100);//删除迭代器位置数据li.erase(li.begin());

3.list 迭代器

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

list的迭代器使用与string和vector一模一样,在此不多介绍。

四.list 模拟实现

1.链表结点

template<class T>
struct _list_node
{_list_node( const T& val = T()):_val(val),_next(nullptr),_prev(nullptr){}T _val;           //存储数据_list_node* _next;//后一个结点的指针_list_node* _prev;//前一个结点的指针
};

注意:我们在此处的struct定义了一个类,我们在定义_next和_prev的时候就不用像C语言那样加上struct。

例如:C语言写法

template<class T>
struct _list_node
{_list_node(const T& val = T()):_val(val),_next(nullptr),_prev(nullptr){}T _val;           //存储数据struct _list_node* _next;//后一个结点的指针struct _list_node* _prev;//前一个结点的指针
};

2.list整体结构

template<class T>
class List
{
public:typedef _list_node<T> node;//...成员方法 增删查改private:node* _head;//头节点
};

3.list构造函数

因为我们的list是一个带哨兵位的双向循环链表。所以这里我们将new出来的结点,设置为循环双向结构。

	List():_head(new node){_head->_next = _head;_head->_prev = _head;}

4.push_back(),pop_back()

push_back()尾插一个数据,pop_back()尾删一个数据。

	//push_backvoid push_back(const T& val ){//创建结点node* newnode = new node(val);node* tail = _head->_prev;//改变指向tail->_next = newnode;newnode->_next = _head;_head->_prev = newnode;newnode->_prev = tail;}
	//pop_backvoid pop_back(){//判空assert(!empty());node* tail = _head->_prev;node* newtail = tail->_prev;//改变指向_head->_next = newtail;newtail->_prev = _head;//释放结点delete tail;}

5.迭代器

list的迭代器不同于string和vector,因为list的物理存储是不连续的,所以我们不能像list和vector一样仅仅使用原生指针来作为list的迭代器。

迭代器的实际就是一种模仿指针的行为来为容器提供一种通用的访问方法的设计。虽然list的迭代器不能使用原生指针来替代,但是我们可以对原生的指针进行类级别的封装来构造迭代器,并且使得迭代器具有指针的行为。

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1.   原生态指针,比如:vector
  2.   将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
  • 指针可以解引用,迭代器的类中必须重载operator*()
  • 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  • 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
  • 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
  • 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

 5.1迭代器结构

迭代器的整体结构如下。

template<class T>
class _list_iterator
{
public:typedef _list_node<T> node;    //结点类typedef _list_iterator<T> self;//迭代器本身//使用原生指针构造迭代器_list_iterator(node* n):_node(n){}//operator*()//operator++()//operator--()//operator->()//operator==()//operator!=()private:node* _node;//迭代器是对原生指针的封装
};

5.2迭代器操作

5.2.1operator*()

    T& operator*(){return _node->_val;}

返回该结点的值的引用。

5.2.2 operator ++(),operator --()

++的操作其实就是让迭代器挪到下一个位置,--操作其实就是让迭代器挪到上一个位置。迭代器++或者--操作的返回值还是一个迭代器。

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

5.2.3operator==(),operator!=()

比较两个迭代器是否相等直接比较迭代器封装的两个指针是否相等就可以了。

	bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}

5.2.4operator->()

我们知道一般的结构体,类指针都是支持使用->访问一些成员的。当list存储的数据是结构体,或者是类试迭代器也应该支持->去访问。

    T*  operator->(){return &_node->_val;}

那么这样我们在外边的访问会不会很奇怪呢?

struct AA{AA(int aa = 10):_aa(aa){}int _aa;};list<AA> lli(10, AA());	list<AA>::iterator lit = lli.begin();//1.调用:lit.operator->()->_aa;//2.调用:lit->_aa;

 在实际调用的时候我们可以直接写成调用2。

5.2.5begin(),end()

	typedef _list_iterator<T> iterator;iterator begin(){//使用哨兵位的下一个结点指针,构造beginreturn iterator(_head->_next);}iterator end(){//使用哨兵位结点指针,构造endreturn iterator(_head);}

可以构成 [ begin,end ).这种左闭右开的结构。

5.3 const 迭代器

const迭代器与普通迭代器最大的不同,就是const迭代器不允许修改迭代器指向的数据。在整个迭代器操作中只有,operator* 和 operator->是对指向数据操作的。我们仅需要将普通迭代器的这两个函数返回值修改即可。并且区分开普通迭代器与const迭代器。

template<class T>
class const_list_iterator
{
public:typedef _list_node<T> node;typedef const_list_iterator self;const_list_iterator(node* n):_node(n){}//返回值加const,不允许修改const T& operator*(){return _node->_val;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self operator++(){_node = _node->_next;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}self operator--(){_node = _node->_prev;return *this;}bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}//返回值加const,不允许修改const T*  operator->(){return &_node->_val;}private:node* _node;
};

但是这种设计让人觉得很不舒服,代码的重复太多。从代码的角度来看,const和普通迭代器仅仅只是返回值不同而已。我们只要给在给迭代器不同的类型,我们只需要给模板多架两个参数,如果是 T& 和 T*就实例化出普通迭代器,如果是 const T& 和 const T*就实例化出const迭代器。

list类内部:

template<class T>
class List
{
public:typedef _list_node<T> node;typedef _list_iterator<T,T&,T*> iterator;//普通迭代器typedef _list_iterator<T,const T&,const T*> const_iterator;//const迭代器List():_head(new node){_head->_next = _head;_head->_prev = _head;}iterator begin(){//使用哨兵位的下一个结点指针,构造beginreturn iterator(_head->_next);}iterator end(){//使用哨兵位结点指针,构造endreturn iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}private:node* _head;
};

迭代器:

//T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
template<class T,class Ref,class Ptr>
class _list_iterator
{
public:typedef _list_node<T> node;typedef _list_iterator<T,Ref,Ptr> self;_list_iterator(node* n):_node(n){}Ref operator*(){return _node->_val;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}Ptr operator->(){return &_node->_val;}bool operator!=(const self& it ){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}//返回迭代器内部的结点指针node* GetNodePtr(){return _node;}private:node* _node;};

根据不同的模板参数从而实例化出不同的代码。

5.4 insert(),erase()

insert()可以在某一迭代器位置插入数据,erase可以删除某一迭代器位置的数据。

    void insert(iterator pos,const T& val){//创建结点node* newnode = new node(val);node* posnode = pos.GetNodePtr();node* prevnode = posnode->_prev;//改变指向newnode->_next = posnode;newnode->_prev = prevnode;prevnode->_next = posnode;posnode->_prev = newnode;}iterator erase(iterator pos){node* posnode = pos.GetNodePtr();node* prevnode = posnode->_prev;node* nextnode = posnode->_next;//修改指向prevnode->_next = nextnode;nextnode->_prev = prevnode;//释放结点delete posnode;return iterator(nextnode);}

 erase在返回的删除位置的下一个结点的迭代器,也是为了绝迭代器失效的问题。

5.5 析构函数

    //清除数据,但是头节点需要保留void clear(){iterator it = begin();while (it != end()){erase(it++);}}~List(){//连同头节点一起释放clear();delete _head;_head = nullptr;}

5.6再看构造函数

迭代器初始化

   	void emptyInit(){_head->_next = _head;_head->_prev = _head;}template<class InputIterator>List(InputIterator frist, InputIterator lest):_head(new node){emptyInit();while (frist != lest){push_back(*frist);frist++;}}

五.list模拟实现整体代码

#pragma once
#include<iostream>
#include<cassert>
using namespace std;template<class T>
struct _list_node
{_list_node( const T& val = T()):_val(val),_next(nullptr),_prev(nullptr){}T _val;           //存储数据_list_node* _next;//后一个结点的指针_list_node* _prev;//前一个结点的指针
};/*
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:1. 原生态指针,比如:vector2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:1. 指针可以解引用,迭代器的类中必须重载operator*()2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
*///普通迭代器1.0版本
//template<class T>
//class _list_iterator
//{
//public:
//	typedef _list_node<T> node;
//	typedef _list_iterator self;
//
//	_list_iterator(node* n)
//		:_node(n)
//	{
//	}
//
//	T& operator*()
//	{
//		return _node->_val;
//	}
//
//	self operator++(int)
//	{
//		self tmp(*this);
//		_node = _node->_next;
//		return tmp;
//	}
//
//	self operator++()
//	{
//		_node = _node->_next;
//		return *this;
//	}
//
//	self operator--(int)
//	{
//		self tmp(*this);
//		_node = _node->_prev;
//		return tmp;
//	}
//
//	self operator--()
//	{
//		_node = _node->_prev;
//		return *this;
//	}
//
//	bool operator!=(const self& it)
//	{
//		return _node != it._node;
//	}
//
//	bool operator==(const self& it)
//	{
//		return _node == it._node;
//	}
//
//	T*  operator->()
//	{
//		return &_node->_val;
//	}
//private:
//	node* _node;
//};//const迭代器1.0版本
//template<class T>
//class const_list_iterator
//{
//public:
//	typedef _list_node<T> node;
//	typedef const_list_iterator self;
//
//	const_list_iterator(node* n)
//		:_node(n)
//	{
//	}
//
//	const T& operator*()
//	{
//		return _node->_val;
//	}
//
//	self operator++(int)
//	{
//		self tmp(*this);
//		_node = _node->_next;
//		return tmp;
//	}
//
//	self operator++()
//	{
//		_node = _node->_next;
//		return *this;
//	}
//
//	self operator--(int)
//	{
//		self tmp(*this);
//		_node = _node->_prev;
//		return tmp;
//	}
//
//	self operator--()
//	{
//		_node = _node->_prev;
//		return *this;
//	}
//
//	bool operator!=(const self& it)
//	{
//		return _node != it._node;
//	}
//
//	bool operator==(const self& it)
//	{
//		return _node == it._node;
//	}
//
//	const T*  operator->()
//	{
//		return &_node->_val;
//	}
//private:
//	node* _node;
//};//迭代器2.0版本
//T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
template<class T, class Ref, class Ptr>
class _list_iterator
{
public:typedef _list_node<T> node;typedef _list_iterator<T, Ref, Ptr> self;_list_iterator(node* n):_node(n){}Ref operator*(){return _node->_val;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}Ptr operator->(){return &_node->_val;}bool operator!=(const self& it){return _node != it._node;}bool operator==(const self& it){return _node == it._node;}node* GetNodePtr(){return _node;}
private:node* _node;
};template<class T>
class List
{
public:typedef _list_node<T> node;typedef _list_iterator<T,T&,T*> iterator;typedef _list_iterator<T,const T&,const T*> const_iterator;void emptyInit(){_head->_next = _head;_head->_prev = _head;}List():_head(new node){emptyInit();}template<class InputIterator>List(InputIterator frist, InputIterator lest):_head(new node){emptyInit();while (frist != lest){push_back(*frist);frist++;}}void push_back(const T& val ){node* newnode = new node(val);node* tail = _head->_prev;tail->_next = newnode;newnode->_next = _head;_head->_prev = newnode;newnode->_prev = tail;}bool empty(){return _head->_next == _head;}void pop_back(){//判空assert(!empty());node* tail = _head->_prev;node* newtail = tail->_prev;//改变指向_head->_next = newtail;newtail->_prev = _head;//释放结点delete tail;}void insert(iterator pos,const T& val){node* newnode = new node(val);node* posnode = pos.GetNodePtr();node* prevnode = posnode->_prev;newnode->_next = posnode;newnode->_prev = prevnode;prevnode->_next = posnode;posnode->_prev = newnode;}iterator erase(iterator pos){node* posnode = pos.GetNodePtr();node* prevnode = posnode->_prev;node* nextnode = posnode->_next;prevnode->_next = nextnode;nextnode->_prev = prevnode;delete posnode;return iterator(nextnode);}iterator begin(){//使用哨兵位的下一个结点指针,构造beginreturn iterator(_head->_next);}iterator end(){//使用哨兵位结点指针,构造endreturn iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}//清除数据,但是需要保留void clear(){iterator it = begin();while (it != end()){it = erase(it);}}~List(){//连同头节点一起释放clear();delete _head;_head = nullptr;}private:node* _head;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/33552.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VARIATIONAL IMAGE COMPRESSION WITH A SCALE HYPERPRIOR

文章目录 VARIATIONAL IMAGE COMPRESSION WITH A SCALE HYPERPRIORABSTRACT1 INTRODUCTION2 COMPRESSION WITH VARIATIONAL MODELS3 INTRODUCTION OF A SCALE HYPERPRIOR 个人总结动机流程思路 VARIATIONAL IMAGE COMPRESSION WITH A SCALE HYPERPRIOR ABSTRACT We describe …

【BASH】回顾与知识点梳理(十七)

【BASH】回顾与知识点梳理 十七 十七. 什么是 Shell scripts17.1 干嘛学习 shell scripts自动化管理的重要依据追踪与管理系统的重要工作简单入侵检测功能连续指令单一化简易的数据处理跨平台支持与学习历程较短 17.2 第一支 script 的撰写与执行撰写第一支 script 17.3 撰写 s…

css flex 上下结构布局

display: flex; flex-flow: column; justify-content: space-between;

腾讯云服务器远程连接的方法大全

腾讯云服务器怎么连接登录&#xff1f;腾讯云服务器支持多种远程连接方法&#xff0c;可以使用腾讯云管理控制台自带的远程连接工具&#xff0c;也可以使用第三方远程连接工具&#xff0c;如如PuTTY、Xshell等&#xff0c;Linux操作系统可以SSH登录&#xff0c;Windows可以使用…

swagger 3.0 学习笔记

引入pom <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency>配置 import io.swagger.models.auth.In; import io.swagger.v3.oas.annotati…

Linux计算机名自动变为bogon,修改计算机名

Linux计算机名自动变为bogon&#xff0c;修改计算机名 问题&#xff1a;这次机房停电&#xff0c;部分VM计算机名自动变为bogon&#xff0c;判断故障&#xff1a;因开启VM的时候&#xff0c;网卡需要获取DNS&#xff0c;但是DNS服务器还没有起来&#xff0c;故自动在resolv.con…

innodb buffer pool

buffer pool是主存中的一个区域&#xff0c;InnoDB 在访问时缓存表和索引数据。缓冲池允许直接从内存访问频繁使用的数据&#xff0c;这加快了处理速度。在专用服务器上&#xff0c;高达80% 的物理内存通常分配给缓冲池。为了提高大容量读取操作的效率&#xff0c;将缓冲池划分…

npm ERR! code ERESOLVEnpm ERR! ERESOLVE unable to resolve dependency tree

拉取项目到本地 执行 npm install 报错 遇到这个问题首先确认的就是版本是不是太高了&#xff0c;降一下版本。或者通过yarn命令替代npm install命令安装&#xff0c;同理&#xff0c;启动也可以采用yarn dev 启动代替npm run dev 下面教大家用一个NVM工具&#xff0c;这个工…

IntellIJ Idea 连接数据库-MySql

前言&#xff1a;可以用mariaDB工具&#xff0c;在本地创建服务器主机和数据库&#xff0c;而后用intellIJ Idea尝试连接 MariaDB创建数据库练习 1.IntellIJ Idea打开界面右侧Database工具&#xff0c;选择MySQL数据库。 2.填写数据库账号密码&#xff0c;地址端口号&#xff…

Kafka:springboot集成kafka收发消息

kafka环境搭建参考Kafka&#xff1a;安装和配置_moreCalm的博客-CSDN博客 1、springboot中引入kafka依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><…

无涯教程-Perl - print函数

描述 此函数将LIST中的表达式的值打印到当前的默认输出文件句柄或FILEHANDLE指定的句柄中。 如果设置,则$\变量将添加到LIST的末尾。 如果LIST为空,则打印$_中的值。 print接受一个值列表,列表中的每个元素都将被解释为一个表达式。 语法 以下是此函数的简单语法- print…

idea+gradle阅读spring5.2.9源码之源码构建报错解决方案

注意 1、先确保gradle版本和spring、jdk版本对应 本文:gradle:5.6.4/spring 5.2.9/jdk1.8&#xff08;gradle和jdk都要先安装好&#xff0c;gradle还要配置好本地资源文件路径&#xff09; 2、原来项目乱了的话&#xff0c;先重新导入下载的源码项目 3、进入源码所在根目录&…

AST入门与实战(三):if节点转switch节点(瑞数5)

原文地址:https://zhuoyue360.com/jsnx/110.html 1. 期望 这是一个瑞数5代解混淆的案例&#xff0c;我们本章节需要做的是把if节点的内容转换成switch-case内容.以此来熟悉AST对JS混淆的对抗. 原始代码: function whileState() {while (1) {aV cA[wU];if (aV < 4) {if (…

Xcode 基座打包

Xcode基座打包-APP更新版本内容无效 问题&#xff1a;解决&#xff1a; 问题&#xff1a; 使用xcode基座打包之后&#xff0c;上传到appstore进行提审发布。 用户在appstore商城进行更新下载&#xff0c;打开更新后的APP发现版本号是最新的&#xff0c;APP里面的其他内容还是上…

Node.js |(二)Node.js API:fs模块 | 尚硅谷2023版Node.js零基础视频教程

学习视频&#xff1a;尚硅谷2023版Node.js零基础视频教程&#xff0c;nodejs新手到高手 文章目录 &#x1f4da;文件写入&#x1f407;writeFile 异步写入&#x1f407;writeFileSync 同步写入&#x1f407;appendFile / appendFileSync 追加写入&#x1f407;createWriteStrea…

2. 获取自己CSDN文章列表并按质量分由小到大排序(文章质量分、博客质量分、博文质量分)(阿里云API认证)

文章目录 写在前面步骤打开CSDN质量分页面粘贴查询文章url按F12打开调试工具&#xff0c;点击Network&#xff0c;点击清空按钮点击查询是调了这个接口https://bizapi.csdn.net/trends/api/v1/get-article-score用postman测试调用这个接口&#xff08;不行&#xff0c;认证不通…

elementui实现当前页全选+所有全选+翻页保持选中状

原文来自&#xff1a;https://blog.csdn.net/sumimg/article/details/121693305?spm1001.2101.3001.6650.1&utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121693305-blog-127570059.235%5Ev38%5Epc_relevant_anti_t3&depth_1-utm…

Linux —— 文件系统

目录 一&#xff0c;背景 二&#xff0c;文件系统 一&#xff0c;磁盘简介 磁盘分为SSD、机械磁盘&#xff1b;机械磁盘&#xff0c;即磁盘高速转动&#xff0c;磁头移动到读写扇区所在磁道&#xff0c;让磁头在目标扇区上划过&#xff0c;即可完成对扇区的读写操作&#xff…

nacos升级开启鉴权后,微服务无法连接的解决方案

版本&#xff1a; 软件版本号备注spring boot2.2.5.RELEASEspring-cloudHoxton.SR3spring-cloud-alibaba2.2.1.RELEASEnacos2.0.1从1.4.2版本进行升级。同时作为注册中心和配置中心 一、升级nacos版本&#xff0c;开启鉴权 1.在application.properties配置文件开启鉴权&…

thinkphp:分组查询(多条相同列的数据只展示一条)

例子&#xff1a;数据库中有trans_num、subinventory_from、transaction_type、creation_date有相同值&#xff0c;在查询该数据库使&#xff0c;只展示这几个值相同的一条 效果&#xff1a; 限制之前 限制之后 代码 限制前&#xff0c;后端代码 public function select_i…