【C++进阶之路】list的基本使用和模拟实现

文章目录

  • 初步认识
    • ①定义
    • ②底层原理
    • ③迭代器的分类
  • 一、基本使用
    • 1.插入结点元素
    • 2.删除结点元素
    • 3.合并两个有序链表
    • 4.将一条链表的某一部分转移到另一条链表
    • 5.对链表排序并去重
    • 6.vector与list排序的比较
  • 二、模拟实现
    • ①要点说明
    • ②基本框架
    • ③迭代器
      • 构造函数
      • ++
      • - -
      • *
      • ->
      • list里的迭代器
    • ④insert
    • ⑤erase
    • ⑥push_back
    • ⑦push_front
    • ⑧pop_front
    • ⑨pop_back()
    • ⑩构造函数
    • ⑪clear
    • ⑫析构函数
    • ⑬赋值重载
  • 源码
  • 总结

初步认识

 在正式讲解之前,我们先简单的认识一下list

①定义

template < class T, class Alloc = allocator<T> > class list;

同样这里有两个模板参数,第一个是实例化的类型,第二个是空间配置器

②底层原理

库里的list是采用带头双向循环链表进行实现的。

  • forward_list是单链表

在这里插入图片描述

  • 带哨兵位的头结点,是为了给end()迭代器留位置。

③迭代器的分类

在这里插入图片描述

1.输入迭代器
2.输出迭代器
3.单向迭代器——forward_list,unorder_xxx
4.双向迭代器——list/map/set
5.随机访问迭代器——vector/string/deque

之间的关系:
在这里插入图片描述

  • 从左到右是被包含的关系。

在这里插入图片描述

一、基本使用

1.插入结点元素

	list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);for (auto e : lt){cout << e << " ";}cout << endl;list<int>::iterator it = lt.begin();for (size_t i = 0; i < 5; i++){it++;}//在第五个位置的前面插入10//第五个位置——是下标为5,也就是第六个元素。lt.insert(it,10);for (auto e : lt){cout << e << " ";}cout << endl;

在这里插入图片描述

  • 说明:因为list不支持随机访问,因此没有+,+=,[] 运算符重载,但支持++,- -

  • 这里的迭代器,在插入之后,并没有失效,因为结点的插入是单个申请,单个释放的。不存在 扩容的现象。

2.删除结点元素

void test_list()
{list<int> lt;lt.push_back(2);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(90);lt.push_back(6);lt.push_back(7);for (auto e : lt){cout << e << " ";}cout << endl;//删除所有的偶数结点list<int>::iterator it = lt.begin();while (it != lt.end()){if (*it % 2 == 0){it = lt.erase(it);}else{it++;}}for (auto e : lt){cout << e << " ";}cout << endl;}
  • erase在释放之后,迭代器是肯定失效的,因为其所指向的空间都失效了。因此库里采用返回值进行更新迭代器。

3.合并两个有序链表

  • 前提必须有序的。
void test_list()
{list<int> lt;lt.push_back(2);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.push_back(7);for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt1;lt1.push_back(1);lt1.push_back(3);lt1.push_back(5);lt1.push_back(7);lt1.push_back(9);for (auto e : lt1){cout << e << " ";}cout << endl;//将lt1合并到lt上lt.merge(lt1);for (auto e : lt){cout << e << " ";}cout << endl;}

在这里插入图片描述

4.将一条链表的某一部分转移到另一条链表

接口:


void splice (iterator position, list& x);void splice (iterator position, list& x, iterator i);void splice (iterator position, list& x, iterator first, iterator last);

注意:迭代器/迭代器区间不能重合!

list<int> lt;lt.push_back(2);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.push_back(7);for (auto e : lt){cout << e << " ";}cout << endl;list<int> lt1;lt1.push_back(1);lt1.push_back(3);lt1.push_back(5);lt1.push_back(7);lt1.push_back(9);for (auto e : lt1){cout << e << " ";}cout << endl;//将lt1转移到到lt的前面lt.splice(lt.begin(),lt1);for (auto e : lt){cout << e << " ";}cout << endl;//将lt的后半部分转移到lt1上list<int>::iterator it = lt.begin();for (size_t i = 0; i < 5; i++){it++;}lt1.splice(lt1.begin(), lt ,it, lt.end());for (auto e : lt1){cout << e << " ";}cout << endl;//将lt1的第一个结点移到lt的最前面lt.splice(lt.begin(), lt1, lt1.begin());for (auto e : lt){cout << e << " ";}cout << endl;

5.对链表排序并去重

  • unique去重的前提是有序
	list<int> lt;lt.push_back(2);lt.push_back(1);lt.push_back(3);lt.push_back(4);lt.push_back(2);lt.push_back(6);lt.push_back(4);lt.sort();for (auto e : lt){cout << e << " ";}cout << endl;lt.unique();for (auto e : lt){cout << e << " ";}cout << endl;

6.vector与list排序的比较

void test_list2()
{list<int> lt;vector<int> v;//设置随机数起点srand((unsigned)time(nullptr));size_t datas_num = 1000000;for (size_t i = 0; i < datas_num; i++){int n = rand();lt.push_back(n);v.push_back(n);}int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();cout <<"vector:" << (end1 - begin1) << endl;int begin2 = clock();lt.sort();int end2 = clock();cout <<"list:" << (end2 - begin2) << endl;
}

在这里插入图片描述

  • 结果:list的排序有点挫,不过如果数据量比较小,可以使用list提供的排序,数据量比较大的话,那就将list的数据拷贝到vector,进行排序,再拷贝回去。
  • 注意:在debug下进行测试可能会得到不一样的结果,因为vector是使用递归的快排进行实现的,如果debug下添加的调试信息会使栈帧的开销较大,从而影响时间的效率。而list的底层原理是归并,虽然也是递归,但调试信息添加的可能较少,因此会较快。而realease版本是发型版本是做了优化的,性能更好。

二、模拟实现

①要点说明

  • 1.为了不与库里面的list冲突,我们需要命名空间对自己实现的类进行封装
  • 2.这里我们实现的框架是按照带头双向循环链表的数据结构进行实现的。
  • 3.为了理解,下面的接口是分开讲解的,最后我会给出源码

②基本框架

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;
};template<class T>
class list
{typedef list_node<T> Node;
public:private:Node* _head;size_t _size;
};

③迭代器

  • 这是list的最关键的部分!

我们先来谈谈是如何实现的,迭代器必然是指向一个结点的指针,但是能完成++操作,以及解引用操作,那该怎么办呢?答案其实很简单——用类进行封装,通过运算符重载实现相应的功能

简单的迭代器框架:

template<class T>
struct list_iterator
{typedef list_iterator<T> iterator;	Node* _node;
};

构造函数

list_iterator(Node* val = nullptr):_node(val)
{}
list_iterator(const iterator& lt):_node(lt._node)
{}
  • 析构我们使用默认生成的即可,可千万不要写析构释放迭代器指向的空间,因为迭代器指向的空间属于list。应由list进行管理。

剩下的操作是,类里面进行重载++,-- ,*。

++

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

- -

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

*

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

这样迭代器的基本功能就实现了,但是const的迭代器该如何实现呢?

是这样么?

typedef const list_iterator<T> const_iterator;

这样迭代器的成员变量不可被修改,即不可以++或者- -,不符合要求,我们想要的只是返回的值不可被修改

一般都会再拷贝一份进行,将拷贝的迭代器改改,但是我们还可以设置第二个模板参数,这样外面可以这样定义。

template<class Tclass Ref> struct list_iterator;
//里面的运算符重载——*
Ref operator *()
{return _node->_val;
}//list里面可以这样定义typedef list_iterator<T,const T&> const_iterator;
typedef list_iterator<T,T&> iterator;
  • 这样问题就解决了。

->

还有第二个问题,如果迭代器指向的是自定义类型,比如:

struct A
{A(int x = 0):_a1(x){}int _a1;
};

如果想要访问其成员,直接解引用是不行的。

我们可以这样:

iterator it = lt.begin();
(*it)._a1;

访问其元素。

我们还可以重载-> 进行访问:

T* operator ->()
{return &(_node->_val);
}
iterator it = lt.begin();
it->_a1;

这样其实省略了一个箭头,这省略的一个箭头其实是为了美观,编译器在处理时,会自动加上的。

如果是const对象呢?其实处理方式一样的,再加一个模板参数。

更新后的模板,和迭代器:

template<class T,class Ref,class Ptr> struct list_iterator;
Ptr operator ->()
{return &(_node->_val);
}
typedef list_iterator<T,const T&,const T*> const_iterator;
typedef list_iterator<T,T&,T*> iterator;

那完整版的迭代器即为:

template<class T,class Ref,class Ptr>
struct list_iterator
{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> self;typedef list_iterator<T, T&, T*> iterator;list_iterator(Node* val = nullptr):_node(val){}list_iterator(const iterator& lt):_node(lt._node){}//重载++self& operator ++() {_node = _node->_next;return *this;}//后置++,为了区别需要重载一下,这里的参数实际上没啥用self operator ++(int){list_iterator tmp(_node);_node = _node->_next;return tmp;}self& operator --(){_node = _node->_prev;return *this;}self operator --(int){list_iterator tmp(_node);_node = _node->_prev;return tmp;}bool operator != (const self& it) const{return _node != it._node;}bool operator == (const self& it) const//const和非const都能用{return _node == it._node;}		Ptr operator ->(){return &(_node->_val);}Ref operator *(){return _node->_val;}Node* _node;
};

list里的迭代器

iterator begin() 
{return _head->_next; //隐式类型转换
}
const_iterator begin()const
{return _head->_next;}
iterator end()
{return _head;
}
const_iterator end()const
{return _head;
}

④insert

//在pos之前插入
void insert(iterator pos, const T& val)
{Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = prev;cur->_prev = newnode;prev->_next = newnode;_size++;
}

⑤erase

Node* erase(iterator pos)
{assert(pos != end());//这个检查是不很严格的,如果删除一个未知的结点,这个是检查不到的!Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return next;
}

⑥push_back

void push_back(const T& val)
{//Node* tail = _head->_prev;//Node* newnode = new Node(val);//newnode->_next = _head;//newnode->_prev = tail;//_head->_prev = newnode;//tail->_next = newnode;insert(end(), val);
}

⑦push_front

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

⑧pop_front

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

⑨pop_back()

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

⑩构造函数

//默认构造函数
list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}//拷贝构造
list(const list<T>& lt)
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto e : lt){push_back(e);}
}

⑪clear

void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}

⑫析构函数

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

⑬赋值重载

void swap(list &tmp)
{std::swap(_head, tmp._head);std::swap(_size, tmp._size);
}
list& operator = (list tmp)
{swap(tmp);return *this;
}

剩余的接口,过于简单,这里就不再列出了,有兴趣请看源码。

源码

namespace my_list
{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;};template<class T,class Ref,class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> self;typedef list_iterator<T, T&, T*> iterator;list_iterator(Node* val = nullptr):_node(val){}list_iterator(const iterator& lt):_node(lt._node){}//重载++self& operator ++() {_node = _node->_next;return *this;}//后置++,为了区别需要重载一下,这里的参数实际上没啥用self operator ++(int){list_iterator tmp(_node);_node = _node->_next;return tmp;}self& operator --(){_node = _node->_prev;return *this;}self operator --(int){list_iterator tmp(_node);_node = _node->_prev;return tmp;}bool operator != (const self& it) const{return _node != it._node;}bool operator == (const self& it) const//const和非const都能用{return _node == it._node;}		Ptr operator ->(){return &(_node->_val);}Ref operator *(){return _node->_val;}Node* _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 = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(const list<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto e : lt){push_back(e);}}~list(){clear();delete _head;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}//cout << _size << endl;}iterator begin() {//return _head->_next;//隐式类型转换为list_itertorreturn _head->_next; }const_iterator begin()const{//return _head->_next;//隐式类型转换为list_itertorreturn _head->_next;}iterator end(){return _head;//返回的是_head的拷贝,因此返回对象具有常属性//隐式类型转换为list_itertor//return itrator(_head->next); //匿名对象}const_iterator end()const{return _head;//返回的是_head的拷贝,因此返回对象具有常属性//隐式类型转换为list_itertor//return itrator(_head->next); //匿名对象}void push_back(const T& val){//Node* tail = _head->_prev;//Node* newnode = new Node(val);//newnode->_next = _head;//newnode->_prev = tail;//_head->_prev = newnode;//tail->_next = newnode;insert(end(), val);}//在pos之前插入void insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = prev;cur->_prev = newnode;prev->_next = newnode;_size++;}Node* erase(iterator pos){assert(pos != end());//这个检查是不很严格的,如果删除一个未知的结点这个是会报错的!Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return next;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void push_front(const T& val){insert(begin(), val);}size_t size(){return _size;}bool empty(){return _size == 0;}void swap(list &tmp){std::swap(_head, tmp._head);std::swap(_size, tmp._size);}list& operator = (list tmp){swap(tmp);return *this;}private:Node* _head;size_t _size;};
}

总结

 今天的分享就到这里了,如果觉得文章不错,点个赞鼓励一下吧!我们下篇文章再见

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

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

相关文章

Raft算法之日志复制

Raft算法之日志复制 一、日志复制大致流程 在Leader选举过程中&#xff0c;集群最终会选举出一个Leader节点&#xff0c;而集群中剩余的其他节点将会成为Follower节点。Leader节点除了向Follower节点发送心跳消息&#xff0c;还会处理客户端的请求&#xff0c;并将客户端的更…

altium designer OUTPUT FILE Analysis

设计完PCB后,会导出相关光绘文件发给板厂打板,针对AD导出的各个光绘文件,我记录下了它们各个文件对应的层如下: GTL: Top Layer 顶层线路 GBL: Bottom Layer 底层线路 G1,G2… : Mid Layer 1, 2, … 中间信号层 GP1,GP2…: Internal Plane Layer 1, 2, …内电层 G…

JAVA8新特性(Stream API,LocalDate,LocalDateTime)

一. Stream API 1.1 基础 代码参数准备&#xff1a; package com.weige.javaskillpoint.controller;import lombok.Data; import lombok.extern.slf4j.Slf4j;import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; Slf4j public class J…

ElasticSearch搜索相关性及打分的相关原理

文章目录 一、相关性和打分简介二、TF-IDF得分计算公式三、BM25&#xff08;Best Matching 25&#xff09;四、使用explain查看TF-IDF五、通过Boosting控制相关度 一、相关性和打分简介 举个例子来说明&#xff1a; 假设有一个电商网站&#xff0c;用户在搜索框中输入了关键词&…

理解LLM中的ReAct

large language models (LLMs)大语言模型在语义理解和交互式决策方面有着不错的表现。ReAct在一次交互中循环使用推理和行动两个操作解决复杂问题&#xff0c;推理即利用模型自身语义理解能力&#xff0c;行动则利用模型以外的能力&#xff08;如计算、搜索最新消息&#xff0c…

架构训练营学习笔记:4-2 存储架构模式之复制架构

高可用的关键指标 问题&#xff1a;分为故障跟灾难。不是有了多活架构就不在用复制架构 &#xff0c;还是之前的合适原则&#xff1a;多活架构的技术复杂度 跟成本都比复制架构高。 高可用的关键指标 恢复时间目标(RecoveryTimeObjective&#xff0c;RTO)指为避免在灾难发生后…

Spring Cloud Gateway - 新一代微服务API网关

Spring Cloud Gateway - 新一代微服务API网关 文章目录 Spring Cloud Gateway - 新一代微服务API网关1.网关介绍2.Spring Cloud Gateway介绍3.Spring Cloud Gateway的特性4.Spring Cloud Gateway的三大核心概念5.Gateway工作流程6.Gateway核心配置7.动态路由8.Predicate自定义P…

阿里云RockMQ与SpringBoot的整合

前言&#xff1a; 开源版本Rocket和商业版本的RocketMQ有些不同&#xff0c;研究的是商业版本的RocketMQ&#xff0c;阿里云的官方文档&#xff0c;感觉有点乱。看不咋明白&#xff0c;网上虽然有教程&#xff0c;大都还是有点缺少&#xff0c;有时候会突然跳了步骤&#xff0c…

fpga4fun.com/Music box

1.Simple beep 先做一个简单地蜂鸣器&#xff0c;原理是晶振通过不同的分频器分成不同的频率&#xff0c;通过电路发出音调不同的声音。 例子中Pluto FPGA板子有25Mhz的时钟频率&#xff0c;采用16位计数器&#xff08;可以产生65536个不同的数值&#xff09;&#xff0c;则最…

C# 细说async/await的用法

目录 一&#xff0c;引言 二&#xff0c;实例演示 2.1 多线程同步执行下载任务&#xff0c;任务完成后通知 2.2 异步执行下载任务&#xff0c;任务完成后通知 三&#xff0c;async/await的用法 3.1 跨线程修改UI控件 3.2 异步获取数据 一&#xff0c;引言 首先先来区分…

网上书店管理系统

目录 一、系统需求分析 二、数据库概念结构设计 四、数据库物理实现 五、数据库功能调试 一、系统需求分析 需求概述 1.系统背景 当今互联网的迅速发展&#xff0c;使得人们获取信息变得极其便利。在从前&#xff0c;人们以线下书店购买书籍的方式获取知识&#xff0c;常常…

WEB:Confusion1

背景知识 SSTI漏洞 题目 根据网站图片和题目描述的提示&#xff0c;大象是php&#xff0c;蟒蛇是python&#xff0c;说明了这个网站是用python写的 在python中&#xff0c;比较常规的漏洞就是SSTI模板注入 没有思路&#xff0c;先点login和register页面看看 查看源代码 之前…

读论文---On Distillation of Guided Diffusion Models

该论文解决的问题 1 简要描述 2 在之前的工作中存在下述问题 计算过程需要计算: 1 unconditional的unet 2 conditional(w text)的unet 下图展示了计算过程 对应的代码 pipelines-> stable_diffusion-> pipline_stable_diffusion.py-> StableDiffusionPipeling-> 7…

C#在工业自动化领域的应用前景如何?

在2021年&#xff0c;C#与工业自动化已经开始结合&#xff0c;并且这种趋势有望在未来继续发展。C#是一种功能强大的编程语言&#xff0c;其面向对象的特性、跨平台支持以及丰富的类库和工具&#xff0c;使其成为在工业自动化领域应用的有力工具。 我这里刚好有嵌入式、单片机…

微服务系列文章 之 nginx日志格式分析以及修改

如何自定义日志格式&#xff0c;就需要修改nginx日志打印格式 一. 打开终端&#xff0c;登录服务器并输入服务器密码 //ssh 用户名服务器ip ssh root192.168.0.132二. 切换到nginx目录 cd /var/log/nginx/ 三. 查看nginx日志 tail -f access.log 日志说明&#xff1a; //…

Servlet的监听器

Servlet常用的监听器 ServletContextAttributeListener 用来感知ServlerContext对象属性变化&#xff0c;比如添加或删除属性变化 ServletContextListener 用来感知ServlerContext对象的创建和销毁的 ServletRequestListener 可以用来监听感知ServletRequest对象的创建和销毁的…

OLED拼接屏采购指南:如何选择最佳方案?

OLED拼接屏作为一种创新的大屏幕显示设备&#xff0c;正在成为各行各业信息展示和传播的重要工具。 然而&#xff0c;面对市场上众多的品牌和型号&#xff0c;如何选择最佳的OLED拼接屏方案成为一项关键任务。 本文将为您提供一份全面且实用的OLED拼接屏采购指南&#xff0c;…

.NET Native AOT的静态库与动态库

.NET不仅可以使用 C静态库与动态库&#xff0c;也可以将.NET实现的函数导出为C静态库与动态库。在没有Native Aot之前&#xff0c;.NET只能通过P/Invoke享受C/C生态&#xff0c;而在Native Aot之后&#xff0c;不仅可以享受这些生态&#xff0c;还可以开发SDK供其他语言调用。 …

蓝桥杯上岸每日N题第二期(一)!!!

大家好 我是寸铁&#x1f4aa; 考前需要刷大量真题,大家一起相互监督&#xff0c;每日做N题&#xff0c;一起上岸吧✌️ ~ 冲刺蓝桥杯省一模板大全来啦 &#x1f4a5; ~ 蓝桥杯4月8号就要开始了 &#x1f64f; ~ 还没背熟模板的伙伴们背起来 &#x1f4aa; &#x1f4aa; &…

Sql Server类型转换,Sql Server函数

CONVERT(数据类型, 表达式, 样式) --样式可以没有 CAST(表达式 as 数据类型) select StudentName 的出生日期是&#xff1a; cast(DateOfBirth as varchar(50)) as 学生信息 from Students; select StudentName 的出生日期是&#xff1a; convert(varchar(50), DateOfB…