【STL】list的模拟实现

目录

前言 

list概述

list的节点

list的迭代器 

list的结构 

构造与析构

拷贝构造与赋值

list的元素操作 

insert()

push_back() 

 push_front()

erase() 

pop_back()

pop_front()

clear()

swap()

size()

完整代码链接 


前言 

如果你对链表还不熟悉或者忘了的话,建议你可以回去复习一下或者看一下这篇文章:双向链表

如果你没看过前几篇vector的模拟实现和string的模拟实现也建议可以去看看,因为这里有些内容在前面讲过了,所以解释的篇幅就比较少了。

如果内容有错,还望指出

希望本篇文章能对你学习STL有所帮助。

list概述

相较于vector,list就显得复杂许多,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。list的底层其实是一个双向链表的结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素,所以list是可以在常数范围内的任意位置进行插入和删除的序列式容器,并且可以前后迭代。但是list的最大的缺陷是不支持任意位置的随机访问。

list的节点

list本身和list节点的结构是不同的,所以我们需要分开写,下面是list的节点结构,很明显它是个双向链表。

    template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}};

list的迭代器 

可以说list的精华就在这迭代器上。list的迭代器的设计不能再向vector一样用个普通指针作为迭代器,因为对于链表来每个节点之间空间是不连续的,并且我们必须要让list的迭代器正确的指向list的节点,并且能够完成迭代器的一系列操作(取值、++、--等)。

所以我们的list迭代器初步设计如下

    template <class T>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T> iterator;Node* _node;//初始化__list_iterator(Node* node):_node(node){}bool operator!=(const iterator& it)const{return _node != it._node;}bool operator==(const iterator& it)const{return _node == it._node;}//*itT& operator*(){return _node->_data;}//it->T* operator->(){return &(operator*());}//++ititerator& operator++(){_node = _node->_next;return *this;}//it++iterator& operator++(int){iterator tmp(*this);_node = _node->_next;return tmp;}//--ititerator& operator--(){_node = _node->_prev;return *this;}//it--iterator& operator--(int){iterator tmp(*this);_node = _node->_prev;return tmp;}};

为了前置++和后置++、前置--和后置--之间能构成函数重载,所以我们需要在参数上动手脚,我这里在参数上就加了个int,包括源码中也是这样实现的。

对于->访问,源码里的实现并结合对应的场景来看,就有点让人难以看懂了

struct Coord
{Coord(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;
};int main()
{hjx::list<Coord> lt;lt.push_back(Coord(10, 20));//匿名对象lt.push_back(Coord(21, 11));//匿名对象auto it = lt.begin();while (it != lt.end()){cout << it->_a1 << ":" << it->_a2 << endl;it++;}return 0;
}

其实这里为了语言的可读性,编译器做了特殊处理,省略了一个->,所以没做特殊处理前应该是这样写的:it->->_a1,前一个->是调用了it.operator->()。

基于上面的迭代器的设计,对于const迭代器我们需要在operator*()和operator->()返回值加上const,但是这样写的话只有返回值不同不构成重载,当然你可以为const迭代器重新弄一个类出来。我们可以看看源码中是怎样设计的

源码中把T&和T*单领出来进行模板的实例化,如果模板实例化出const类型那这个迭代器就是const迭代器。

所以我们将迭代器重新设计如下

    template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> iterator;Node* _node;__list_iterator(Node* node):_node(node){}bool operator!=(const iterator& it)const{return _node != it._node;}bool operator==(const iterator& it)const{return _node == it._node;}//*itRef operator*(){return _node->_data;}//it->Ptr operator->(){return &(operator*());}//++ititerator& operator++(){_node = _node->_next;return *this;}//it++iterator& operator++(int){iterator tmp(*this);_node = _node->_next;return tmp;}//--ititerator& operator--(){_node = _node->_prev;return *this;}//it--iterator& operator--(int){iterator tmp(*this);_node = _node->_prev;return tmp;}};

list的结构 

    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;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const {return const_iterator(_head);}private:Node* _head;};

 list在源码中的实现其实就是带头节点的双向循环链表

构造与析构

 构造函数

对于只有一个哨兵位来说的话只要让next和prev都指向自己就好了。

		//对哨兵位的头结点进行初始化void empty_Init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}list(){empty_Init();}//迭代器区间构造template<class Inputiterator>list(Inputiterator first, Inputiterator last){empty_Init();while (first != last){push_back(*first);first++;}}

析构函数 

调用clear函数就行,最后不要忘记哨兵位也要释放掉。 

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

拷贝构造与赋值

这里就提供现代的写法啦,毕竟现代写法更简单。

有些细心的同学可能在C++文档中看到拷贝构造的接口时会省略掉模板参数,这是被C++所允许的,在类里面可以这样,但在类外可不能省略。建议初学者还是不要去省略这里的模板参数了。

 

		list(const list<T>& lt){empty_Init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}list<T>& operator=(list<T> lt){swap(lt);return *this;}

list的元素操作 

如果你数据结构学的非常扎实的话,这部分内容对你来说应该是小菜一碟。 

insert()

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

push_back() 

        void push_back(const T& x){insert(end(), x);}

 push_front()

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

erase() 

        iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}

pop_back()

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

pop_front()

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

clear()

        void clear(){//写法一//Node* cur = _head->_next;头删//while (cur != _head)//{//	_head->_next = cur->_next;//	delete cur;//	cur = _head->_next;//}//_head->_next = _head->_prev = nullptr;//写法二iterator it = begin();while (it != end()){it = erase(it);//erase返回的是下一个位置的迭代器}}

swap()

		void swap(list& x){std::swap(_head, x._head);}

size()

		size_t size()const{Node* cur = _head->_next;size_t count = 0;while (cur != _head){count++;cur = cur->_next;}return count;}

 关于其它函数有兴趣可以自己动手去实现一下,这里就不在展示了。

完整代码链接 

代码链接:list

源码链接:STL源码 

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

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

相关文章

Harmony鸿蒙南向驱动开发-PWM

PWM&#xff08;Pulse Width Modulation&#xff09;即脉冲宽度调制&#xff0c;是一种对模拟信号电平进行数字编码并将其转换为脉冲的技术&#xff0c;广泛应用在从测量、通信到功率控制与变换的许多领域中。通常情况下&#xff0c;在使用马达控制、背光亮度调节时会用到PWM模…

微信小程序实现输入appid跳转其他小程序

前言 本文记录wx.navigateToMiniProgram打开另一个小程序API使用方法&#xff0c;并封装为组件。 wxml 部分 输入框用来记录appid&#xff0c;按钮用来查询并跳转。 <view class"container"><input class"input" placeholder"请输入要查…

Flutter Your project requires a newer version of the Kotlin Gradle plugin

在开发Flutter项目的时候,遇到这个问题Flutter Your project requires a newer version of the Kotlin Gradle plugin 解决方案分两步: 1、在android/build.gradle里配置最新版本的kotlin 根据提示的kotlin官方网站搜到了Kotlin的最新版本是1.9.23,如下图所示: 同时在Ko…

ISO27017云服务安全管理体系认证介绍!

云服务信息安全管理体系&#xff08;简称“CSISMS”&#xff09;&#xff0c;以ISO/IEC 27017:2015为认证依据。ISO/IEC 27017标准是建立在ISO/IEC 27001信息安全管理体系框架和ISO/IEC 27002作为最佳实践控制设置的坚实基础之上&#xff0c;通过ISO/IEC 27017标准认证&#xf…

python如何写入csv

在使用python对文件操作的过程中&#xff0c;你肯定碰到过对csv文件的操作&#xff0c;下面就python对csv文件的操作进行详述。 CSV&#xff08;Comma-Separated Values&#xff09;逗号分隔符&#xff0c;也就是每条记录中的值与值之间是用分号分隔的。 打开CSV文件并写入一…

实战项目——智慧社区(一)

1、项目介绍 系统功能 登录、修改密码、登出 &#xff08;1&#xff09;首页 &#xff08;1.1&#xff09;数据统计&#xff1a;小区人员统计对比图&#xff0c;占比图 &#xff08;2&#xff09;物业管理 &#xff08;2.1&#xff09;小区管理&#xff1a;小区数据的增删改…

C++ sort函数

头文件 <algorithm> 使用 #include <iostream> //#include <vector> #include <algorithm> using namespace std; struct student {int sno;string name; }; //此处值得注意的是&#xff1a;如果要将形参定义为引用类型&#xff0c;则必须先定义为…

在开发过程中使用 git rebase 还是 git merge

在开发过程中使用 git rebase 还是 git merge Merge(合并)的优点和缺点Rebase(变基)的优点和缺点总结&#xff1a; Git merge 和rebase的目的是一样的&#xff0c;它们都是将多个分支合并成一个。 虽然他们最终的目标是一样的&#xff0c;但这两种方法实现的方式是不同的。那么…

富足--改变固有的行为模式分清幻想与现实

科学不等于我们所说的科技。 科学研究就是把钱变成知识&#xff0c;⽽技术开发就是把知识变成钱&#xff0c;这样⼀说&#xff0c;⼤家就知道它们不是⼀回事了。 技术开发的⽬的或者说创新的⽬的 是赚钱&#xff0c;不是创造知识。 创新要基于现有的⼈类知识&#xff0c;或者…

leetcode73 矩阵置零

题目描述 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用原地算法。 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 输入&#xff1a;matrix [[0,1,2,0],[3,4…

【Go】go mod初始化

遇到问题 创建go项目时使用 go mod init 命令提示如下错误&#xff1a; go: cannot determine module path for source directory /Users/easton/go/basic-go (outside GOPATH, module path must be specified) 解决方案一 这是因为go mod init 初始化项目时&#xff0c;需要…

【数据结构】单链表(一)

上一篇【数据结构】顺序表-CSDN博客 我们了解了顺序表&#xff0c;但是呢顺序表涉及到了一些问题&#xff0c;比如&#xff0c;中间/头部的插入/删除&#xff0c;时间复杂度为O(N);增容申请空间、拷贝、释放旧空间会有不小的消耗&#xff1b;增容所浪费的空间... 我们如何去解…

蓝桥杯刷题 二分-[99]分巧克力(C++)

题目描述 儿童节那天有 K 位小朋友到小明家做客。 小明拿出了珍藏的巧克力招待小朋友们。 小明一共有 N 块巧克力&#xff0c;其中第 i 块是 HiWi 的方格组成的长方形。 为了公平起见&#xff0c;小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。 切出的巧克力需要…

java数据结构与算法刷题-----LeetCode210. 课程表 II

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 深度优先遍历但不进行逆拓扑排序&#xff08;不用栈&#xff09;…

leetcode 343. 整数拆分

题目 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 36 解释: 1…

数据结构与算法 — 贪心算法

数据结构与算法 数据结构与算法是计算机科学中的两个核心概念&#xff0c;它们在软件开发和问题解决中起着至关重要的作用。 数据结构 数据结构是计算机中存储、组织和管理数据的方式&#xff0c;它能够帮助我们高效地访问和修改数据。不同的数据结构适用于不同类型的应用场…

记一次IP访问MySQL失败多次被自动锁定导致无法连接问题,解决方法只需一条SQL。

&#x1f469;&#x1f3fd;‍&#x1f4bb;个人主页&#xff1a;阿木木AEcru &#x1f525; 系列专栏&#xff1a;《Docker容器化部署系列》 《Java每日面筋》 &#x1f4b9;每一次技术突破&#xff0c;都是对自我能力的挑战和超越。 前言 今天下午还在带着耳机摸鱼&#xff…

获取当前的年、月、日、时、分、秒,并将这些信息用作保存 Excel 文件的前缀

要获取当前的年、月、日、时、分、秒&#xff0c;并将这些信息用作保存 Excel 文件的前缀&#xff0c;你可以使用 Python 的 datetime 模块来获取当前时间&#xff0c;并格式化时间字符串&#xff0c;然后使用 pandas 库将数据保存为 Excel 文件。示例代码&#xff1a; from d…

Netty的基本架构与组件

Netty实战精髓 前言 Netty的组成部分 1、Channel 2、Callback 3、Future ChannelFuture 提供多个附件方法来允许一个或者多个 ChannelFutureListener 实例&#xff0c;这个回调方法 operationComplete() 会在操作完成时调用。 4、Event和Handler 5、EventLOOP Netty 通过触发…

Objective-C学习笔记(block,协议)4.10

1.block&#xff1a;是一个数据类型&#xff0c;存储一段代码&#xff0c;代码可以有参数有返回值。 2.声明block&#xff1a; 返回值类型 (^block变量名称)(参数列表); int (^myblock) (int num1,int num2); 代码段格式&#xff1a;^返回值类型(参数列表){ 代码段 }; int (^m…