【C++】list 模拟笔记

文章目录

  • list
  • 定义结点类(list_node)
  • 为什么封装迭代器为类 ?
  • 库里面模板多参数的由来 ?
  • 为什么普通迭代器不能隐式类型转换成const迭代器?
  • 迭代器位置指向及其返回值和整体代码

list

  list 和前面学习的 string 和 vector 稍有差别,list 存储空间是不连续的,不支持随机访问。list是一个双向带头循环链表,成员变量只有一个结点(Node)类型的指针 。在 C 语言部分,结点是个结构体,而在 C++ 中已经升级成为了类,可以实现自己的各种默认成员函数。这无疑成为了我们实现的难点。因为 list也是个类,其成员函数是 Node 类的指针,而且我们还要用模板来实现

定义结点类(list_node)

  这里注意用 struct 定义结点(Node)类,因为 list 类要频繁使用 Node 类,友元太麻烦,下方迭代器类也是用 struct 定义,方便 list 频繁使用。我们定义的时候,随便把构造函数写一下,用 T() 空类型作缺省值,T 如果是内置类型 int,就会用 0 初始化。类名不用 Node,因为库里别的容器也会用到结点类,且实现方式不一样,这里区分一下

库里定义的

自己实现的:

为什么封装迭代器为类 ?

  在 sting 和 vector 里面,迭代器可以实现成原生指针,解引用变得到了 val 值。而现在 list的存储空间不连续,迭代器++是到下一个结点位置,我们中间解引用得到的是一个结点,而我们现在需要的是解引用能到得结点里面的 val 值。对此我们不得不将迭代器封装成为一个类,通过重载 operator++ 和 operator* ,来实现迭代器的功能。
  迭代器的成员变量只有一个就是 Node 类型的指针, 由于我们迭代器类需要结点类型的指针,于是我们 typedef 重命名一下 list_node 为 Node

库里面模板多参数的由来 ?

  首先迭代器的类名不能直接是 iterator,别的容器也有迭代器,这里定义成 _list_iterator 。我们一开始是这么定义普通对象的 iterator ,那么思考我们如何定义 const 迭代器?

typedef _list_iterator iterator;

  假如我们向下面这样定义 const 迭代器,第一眼看好像没什么问题,但仔细一想,迭代器的成员变量是一个 Node 指针,这么定义是结点不能移动指向下一个位置,反而结点指向的内容可以被修改。我们需要的是 operator* 解引用后,结点的 val 不能被修改,但是结点可以依然可以移动,指向下一个位置

typedef const _list_iterator const_iterator;

  倘若我们像下面这样多定义一个 const 迭代器类,然后除了返回值不一样,其它照抄 _list_iterator 类来写的话,又会造成代码冗余

template
struct _list_const_iterator
{
……
}
typedef const _list_const_iterator const_iterator;

  对此我们多加一个模板参数,T& ,对于普通对象用实例化为普通迭代器,const 对象就实例化为 const 迭代器,这样就解决了 operator* 函数返回值的问题,而库里面又重载了 operator-> 函数,返回值是 T*,有分普通和 const 对象,因此我们再增加一个模板参数 T*。在这里注意一下,operator-> 返回的是指针,假设我们有 list 迭代器 it,且 T 类型为一个结构体。当我们要用迭代器访问结构体内的成员,我们就要这样写,it->->(结构体内的成员),第一个箭头调用 operator->,第二个指向结构体内的成员。但是编译器要求可读性,会优化为 it->(结构体内的成员)

typedef _list_iterator<T,T&> iterator;
typedef _list_iterator<T,const T&> const_iterator;
//普通迭代器
typedef _list_iterator<T,T&,T
*> iterator;
//const迭代器
typedef _list_iterator<T,const T&,const T
*> const_iterator



为什么普通迭代器不能隐式类型转换成const迭代器?

  首先确保自己知道了 list 的大致实现原理。那么当我们自己实现一个 iterator 时,如果不加以注意,可能会发生如下情况:我们明明没有对权限进行放大,为什么编译器还会报错呢?

  这是因为在泛型编程中,如果我们没有专门定义一个拷贝构造函数,那么默认的会以自己的类型为基准去考虑参数类型。不要用 int 可以隐式转化成 const int 的思维去考虑类模板。虽然 iterator和const_iterator 在底层调用的都是同一个类模板,但是在实例化的时候,编译器会认为这是两个不同的类型!所以这里原因很清楚了,当我们定义一个const_iterator时,其默认拷贝构造的参数也是const_iterator。
  所以这里原因很清楚了,当我们定义一个 const_iterator 时,其默认拷贝构造的参数也是 const_iterator。因此,编译器才会报错说 iterator<int, int&, int*> 无法转化为 iterator<int, const int&, const int*> 。
  解决方式也很简单,我们只需要在list_iterator 类中定义一个构造函数即可,参数为普通对象的迭代器类型,这样无论被拷贝对象时const迭代器还是普通迭代器都能调用这个函数,而且迭代器我们只需要完成浅拷贝,因为我们就是要指向同一块空间!!!

看看库里的:

迭代器位置指向及其返回值和整体代码

  稍微注意一下,begin() 指向的是 _head 的下一个结点,而 end() 指向的是 _head 结点。增删查改中,由于 erase 函数删除结点会导致迭代器失效,因此需要更新返回新的迭代器,新的迭代器指向删除结点的下一个。 insert 函数的话也和 erase 函数保持一致返回迭代器,不过返回的是新插入的结点

#pragma once
#include <iostream>
#include <assert.h>namespace Me
{//定义结点template<class T>struct list_node{//定义为公有,让迭代器访问list_node<T>* _prev;list_node<T>* _next;T _val;//升级成为了类有自己的构造函数list_node(const T& val = T()): _prev(nullptr),_next(nullptr), _val(val){}};//封装迭代器,定义为公有,让list访问template<class T,class Ref,class Ptr>struct _list_iterator{typedef list_node<T> Node;typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;typedef _list_iterator<T, Ref, Ptr> self;Node* _node;//升级成为了类有自己的构造函数_list_iterator(Node* node=nullptr):_node(node){}_list_iterator(const iterator&x):_node(x._node){}Ref operator*() const{return _node->_val;}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) {//这里不用单独写拷贝构造,浅拷贝就行,按需求来分析_list_iterator<T> tmp(*this);_node = _node->_prev;return tmp;}Ptr operator->(){return &_node->_val;}bool operator!=(const self& it) const{return _node != it._node;}bool operator==(const self& it) const{return _node == it._node;}};//定义链表template<class T>class list//定义链表{typedef list_node<T> Node;public://普通迭代器typedef _list_iterator<T,T&,T*> iterator;//const迭代器typedef _list_iterator<T,const T&,const T*> const_iterator;//typedef const _list_iterator<T> const_iterator;// 这样设计const迭代器是不行的,因为const迭代器期望指向内容不能修改// 这样设计是迭代器本身不能修改iterator begin(){//return _head->_next;return iterator(_head->_next);}iterator end(){//左闭又开 _head不存储有效数据//return iterator(_head);return _head;}const_iterator begin() const{//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{//左闭又开 _head不存储有效数据//return iterator(_head);return _head;}//哨兵卫头结点初始化void empty_init(){_head = new Node;_head->_prev = _head;_head->_next = _head;}list(){empty_init();}list(list<T>& lt){empty_init();//迭代拷贝for (auto& e : lt){push_back(e);}}void swap(list<T> lt){std::swap(_head, lt._head);}//拷贝构造后交换list<T>& operator=(list<T> lt){swap(lt);return *this;}//清理部分void clear(){iterator it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}void push_front(const T& x){insert(begin(), x);}void push_back(const T& x){//Node* newnode = new Node(x);//Node* tail = _head->_prev;//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;insert(end(), x);}void pop_front(){erase(begin());}void pop_back(){//assert(_head != _head->_next);//Node* tail = _head->_prev;//Node* tailP = tail->_prev;//tailP->_next = _head;//_head->_prev = tailP;//delete tail;erase(--end());}iterator insert(iterator pos, const T& x){//调用结点的拷贝构造函数Node* newnode = new Node(x);//找插入的前一个结点Node* prev = pos._node->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = pos._node;pos._node->_prev = newnode;return newnode;}iterator erase(iterator pos){assert(pos._node != _head);assert(_head != _head->_next);Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;pos._node = nullptr;return next;}size_t size(){size_t sz = 0;for (auto& e : *this){sz++;}return sz;}void print(){iterator it = begin();while (it != end()){cout << *it << " ";++it;}cout << endl;}private:Node* _head;//带哨兵我的头节点};
}

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

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

相关文章

微信小程序使用ECharts的示例详解

目录 安装 ECharts 组件使用 ECharts 组件图表延迟加载 echarts-for-weixin 是 ECharts 官方维护的一个开源项目&#xff0c;提供了一个微信小程序组件&#xff08;Component&#xff09;&#xff0c;我们可以通过这个组件在微信小程序中使用 ECharts 绘制图表。 echarts-fo…

excel中单行换成多行

今天碰以下情况&#xff1a; 这在excel表中是在一个单元格&#xff0c;现在需要对其进行转换&#xff0c;将一个单元格换成多行 步骤&#xff1a; 1.删除换行符&#xff0c;添加一个逗号 2.选择数据-分列-分隔字符-逗号-确定 3.复制上述数据&#xff0c;选择性粘贴-转置 完…

2816. 判断子序列

题目链接&#xff1a; 自己的做法&#xff1a; #include <bits/stdc.h>using namespace std;const int N 1e5 10; int a[N], b[N]; int main() {int n, m;bool flag true;scanf("%d%d", &n, &m);for (int i 0; i < n; i) scanf("%d"…

哈希:探索快速的数据存储和搜索方法

哈希&#xff1a;探索快速的数据存储和搜索方法 哈希表作为一种高效的数据存储结构&#xff0c;可以使数据的存储位置与关键码之间建立一一映射的关系&#xff0c;从而加快元素的搜索速度。然而&#xff0c;哈希方法也面临着哈希冲突的问题&#xff0c;即不同的关键字通过相同…

dxf怎么转换成PDF格式?转换方法其实很简单

PDF文件是一种可靠的文件格式&#xff0c;可以在各种操作系统和软件上打开和查看。而dxf是CAD文件的一种格式&#xff0c;打开它一般都是需要相关的操作软件才能打开&#xff0c;不是特别方便&#xff0c;将dxf文件转换成PDF格式就可以很好的解决这一问题&#xff0c;下面教大家…

Kafka - Primie Number of Partitions Issue Consumer Group Rebalance

文章目录 生产者&#xff1a;将数据写入 Kafka 的客户端。 消费者&#xff1a;从 Kafka 中读取数据的客户端。 Topic&#xff1a;Kafka 中用于组织和存储数据的逻辑概念&#xff0c;类似于数据库表。 Record&#xff1a;发送到 Topic 的消息称为 Record。 Partition&#x…

List有值二次转换给其他对象报null

List<PlatformUsersData> listData platformUsersMapper.selectPlatformUserDataById(data); users.setPlatformUsersData(listData);为什么listData 有值&#xff0c;users.getPlatformUsersData&#xff08;&#xff09;仍然为空在这段代码中&#xff0c;我们假设listD…

NLP(六十)Baichuan-13B-Chat模型使用体验

2023年7月11日&#xff0c;百川智能正式发布参数量130亿的通用大语言模型Baichuan-13B-Base、对话模型Baichuan-13B-Chat及其INT4/INT8两个量化版本。   本文将介绍大模型BaiChuan-13B-Chat的使用体验&#xff0c;其HuggingFace网址为&#xff1a;https://huggingface.co/bai…

【团队协作开发】IDEA中Git新建自己的dev工作分支,合并到master主分支教程(极其简单,新手)

文章目录 一、创建新dev工作分支二、push到自己的远程dev工作分支三、工作分支合并到master主分支1、先切换到master主分支2、将远程工作dev分支的内容merge到当前master分支中3、将merge提交到远程master分支 一、创建新dev工作分支 创建完新dev分支以后将默认切换到新dev分支…

FFmpeg5.0源码阅读—— avcodec_send_frame avcodec_receive_packet

摘要&#xff1a;本文主要描述了FFmpeg中用于编码的接口的具体调用流程&#xff0c;详细描述了该接口被调用时所作的具体工作。   关键字&#xff1a;ffmpeg、avcodec_send_frame、avcodec_receive_packet   读者须知&#xff1a;读者需要了解FFmpeg的基本使用流程&#xf…

如何理解自动化

目录 1.如何定义自动化 2.自动化给人类带来的福利 3.如何学习自动化 4.自动化潜在的危害 1.如何定义自动化 自动化是指利用计算机、机械、电子技术和控制系统等现代科学技术手段&#xff0c;对各种工业、商业、农业和日常生活中的操作和过程进行自动控制和执行的过程。它旨在…

Vc - Qt - 自定义ComboBox

示例代码创建了一个名为ComboBoxWidget的自定义QWidget类&#xff0c;并在initUI方法中创建了一个垂直布局。然后将一个只读的QLineEdit和一个QPushButton添加到布局中。当按钮被点击时&#xff0c;会调用showMenu方法&#xff0c;该方法创建一个QMenu并添加选项。每个选项连接…

CodeForces:Madoka and Underground Competitions

经过观察&#xff0c;发现只要延小区域 右上-左下 的对角线填满X即可&#xff0c;那么就是可以总结为满足(i j) % k (r c) % k #include <bits/stdc.h> using namespace std; int t; void solve(){int n, k, r, c;cin >> n >> k >> r >> c…

什么是搜索引擎?2023 年搜索引擎如何运作?

目录 什么是搜索引擎&#xff1f;搜索引擎的原理什么是搜索引擎爬取&#xff1f;什么是搜索引擎索引&#xff1f;什么是搜索引擎检索?什么是搜索引擎排序&#xff1f; 搜索引擎的目的是什么&#xff1f;搜索引擎如何赚钱&#xff1f;搜索引擎如何建立索引?网页抓取文本处理建…

【C++基础(五)】类和对象(上)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C初阶之路⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 类和对象-上 1. 前言2. 类的引入3. 类的定义4. 类的…

【人工智能】深度优先搜索、代价一致搜索、深度有限搜索、迭代深度优先搜索、图搜索

【人工智能】无信息搜索—BFS 、代价一致、DFS、深度受限、迭代深入深度优先、图搜索 什么是搜索 搜索问题是指既不能通过数学建模解决,又没有其他算法可以套用或者非遍历所有情况才能得出正确结果。这时就需要采用搜索算法来解决问题。搜索就是一种通过穷举所有解的状态,来…

iClient3D for CesiumWebGL入门之使用vscode以服务方式运行调试

作者&#xff1a;超图研究院技术支持中心-于丁 iClient3D for Cesium&WebGL入门之使用vscode以服务方式运行调试 相信大家第一次使用SuperMap iClient3D for Cesium或SuperMap iClient3D for WebGL的时候&#xff0c;都遇到过和我一样的事情&#xff1a; 在文件夹中直接打…

Docker网络模式

Docker网络模式 一、Docker网络实现原理二、Docker的网络模式1、host模式1.1 host模式原理1.2 host模式实操 2、Container模式2.1 模式原理 3、none模式4、bridger模式4.1 bridge模式的原理4.2 bridge实操 5、overlay模式6、自定义网络模式6.1 为什么需要自定义网络模式&#x…

03. 自定义镜像 Dockerfile

目录 1、前言 2、构建镜像的方式 2.1、docker commit 2.1.1、先查看下当前的容器 2.1.2、生成该容器镜像 2.1.3、查看镜像列表 2.2、Dockerfile 2.2.1、创建Dockerfile文件 2.2.2、编写Dockerfile文件 2.2.3、构建镜像 2.2.4、使用该镜像生成容器 3、Dockerfile 3…

FTP与HTTP: 哪种协议更适合大文件传输?

随着互联网技术的发展&#xff0c;网络传输已成为了现代社会中不可或缺的一部分。无论是文本、图像、音频、视频等各种类型的数据&#xff0c;相应的传输协议也在不断地发展和更新。FTP&#xff08;File Transfer Protocol&#xff09;和HTTP&#xff08;Hyper Text Transfer P…