STL库 —— list 的编写

目录

一、成员变量

​编辑

二、push_back 函数

三、迭代器 iterator

3.1 iterator 结构体

3.2 begin() 与 end() 函数

3.3 iterator 运算符重载

3.4 -> 的重载

3.5 const_iterator

四、测试代码

五、修饰符成员

5.1 insert 函数

5.2 erase 函数

5.3 push 函数

5.4 pop 函数

六、容量成员

6.1 size 函数

6.2 empty 函数

七、模板优化iterator


一、成员变量

	template<class T>struct ListNode{T _data;ListNode<T>* _prev;ListNode<T>* _next;ListNode(const T& x = T()):_data(x),_prev(nullptr),_next(nullptr){}};template<class T>class my_list{typedef ListNode<T> Node;private:Node* _head;//哨兵位头结点};

		my_list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}

二、push_back 函数

为了测试方便,我们在这里先把 push_back 写出来。

		void push_back(T data){Node* NewNode = new Node(data);NewNode->_next = _head;NewNode->_prev = _head->_prev;_head->_prev->_next = NewNode;_head->_prev = NewNode;}

三、迭代器 iterator

3.1 iterator 结构体

这里的迭代器和之前的迭代器有所不同,诸如 string 、 vector ,他们的内存空间是连续的,迭代器的底层都是基于指针的,而 list 中,由于存储单元的随机性,迭代器不可以再是单纯的指针,而是像上面那样的结构体对象:

    template<class T>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T> iterator;Node* _node;ListIterator(Node* node)//构造函数{_node = node;}};

3.2 begin() 与 end() 函数

然后,需要在 my_list 类中新添 begin 和 end 函数,因为他们都是服务于迭代器,所以他们的返回值类型应该是迭代器,还要注意,根据 STL 通用设计模式, end() 指向的位置不用于存放有效数据元素,而是作为遍历结束的标记。所以 end() 函数可以直接返回通过 _head 构造的迭代器,以此表示链表的末尾。

    template<class T>class my_list{typedef ListNode<T> Node;public:typedef ListIterator<T> iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}private:Node* _head;};

3.3 iterator 运算符重载

随后,为了使迭代器可以跑起来,需要再对 ++ 、 -- 、 != 等运算符进行重载,
其中,为了保持与内建类型行为一致,内建类型(如intfloat等)的前置自增和自减操作返回的是引用。自定义类型(如迭代器)模拟内建类型的行为,包括操作符的使用,有助于保持语言的一致性和直观性。
后置自增和自减不返回其引用的原因与前置自增自减的设计意图及使用方式有关。后置自增操作符的主要特点是先返回对象当前的值(在自增之前的状态),然后再对对象进行自增操作。这一行为决定了其返回类型应该是对象的值而不是引用
顺便,我们可以把 == 和 * 运算符都重载:

		typedef ListIterator<T> iterator;iterator& operator++()//前置++{_node = _node->_next;return *this;}iterator operator++(int)//后置++{iterator tmp(*this);_node = _node->_next;return tmp;}iterator& operator--()//前置--{_node = _node->_prev;return *this;}iterator operator--(int)//后置--{iterator tmp(*this);_node = _node->_prev;return tmp;}T& operator*(){return _node->_data;}bool operator!=(const iterator& it){return  _node != it._node;}bool operator==(const iterator& it){return _node == it._node;}

3.4 -> 的重载

当我们创建一个自定义类型,如 class A(此时在Flash命名空间内):

	class A{public:int a = 0;int b = 0;};

去测试 A 类型的 list 时,虽然 push_back 可以用,但是我们想遍历链表时,迭代器却不能用了:

	void test(){my_list<A> la;la.push_back({ 6, 12 });la.push_back({ 7, 18 });la.push_back({ 3, 22 });my_list<A>::iterator it = la.begin();while (it != la.end()){cout << *it << " ";it++;}cout << endl;}


错误的原因在于,系统不支持自定义类型的流插入,这里有两种解放方案:
1.重新重载一个 << 运算符
2.改变输出的格式 cout << (*it).a << (*it).b << " ";

		while (it != la.end()){cout << (*it).a << (*it).b << " ";it++;}

 但是究其本源,迭代器模拟的还是指针,如果是指针的话,就可以通过 -> 读取指针指向空间的数据,所以这里可以重载 -> 来达到模拟指针目的。

		T* operator->(){return &_node->_data;//->的优先级更高}

3.5 const_iterator

为了保证迭代器的内容不被修改,我们还要为迭代器添加 const ,但是根据所学知识,仅在函数返回值前加 const 是不能满足需求的,这只能保证迭代器本身不能被修改,此时就不可能自增和自减了,所以要新建一个 const_iterator 类,来模拟实现 const T* 指针的行为。

其中只有 operator* 与 operator-> 返回值不同。

template<class T>struct ListConstIterator{typedef ListNode<T> Node;typedef ListConstIterator<T> const_iterator;Node* _node;ListConstIterator(Node* node):_node(node){}// *itconst T& operator*(){return _node->_data;}// it->const T* operator->(){return &_node->_data;}// ++itconst_iterator& operator++(){_node = _node->_next;return *this;}const_iterator operator++(int){const_iterator tmp(*this);_node = _node->_next;return tmp;}const_iterator& operator--(){_node = _node->_prev;return *this;}const_iterator operator--(int){const_iterator tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};

此时在 my_list 类中在添加 cbegin 和 cend 函数:

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

四、测试代码

有了之前的代码,我们就可以进行统一的测试:

    void test_push_back(){my_list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(5);my_list<int>::iterator it = l1.begin();while (it != l1.end()){cout << *it << endl;it++;}}

五、修饰符成员

上面的代码就可以让我们基本理清 list 的行为,有助于写下面的函数。

5.1 insert 函数

库中的 insert 支持的是迭代器访问,此时 pos 是上面的迭代器结构体, pos._node 才是此时要访问的节点。pos 位置前的节点的下一个节点是新插入的节点,pos位置是新插入节点后面的节点。

		void insert(iterator pos, T data){Node* NewNode = new Node(data);Node* prev = pos._node->_prev;Node* cur = pos._node;//节点位置: prev Newnode curprev->_next = NewNode;NewNode->_prev = prev;NewNode->_next = cur;cur->_prev = NewNode;}

5.2 erase 函数

erase 函数更加简单,其不需要创建新节点,仅需控制指针的指向即可。

		void erase(iterator pos){Node* prev = pos._node->_prev;Node* next = pos._node->_next;//prev pos nextprev->_next = next;next->_prev = prev;delete pos._node;}

5.3 push 函数

有了 insert 函数, push_back 函数就可以直接复用,因为是尾插,且传入的 pos 位置是插入位置的下一个节点, end 函数指向的是哨兵位节点,所以 pos 直接取到 end 即可。

		void push_back2(T data){insert(end(), data);}

同 push_back 使用 end 作为 pos 一样, begin 返回的是哨兵位的下一个节点,所以传入位置直接取到 begin 即可。

		void push_front(T data){insert(begin(), data);}

5.4 pop 函数

因为没有重载运算符 - ,所以 pop_back 传值时只能使用自减操作, end 指向的是哨兵位,所以需要使用 end 前面的位置,标准库中并没有实现减操作的重载,且效率很低,所以没有必要手搓。

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

六、容量成员

6.1 size 函数

`std::list`的`size()`成员函数返回容器中元素的数目。在C++11及之后的标准中,`std::list::size()`的时间复杂度为常数时间O(1)。这是通过内部维护一个表示元素数量的计数器来实现的。
每当添加或删除元素时,这个计数器就会相应地更新。
因此,`size()`函数的实现通常看起来像这样:

        size_t size() const {return _size;  // _size是一个成员变量,用来追踪当前链表中元素的数量}

这里的`_size`是容器的一个私有成员变量,其在构造函数中初始化,在向容器添加元素或从容器中移除元素时被更新。

但是要注意,在使用复用函数时,如上的 "push_front" "pop_back" 时, _size 不需要进行自增或自减操作,这样可能会自增自减两次,导致错误。

6.2 empty 函数

		bool empty(){return _size == 0;}

七、模板优化iterator

上面的 iterator 与 const_iterator 重复度太高,此时,就可以使用模板进行优化,让编译器根据传入的参数进行实例化:

template<class T, class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> iterator;Node* _node;};

 与之前学的模板不同的是,传入的参数增加了,传入的参数变成了引用和指针:

    template<class T>class my_list{typedef ListNode<T> Node;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;}

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

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

相关文章

WordPress关注公众号可见内容插件源码

简介&#xff1a; WordPress公众号引流工具——关注公众号可见内容插件推荐 通过关注微信公众号&#xff0c;获取随机验证码从而获得隐藏文本的访问权限。 插件特点 隐藏内容扫码关注获取验证码 可以作为引流公众号 支持无必须API接口&#xff0c;无备案域名也可以 自定义…

先进电机技术 —— 步进电机控制综述

一、背景 随着自动化技术的发展和精密控制需求的增长&#xff0c;步进电机作为一种重要的执行元件在众多领域展现出了卓越的性能优势。步进电机&#xff0c;又称为步进驱动器或步进马达&#xff0c;是一种能够将电脉冲信号精确转换为角位移或直线位移的特殊电动机。其工作原理…

信息系统项目管理师0044:IT治理方法与标准(3信息系统治理—3.1 IT治理—3.1.4 IT治理方法与标准)

点击查看专栏目录 文章目录 3.1.4 IT治理方法与标准1. ITSS中1T服务治理 3.1.4 IT治理方法与标准 考虑到IT治理对组织战略目标达成的重要性&#xff0c;国内外各类机构持续研究并沉淀IT治理相关的最佳实践方法、定义相关标准&#xff0c;这里面比较典型的是我国信息技术服务标准…

Leetcode算法训练日记 | day18

一、找树左下角的值 1.题目 Leetcode&#xff1a;第 513 题 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1示例 2: 输入: [1,2,3,4,null,5,6,null,null,7] 输出:…

Python:六大数据类型理论与示例

在这篇文章中&#xff0c;我们深入探讨了Python中的六大基本数据类型&#xff1a;数字&#xff08;Number&#xff09;、字符串&#xff08;String&#xff09;、列表&#xff08;List&#xff09;、元组&#xff08;Tuple&#xff09;、集合&#xff08;Set&#xff09;、字典…

Hive的分区与排序

一、Hive分区 1.引入&#xff1a; 在大数据中&#xff0c;最常见的一种思想就是分治&#xff0c;我们可以把大的文件切割划分成一个个的小的文件&#xff0c;这样每次操作一个个小的文件就会很容易了&#xff0c;同样的道理&#xff0c;在hive当中也是支持这种思想的&#xff…

UE4 避免布料模拟重置后抖动

问题&#xff1a;每次设置带布料模拟的布料新位置&#xff0c;就会发生突然的抖动 解决办法&#xff1a;给“布料混合权重”或“布料最大距离缩放”K帧&#xff0c;参考数值为0.2—1&#xff08;红框内的值都试过无法解决&#xff09;

JVM性能调优——运行时参数

文章目录 1、JVM参数选项类型1.1、标准参数选项1.2、非标准参数选项1.3、非稳定参数选项 2、添加JVM参数的方式3、常用JVM参数选项4、通过Java代码获取JVM参数5、小结 熟悉JVM参数对于系统调优是非常重要的。比如一个高流量的延迟的电子交易平台&#xff0c;它要求的响应时间都…

头歌机器学习实验 第7次实验 局部加权线性回归

任务描述 本关任务&#xff1a;编写一个利用局部加权计算回归系数的小程序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.局部加权算法的思想&#xff1b;2.局部加权的核心算法。 局部加权算法的思想 在局部加权算法中 &#xff0c;我们给待预测点附近…

【mT5多语言翻译】之六——推理:多语言翻译与第三方接口设计

请参考本系列目录&#xff1a;【mT5多语言翻译】之一——实战项目总览 [1] 模型翻译推理 在分别使用全量参数微调和PEFT微调训练完模型之后&#xff0c;我们来测试模型的翻译效果。推理代码如下&#xff1a; # 导入模型 if conf.is_peft:model AutoModelForSeq2SeqLM.from_pr…

为什么需要SOCKS代理?

在数字化时代&#x1f310;&#xff0c;随着网络安全威胁的不断演进和增加&#xff0c;保护个人隐私和数据安全成为了互联网用户的一大挑战&#x1f6e1;️。在寻求增强在线安全和隐私的解决方案时&#xff0c;SOCKS代理成为了一个关键的技术工具&#x1f511;。本文旨在详细探…

python如何输入多行

Python中的Input()函数在输入时&#xff0c;遇到回车符&#xff0c;那么一次输入就结束了。这不能满足输入多行文本并且行数也不确定的情形&#xff0c;当然输入空行也是允许的。 方法1&#xff1a;利用异常处理机制实现 lines[] while True:try:lines.append(input())except:…

JSON三种数据解析方法

文章目录 一、什么是JSON数据&#xff1f;二、JSON解析方法2.1、原生解析2.2、Gson解析2.3、FastJson解析 四、总结 一、什么是JSON数据&#xff1f; 先看下面&#xff0c;这里有一段JSON数据&#xff0c;我们根据这段数进行讲解&#xff1a; {"paramz": {"fe…

springboot+vue全栈开发【1.准备工作篇】

目录 前言环境配置关于Maven 前言 为了坚持每天写博客所以开了这个系列~~ 环境配置 1.配置java环境&#xff0c;下载jdk&#xff0c;配置环境变量 这一步有很多教程&#xff0c;不赘述了 2.安装idea 关于Maven Maven是一个项目管理工具&#xff0c;可以对java项目进行自动化…

Harmony与Android项目结构对比

主要文件对应 Android文件HarmonyOS文件清单文件AndroidManifest.xmlmodule.json5Activity/Fragmententryability下的ts文件XML布局pages下的ets文件resresourcesModule下的build.gradleModule下的build-profile.json5gradlehvigor根目录下的build.gradle根目录下的build-profi…

Linux——fork复制进程

1)shell: 在计算机科学中&#xff0c;Shell俗称壳&#xff08;用来区别于核&#xff09;&#xff0c;是指“为使用者提供操作界面”的软件&#xff08;command interpreter&#xff0c;命令解析器&#xff09;。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令&…

【Locust分布式压力测试】

Locust分布式压力测试 https://docs.locust.io/en/stable/running-distributed.html Distributed load generation A single process running Locust can simulate a reasonably high throughput. For a simple test plan and small payloads it can make more than a thousan…

30岁《爱·回家》小花多次得罪高层,正式宣布离巢TVB。

30岁的苏韵姿&#xff08;Andrea&#xff09;16年选港姐入行&#xff0c;虽然无三甲名次&#xff0c;但靠着皇后大学戏剧学士学位背景&#xff0c;她很快已有机会入剧组&#xff0c;凭《爱回家之开心速递》熊心如&#xff08;红衫鱼&#xff09;一角成功入屋&#xff0c;不过去…

Mongodb入门--头歌实验MongoDB 数据库基本操作

一、数据库创建 任务描述 本关任务&#xff1a;创建数据库。 相关知识 本关评测是在 Linux 环境下进行的&#xff0c;MongoDB 的安装与配置测评系统均已默认完成。 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 1.如何连接数据库&#xff1b; 2.如何创建数据库。 连接数…

双云及多云融合(混合云)

背景&#xff1a;客户对于业务的高可用需求&#xff0c;当发生故障时&#xff0c;业务还能正常使用&#xff0c;如某云机房整体宕机&#xff0c;或云管理服务整体宕掉&#xff0c;导致客户业务不可用&#xff0c;此时&#xff0c;需有业务能顺利切换到灾备云上。 需求&#xf…