【C++】list模拟实现list迭代器失效问题

list模拟实现&list迭代器失效问题

  • 一,list模拟实现
    • 1. list的主要框架接口模拟
    • 2. list构造&拷贝构造&析构
    • 3. list迭代器
      • 3.1 普通迭代器
      • 3.2 const迭代器
    • 4. 增删查改
  • 二,迭代器失效问题
    • 1. list的迭代器失效原因
    • 2. 解决办法

一,list模拟实现

在上节我们熟悉了list的底层结构和各个接口的含义后,下面我们来对list的主要接口进行模拟实现

1. list的主要框架接口模拟

list的底层是双向带头循环链表,所以我们先写一个节点的类,这个节点类也是一个类模板,由给定的数据类型生成相应的类

这个节点类可以写成结构体,用结构体是为了方便访问数据,不用单独写取数据接口

//list的节点
template<class T>
struct ListNode {ListNode* _prev;ListNode* _next;T _data;ListNode(const T& t = T()):_prev(nullptr),_next(nullptr),_data(t){}};

其次我们写list的基本框架,其成员变量我们可以用节点声明一个头结点,根据头结点的链接关系我们就可以知道其list存放的内容

template<class T>
class mylist {typedef ListNode<T> Node;//方便阅读
public://....
private:Node* _head;
};

2. list构造&拷贝构造&析构

list的构造有无参的构造,用n个值初始化构造,迭代器区间构造


我们这里实现无参的构造

void list_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;
}mylist() {list_init();
}

初始化时其头指针和外指针指向自己:
在这里插入图片描述


现在我们来实现拷贝构造

拷贝构造的实现我们和vector一样依次去插入即可。

mylist(mylist<T>& ml) {list_init();for (const auto e : ml) {push_back(e);}
}

重载operator=,这里我们可以转变一下思路,当传入一个list对象时,发生了拷贝构造,用拷贝构造的这个对象和当前要调用operator=的对象进行交换,再返回交换后的这个对象。

mylist& operator=(mylist lt)//在类中时可以省略模板参数swap(lt);return *this;
}

3. list迭代器

由于list的底层空间的不连续性,所以不能像string和vector一样直接用原生指针,我们要对其进行封装,使其可以像指针那样可以进行++或者–那样去使用。

template<class T>
struct List_Iterator {typedef ListNode<T> Node;typedef List_Iterator<T> self;Node* _node;//..
};

对于迭代器的构造函数,比较简单,可写可不写

template<class T>
struct List_Iterator {typedef ListNode<T> Node;typedef List_Iterator<T> self;Node* _node;List_Iterator(Node* n):_node(n){}//..
};

3.1 普通迭代器

我们先实现普通迭代器
既然要对这个类进行封装,那么就是为了让其可以进行++或者–等运算,所以我们需要对这些操作进行重载


重载operator++,有前置++和后置++,

//前置++
self& operator++() {_node = _node->_next;return  *this;
}//后置++
self operator++(int) {self tmp(*this);//拷贝构造_node = _node->_next;return tmp;
}

注:拷贝构造我们后面会实现


重载operator--和operator++类似,我们直接上代码

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

重载operator->,这是因为当list中存储的是其他结构体时,可以通过 it->_data 来直接访问其内容。

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

这里来举个具体的场景,看下面的代码:

struct Exp {int _a1 = 1;int _a2 = 2;Exp(int a1 = 1,int a2 = 2):_a1(a1),_a2(a2){}
};void test() {mylist<Exp> ex;ex.push_back(Exp(1, 2));mylist<Exp>::iterator it = ex.begin();while (it != ex.end()) {cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;++it;}
}

看到这段代码,你可能会一头雾水,为啥 it->_a1可以直接访问struct的成员变量,it->不是返回的是这个结构体的指针吗?

在这里插入图片描述
其实这里是省略了一个->,我们可以加一句代码来解释

struct Exp {int _a1 = 1;int _a2 = 2;Exp(int a1 = 1,int a2 = 2):_a1(a1),_a2(a2){}
};void test() {mylist<Exp> ex;ex.push_back(Exp(1, 2));mylist<Exp>::iterator it = ex.begin();while (it != ex.end()) {cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;cout << it.operator->()->_a1 << it.operator->()->_a2 << endl;++it;}
}

it->_a1中,it->取到的是结构体对象的地址,其后面应该再用一个->访问到其中的变量,但是后面的箭头被省略了,这里也是C++为了方便使用而做的处理。


operator*返回的是T& 减少拷贝

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

3.2 const迭代器

实现了普通迭代器,现在我们终于要实现const迭代器了,现在思考一下,普通迭代器和const迭代器区别在哪,其实const迭代器的区别就是operator*和operator->的返回值加上const。

试想一下,如果我们像vector一样只在普通迭代器前面直接加一个const。我们直接再将普通迭代器的代码拷贝一份,在operator*和operator->返回值前加上const,其余代码不变,也可以实现const迭代器。但是这样写代码难免有些冗余,如果相同的场景下要拷贝的代码有上千万行呢,难道也要拷贝吗?
在这里插入图片描述

所以这里我们只需在模板参数中添加两个参数Ref,Ptr。分别代表 operator和operator->的返回值,当传入的是const T或者const T&时,迭代器类会返回相应的类型。具体在list的类中再对const迭代器进行封装。

template<class T,class Ref,class Ptr>
struct List_Iterator {//typedef ListNode<T> Node;typedef List_Iterator<T,Ref,Ptr> self;Node* _node;List_Iterator(Node* n):_node(n){}//前置++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;}Ref operator* () {return _node->_data;}Ptr operator->() {return &_node->_data;}bool operator==(const self& s) {return _node == s._node;}bool operator!=(const self& s) {return _node != s._node;}};template<class T>
class mylist {typedef ListNode<T> Node;//方便阅读public:typedef List_Iterator<T,T&,T*> iterator;//写成公有,类外也可以访问typedef List_Iterator<T,const T&,const T*> const_iterator;//....
}

在list类中如何用用到iterator类呢,下面我们来实现一下:

iterator begin() {return _head->_next;//单参数的构造函数支持隐式类型转换,
}iterator end() {return _head;//迭代器区间是左闭右开,end指向最后一个元素的下一个位置
}

4. 增删查改

list的插入删除比较简单,因为不用考虑数据挪动的问题,只要创建或者删除新的节点,改变前后节点的指针即可。


insert

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

erase可能会造成迭代器失效的问题,具体原因和解决办法我们在下面讲解。

iterator erase(iterator pos) {assert(pos != end());//会发生隐式类型转换,将指针类型转换成iterator类Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;cur = nullptr;return next;//迭代器失效,返回删除位置的下一个位置
}

push_back()和push_front和pop_back()和pop_front的实现可以直接复用insert,在末尾位置插入即可

void push_back(const T& t) {insert(end(), t);
}void push_front(const T& t) {insert(begin(), t);
}
void pop_back() {erase(--end());
}void pop_front() {erase(begin());
}

二,迭代器失效问题

1. list的迭代器失效原因

list的插入不会像vector造成迭代器失效,因为vector一旦发生扩容,其所有位置的迭代器都会失效,但是list不用扩容,当有元素插入时才创建节点,再链接到链表中。
但是list的删除会造成迭代器失效,因为删除一个节点后,这个节点的迭代器位置实际上已经不存在了,再继续使用则会出错,除非下次使用时再给其赋值。

2. 解决办法

解决办法就是让删除后返回被删除节点的下一个节点的迭代器,下面是正确的使用场景,

void Test()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); // it = l.erase(it);}
}

list的模拟实现部分我们讲解完了,希望大家看了后会有所收获,期待更多的C++相关的知识请大家三连一波哦
在这里插入图片描述

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

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

相关文章

想到2024年最有前景的副业创业机会?那这个项目你绝对不能错过!

大家好&#xff0c;我是电商花花。 现在看到别人要么都有自己的副业&#xff0c;要么都在做创业项目&#xff0c;你是不是也看的激情澎湃&#xff0c;想要做抖音小店&#xff0c;想要创业赚钱。 2024年做什么副业、创业有盼头&#xff1f;我觉得还得是抖音小店无货源这个电商…

Cookie、Session、Token、JWT

文章目录 1. Cookie1.1 为什么需要Cookie1.2 Cookie是什么1.3 Cookie机制 2. Session2.1 为什么需要Session2.2 Session机制 3. Token3.1 Token简介3.2 Token和Session的区别 4. JWT4.1 JWT简介4.2 JWT的数据结构与使用 5. Gin框架中JWT的使用5.1 安装5.2 使用 1. Cookie 1.1 为…

【滑动窗口】力扣239.滑动窗口最大值

前面的文章我们练习数十道 动态规划 的题目。相信小伙伴们对于动态规划的题目已经写的 得心应手 了。 还没看过的小伙伴赶快关注一下&#xff0c;学习如何 秒杀动态规划 吧&#xff01; 接下来我们开启一个新的篇章 —— 「滑动窗口」。 滑动窗口 滑动窗口 是一种基于 双指…

Matlab|配电网智能软开关(sop)规划模型

目录 1 主要内容 目标函数 2 部分程序 3 程序结果 3.1 sop选址定容优化模型 3.2 对比算例&#xff08;不含sop&#xff09; 4 下载链接 1 主要内容 该程序参考文献《基于改进灵敏度分析的有源配电网智能软开关优化配置》&#xff0c;采用二阶锥算法&#xff0c;以改进的…

动态调整html表格每列宽度

为什么想自动计算列宽呢&#xff1f;因为我有一次拿到一个项目&#xff0c;它里面的列宽都是写死的。后来需要改&#xff0c;一个个的改太麻烦了。 诸如这样的表格在网站上非常常见。我们不对列做设置的话&#xff0c;列宽就会取每列文本内容的最大长度。在只有一条文本非常长…

ubuntu安装开源汇编调试器NASM

安装 安装很简单&#xff0c;直接在终端输入以下命令即可 sudo apt-get install nasm 安装完成后&#xff0c;如果可以查看到nasm的版本号即可视为安装成功 nasm -version 测试 创建汇编文件 创建一个asm文件 vim hello.asm 文件内容如下 section .datahello: db …

Linux随记(八)

一、crontab运行shell脚本&#xff0c;py脚本 &#xff08;注意事项&#xff09; 情景描述&#xff1a; 目前有个sh脚本他最初大致内容是。 cat t11.sh#!/bin/bash source /etc/profile /bin/python3 /tmp/1.py sh /tmp/1.sh echo -e "$(date %F)" >…

LeetCode148题:排序链表(python3)

在数组排序中&#xff0c;常见的排序算法有&#xff1a;冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等。 而对于链表排序而言&#xff0c;因为链表不支持随机访问&#xff0c;访问链表后面的节点只能依靠 next 指针从头…

2024-03-05 linux 分区老显示满,Use 100%,原因是SquashFS 是一种只读文件系统,它在创建时就已经被填满,所有空间都被使用。

一、这两天一直纠结一个问题&#xff0c;无论怎么修改&#xff0c;linux 分区老显示满&#xff0c;Use 100%&#xff0c;全部沾满。如下图的oem分区。 二、导致出现上面的原因是&#xff1a;SquashFS文件系统里的空间利用率总是显示为100%。 三、SDK里面也说明SquashFS文件系统…

昏暗场景增强-低照度增强-弱光增强(附代码)

引言 随着现代科技的发展&#xff0c;图像采集设备已经渗透到生活的方方面面&#xff0c;然而在昏暗场景、低照度或弱光条件下&#xff0c;图像的质量往往受到严重影响&#xff0c;表现为亮度不足、对比度低下、色彩失真以及细节丢失等问题。这类图像对于人眼识别和计算机视觉…

基于机器学习的网络入侵检测与特征选择及随机森林分类器性能评估(NSL-KDD数据集)

简介 本文将详细介绍如何利用Python和相关机器学习库对NSL-KDD数据集进行预处理&#xff0c;特征选择&#xff0c;并通过随机森林算法构建网络入侵检测模型。同时&#xff0c;还将展示如何计算并可视化模型的ROC曲线以评估其性能。 首先&#xff0c;我们导入了必要的库&#…

Java服务器-Disruptor使用注意

最近看了一下部署后台的服务器状况&#xff0c;发现我的一个Java程序其占用的CPU时长超过100%&#xff0c;排查后发现竟是Disruptor引起的&#xff0c;让我们来看看究竟为什么Disruptor会有这样的表现。 发现占用CPU时间超过100%的进程 首先是在服务器上用top命令查看服务器状…