【C++】list模拟实现(完结)

1.普通迭代器(补充)

1.1 后置++和后置--

我们迭代器里面实现了前置++和前置--,还需要实现后置++后置--

 在list.h文件的list_iterator类里面实现。

//后置++/--
Self& operator++(int)
{Self tem(*this);//保存原来的值_node = _node->_next;return tem;
}
Self& operator--(int)
{Self tem(*this);//保存原来的值_node = _node->_prev;return tem;
}

1.2 重载->

因为迭代器模拟的是指针的行为,所以这里我们还要重载一下->。

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

这个->的应用场景如下。

现在有个AA的类或结构体。

struct AA
{int _a = 1;int _b = 2;
};

list里面存这个AA类型的数据。

void test5()
{list<AA> la;}

然后给里面随便存几个数据。

list<AA> la;
la.push_back(AA());
la.push_back(AA());
la.push_back(AA());
la.push_back(AA());

再用迭代器遍历,此时就要用->来引出AA里面的_a或者_b,不能用.引出。

list<AA>::iterator ia = la.begin();
while (ia != la.end())
{cout << ia->_a << " " << ia->_b << endl;++ia;
}

完整地写法如下。

cout << ia.operator->()->_a << " " << ia.operator->()->_b << endl;

第一个箭头是运算符重载的->,第二个_a前面的->就是没有重载的解引用符号。

为了可读性,省略了一个箭头,这里省略的也是第二个->。 

2.const迭代器

2.1 const_iterator

const修饰迭代器,我们不可以对迭代器指向的内容进行修改,但是迭代器可以改变自己的指向

我们如何实现迭代器可以改变自己指向而不让改变指向内容?

我们修改迭代器指向的内容通过什么修改?通过迭代器类里面的两个运算符重载,operator*operator->, 如果我们现在把operator*和operator->的返回值用const修饰,就不能通过他们两个修改指向内容了。

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

但是我们不可以直接把list_iterator里面的这两个成员函数给改了,改了的话普通迭代器就不能改了,我们只要const迭代器不能改。所以,我们重新实现一个类,叫list_const_iterator,这个类除了前面两个运算符重载函数变,其他成员函数一律不变

list.h文件的namespace里实现。

template<class T>
struct list_const_iterator
{typedef list_node<T> Node; //换个短的名字typedef list_const_iterator<T> Self;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*() {return _node->_data;}const T* operator->(){return &_node->_data;}//前置++/--Self& operator++() //重载++{_node = _node->_next;//加到下一个节点return *this;//返回自己}Self& operator--() //重载--{_node = _node->_prev;//减到前一个节点return *this;//返回自己}//后置++/--Self& operator++(int){Self tem(*this);//保存原来的值_node = _node->_next;return tem;}Self& operator--(int){Self tem(*this);//保存原来的值_node = _node->_prev;return tem;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};

同时,我们要在list类里面,public部分加上下面这句话。

typedef list_const_iterator<T> const_iterator;

以及迭代器的beginend接口。

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

 const迭代器部分的测试我们和接下来要说的打印函数一起测。

2.2 print_container 打印

这个函数就是为了方便我们打印数据。

template<class container>
void print_container(const container& c)
{for (auto e : c){cout << e << " ";}cout << endl;
}

test.cpp中对const迭代器和print_container一起测试。

void test6()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.print_container(lt);
}

3.优化两种迭代器的实现

很容易发现,我们前面的实现方式代码过于冗余,list_iterator 和 list_const_iterator两个类重叠部分太多。于是,我们就需要用到类模板,来优化一下。

list_iterator类的基础上优化。

我们增加两个模板参数,Ref和Ptr。

Ref是引用,或者是const引用;Ptr是地址,或者const地址。

 然后把operator*返回值类型换成Ref,operator->返回值类型换成Ptr。

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

list类里面的如下部分也要改。

就可以了,迭代器的类模板就实现好了。 

我们用前面的测试样例在test.cpp测试一下。

4.迭代器失效问题

list的迭代器失效在insert插入数据是按理来说应该是不会发生的,因为这里没有扩容的的问题。

但是删除数据就有迭代器失效的问题,迭代器失效问题更加详细具体的分析在【C++】vector模拟实现、迭代器失效问题(超详解)

list这里如果删除一个节点,迭代器绝对就是类似野指针了。

解决方案还是对erase进行改造,让erase有返回值,erase返回的是删除结点的下一个节点位置。

iterator erase(iterator pos)
{assert(pos != end());//不可以删哨兵位头节点Node* prev = pos._node->_prev;//存pos前后节点Node* next = pos._node->_next;prev->_next = next;//链接pos前后节点next->_prev = prev;delete pos._node;//释放pos节点--_size;return next;
}

test.cpp中测试一下。我们删除所有偶数。

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);
auto it = lt.begin();
while (it != lt.end())
{if (*it % 2 == 0){it = lt.erase(it);}else{++it;}
}
lt.print_container(lt);

 所以list的迭代器失效问题就简单很多。

但是为了和库里面保持一致,我们还是对insert也改进一下,让他也有返回值。

iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//连接起来newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;return newnode;//返回插入的节点
}

5.析构函数

5.1 clear

清空所有数据,但是不清除哨兵位头节点。

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

5.2 析构函数

析构函数可以套用clear,clear不清除哨兵位,析构这里另外清除一下就可以了。

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

6.构造函数(优化)

6.1 空链表初始化

我们重新写一个函数,用来初始化空的list。空的list就是只有哨兵位头节点

void empty_init()
{_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;
}

这个代码和list默认构造代码一模一样。这样实现主要是为了方便后面的拷贝构造。

6.2 默认构造

我们现在默认构造的代码就直接复用前面写的empty_init。

list()
{empty_init();
}

6.3 拷贝构造

这里的拷贝构造依然是复用push_back实现。

list(const list<T>& lt)
{for (auto& e : lt){push_back(e);}
}

比如说我们用lt1拷贝构造lt2 

但是此时的lt2连哨兵位都没有,这样写肯定有问题。所以我们要加上前面写的empty_init。

//lt2(lt1)
list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}

 

 在test.cpp里对前面的三个一起测试。

void test8()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);//拷贝构造lt1.print_container(lt1);lt2.print_container(lt2);
}

 

7.operator= 赋值运算符重载

这里的赋值运算符重载,也是要深拷贝,所以我们还是采用现代写法,现代写法详解在【C++拓展】深拷贝现代写法 

首先自己写一个swap。

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

然后再交换。

list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

test.cpp中测试一下。

list<int> lt1, lt2;
lt1.push_back(1);
lt1.push_back(2);
lt2.push_back(3);
lt2.push_back(4);
lt1.print_container(lt1);
lt2.print_container(lt2);
lt2 = lt1; //赋值
lt1.print_container(lt1);
lt2.print_container(lt2);

 

list的内容就结束了,感谢观看,下篇再见~

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

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

相关文章

基于Python的飞机大战复现

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

MR30分布式 IO 模块在冷却水泵系统中的卓越应用

在当今各类工业生产以及大型设施运行的场景中&#xff0c;冷却水泵系统起着至关重要的作用&#xff0c;它犹如保障整个运转体系顺畅运行的 “血液循环系统”&#xff0c;维持着设备适宜的温度环境&#xff0c;确保其稳定、高效地工作。而随着科技的不断发展&#xff0c;明达技术…

银河麒麟桌面系统——桌面鼠标变成x,窗口无关闭按钮的解决办法

银河麒麟桌面系统——桌面鼠标变成x&#xff0c;窗口无关闭按钮的解决办法 1、支持环境2、详细操作说明步骤1&#xff1a;用root账户登录电脑步骤2&#xff1a;导航到kylin-wm-chooser目录步骤3&#xff1a;编辑default.conf文件步骤4&#xff1a;重启电脑 3、结语 &#x1f49…

多线程常见问题集

一、多线程预防和避免线程死锁 如何预防死锁&#xff1f; 破坏死锁的产生的必要条件即可&#xff1a; 破坏请求与保持条件&#xff1a;一次性申请所有的资源。破坏不剥夺条件&#xff1a;占用部分资源的线程进一步申请其他资源时&#xff0c;如果申请不到&#xff0c;可以主动释…

Java ArrayList 与顺序表:在编程海洋中把握数据结构的关键之锚

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 前言&#xff1a;在 Java编程的广袤世界里&#xff0c;数据结构犹如精巧的建筑蓝图&#xff0c;决定着程序在数据处理与存储时的效率、灵活性以…

【第三方云音乐播放器SPlayer本地安装结合内网穿透打造个性化远程音乐库】

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 前言1. 安装Docker2. 创建并启动Splayer容器3. 本地访问测试4. 公网远程访问本地Splayer4.1 内网穿…

easyui combobox 只能选择第一个问题解决

easyui combobox 只能选择第一个问题解决 问题现象 在拆分开票的时候&#xff0c;弹出框上面有一个下拉框用于选择需要新增的明细行&#xff0c;但是每次只能选择到第一个 选择第二条数据的时候默认选择到第一个了 代码如下 /*新增发票编辑窗口*/function addTicketDialog…

从零开始:Linux 环境下的 C/C++ 编译教程

个人主页&#xff1a;chian-ocean 文章专栏 前言&#xff1a; GCC&#xff08;GNU Compiler Collection&#xff09;是一个功能强大的编译器集合&#xff0c;支持多种语言&#xff0c;包括 C 和 C。其中 gcc 用于 C 语言编译&#xff0c;g 专用于 C 编译。 Linux GCC or G的安…

transformer.js(三):底层架构及性能优化指南

Transformer.js 是一个轻量级、功能强大的 JavaScript 库&#xff0c;专注于在浏览器中运行 Transformer 模型&#xff0c;为前端开发者提供了高效实现自然语言处理&#xff08;NLP&#xff09;任务的能力。本文将详细解析 Transformer.js 的底层架构&#xff0c;并提供实用的性…

STM32 Keil5 attribute 关键字的用法

这篇文章记录一下STM32中attribute的用法。之前做项目的时候产品需要支持远程升级&#xff0c;要求版本只能向上迭代&#xff0c;不支持回退。当时想到的方案是把版本号放到bin文件的头部&#xff0c;设备端收到bin文件的首包部数据后判断是否满足升级要求&#xff0c;这里就可…

aws服务--机密数据存储KMS(1)介绍和使用

在AWS(Amazon Web Services)中存储机密数据时,安全性和合规性是最重要的考虑因素。AWS 提供了多个服务和工具,帮助用户确保数据的安全性、机密性以及合规性。AWS Secrets Manager、KMS(Key Management Service)是推荐的存储机密数据的AWS服务和最佳实践。这里先看KMS。 …

51c~C语言~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12652943 一、嵌入式开发中的C语言编译器 如果你和一个优秀的程序员共事&#xff0c;你会发现他对他使用的工具非常熟悉&#xff0c;就像一个画家了解他的画具一样。----比尔.盖茨1 不能简单的认为是个工具 嵌入式程序开发…

ensp静态路由实验

一、实验目的 1、熟练掌握交换机的基本配置命令 2、熟练掌握静态路由的使用方法 3. 熟练掌握交换机端口模式 二、实验内容 需求&#xff1a; 根据要求利用现有实验设备组建小型局域网 实验设备&#xff1a; 交换机S37002台&#xff1b;PC机2台&#xff1b;路由器2台。 …

深度学习3

五、自动微分 1、基础概念 模块 autograd 负责自动计算张量操作的梯度&#xff0c;具有自动求导功能&#xff1b;autograd 创建一个动态计算图来跟踪张量的操作&#xff0c;每个张量是计算图中的一个节点&#xff0c;节点之间的操作构成图的边。 属性 requires_grad 决定…

路由器中继与桥接

一 . 背景 现在的路由器大多数已经开始支持多种网络连接模式&#xff0c;以下将以TP-Link迷你无线路由器为例进行展开介绍。在TP-Link迷你无线路由器上一般有AP&#xff08;接入点&#xff09;模式&#xff0c;Router&#xff08;无线路由&#xff09;模式&#xff0c;Repeate…

人工智能|计算机视觉——微表情识别(Micro expression recognition)的研究现状

一、简述 微表情是一种特殊的面部表情,与普通的表情相比,微表情主要有以下特点: 持续时间短,通常只有1/25s~1/3s;动作强度低,难以察觉;在无意识状态下产生,通常难以掩饰或伪装;对微表情的分析通常需要在视频中,而普通表情在图像中就可以分析。由于微表情在无意识状态…

嵌入式系统与OpenCV

目录 一、OpenCV 简介 二、嵌入式 OpenCV 的安装方法 1. Ubuntu 系统下的安装 2. 嵌入式 ARM 系统中的安装 3. Windows10 和树莓派系统下的安装 三、嵌入式 OpenCV 的性能优化 1. 介绍嵌入式平台上对 OpenCV 进行优化的必要性。 2. 利用嵌入式开发工具&#xff0c;如优…

React(五)——useContecxt/Reducer/useCallback/useRef/React.memo/useMemo

文章目录 项目地址十六、useContecxt十七、useReducer十八、React.memo以及产生的问题18.1组件嵌套的渲染规律18.2 React.memo18.3 引出问题 十九、useCallback和useMemo19.1 useCallback对函数进行缓存19.2 useMemo19.2.1 基本的使用19.2.2 缓存属性数据 19.2.3 对于更新的理解…

STM32设计学生宿舍监测控制系统-分享

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 本项目旨在利用STM32单片机为核心&#xff0c;结合传感器技术、无线通信技…

华为无线AC+AP组网实际应用小结

之前公司都是使用的H3C的交换机、防火墙以及无线AC和AP的&#xff0c;最近优化下无线网络&#xff0c;说新的设备用华为的&#xff0c;然后我是直到要部署的当天才知道用华为设备的&#xff0c;就很无语了&#xff0c;一点准备没有&#xff0c;以下为这次的实际操作记录吧&…