[C++初阶]list的模拟实现

一、对于list的源码的部分分析

1.分析构造函数

首先,我们一开始最先看到的就是这个结点的结构体,在这里我们可以注意到这是一个双向链表。有一个前驱指针,一个后继指针。然后在有一个存储数据的空间

其次它的迭代器是一个自定义类型,而非原生指针。这里我们先不管,我们接着往下看。

我们继续找成员变量,在这里我们就找到了成员变量,但是这个类型我们看不懂,于是我们先略过。

通过对定义的查找,可以看到这个实际上还是一个指针。但是这个指针我们还是看不懂。

于是我们继续去速览定义,于是就找到了这里

这里我们可以注意到这个结点的类型其实就是一个类模板,这个模板正好就是我们一开始就看到的用结构体定义的一个结点。因此我们可以知道,这个成员变量实际就是一个指针,这个指针指向一个结点。这样一想象,就有点像我们在c语言使用链表时候的头节点了。

那么接下来,我们应该分析一下构造函数是什么样子的。
不难发现,就在成员变量的下方,正好就是一个无参的构造函数。也就是默认构造函数

但是在这里它又封装了一层函数,于是乎我们继续深入查看

如下所示,我们看到了具体的函数内容,从名字上来看,get_node 我们挺名字就知道开空间的。也就是得到一个结点,然后返回这个结点指针。这样一来,我们的成员变量就获取的实际的一个结点,然后让它的next和prev都指向自己,这样一来这个结点形成一个自循环。现在就能看出来这是一个带头双向循环链

我们也可以继续深入

如上图所示,这里的allocated设计到空间配置器。这里我们就先不做了解了。之后在学习中我们详细介绍。

我们往下看会看到put_node,这个函数其实就是释放结点的。在后面还有这样一个函数,这个函数是create_node不难理解,这个就是获取一个结点,先给这个结点开空间,然后调用构造函数。等一系列操作。既然这里涉及到一个构造,那么我们可以继续深入,看看这个构造里面是什么东西?但是这里涉及到了C++11的内容了,这边我们就先不管它了,我们只需要知道这里的空间能new出来就行。

2.对于头尾插的分析

作为链表,我们知道除了构造最重要的,肯定是头尾插,这里我们先找到头尾插的对应的函数

我们发现头尾插都调用了insert这个函数,因此我们需要先找到这个函数,

这里我们也是能大概理解的,先创造一个结点,然后进行连接。现在我们也基本读懂了这个大体的框架了,但是这里还需要注意的是:由于一开始的结点里面的指针都是空指针,导致后面需要经历很多的强制类型转化。所以这里我们如果一开始就定义好指针的类型是最好的。


二、list的模拟实现

1.list的节点声明

为了不和标准库中的list类冲突,我们可以开一个自己的命名空间,

其次,C++中对结点进行定义的时候可以只写类名,这与class是一样的。注意不要忘记带上模板参数T,因为我们写的结点也只是一个模板。因为类名不是类型,他实例化以后可以有各种各样的结点类型

	template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;};

2.list类的成员变量

由于我们的结点是一个模板,对于它而言,它的类型就比较繁琐,我们可以在list类里面使用typedef进行一次重命名。然后再私有里面再定义一个指针,这个指针就是一个结点指针。

	template<class T>class list{typedef list_node<T> Node;public:private:Node* _head;};

3.list类的默认构造函数

如下所示,我们定义好了成员变量以后,我们就写一个默认构造函数,当我们对这个链表类进行实例化的时候,自动调用这个默认构造函数,这个默认构造函数会为成员变量的头节点指针分配实际的空间,在new Node空间的时候,会调用它Node(即list_node<T>)类的默认构造函数函数。从而成功的开辟好这块空间。
 

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

4.list类的尾插

如下所示,我们的尾插逻辑也是比较简单的,先利用我们传过来的val去开辟一个新的结点,注意这里开辟新结点的时候使用new的话可以直接带一个括号去调用它的构造函数

		void push_back(const T& val){Node* newnode = new Node(val);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

5.结点的默认构造函数

有了上面的分析之后,我们现在缺的是一个结点的默认构造函数,我们直接给一个缺省值,使用T()就是一个匿名对象来初始化,对于内置类型也是同样适用的。然后我们使用初始化列表即可。

		list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}

6.list类的迭代器

首先我们思考一下,可否像vector一样直接在类里面typedef 一个迭代器?

显然是不行的,因为链表不支持下标随机访问,list是不连续的,指针加1后,地址早已不知道跑到哪个结点去了。其次这里仅仅只是结点的指针,解引用后,找到的仅仅只是结点,我们还需要进一步解引用才能找到对应的真正的值。

直接typedef的话,会使得迭代器的++和解引用操作均失效了,这时候我们只能使用运算符重载了。才能解决这个问题。既然要运算符重载,这里我们最好再次封装一个类出来。因为如果不封装一个类出来的话,我们无法完成此处的运算符重载。

如下所示,是我们实现的iterator的类

	template<class T>struct __list_iterator{typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}T& operator*(){return _node->_val;}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}bool operator!=(const __list_iterator<T>& it){return _node != it._node;}};

这个类我们使用了一个结构体去封装,在这个结构体中,我们只有一个成员变量,这个成员变量是结点类的指针,用于指向某一个结点, 在我们一开始定义出迭代器的时候,我们需要先写一个构造函数,用于迭代器的初始化。即需要传一个参数node来控制。

与之对应的,我们在list中就需要写出对应的begin和end函数,来返回迭代器。

		typedef __list_iterator<T> iterator;iterator begin(){//return _head->_next //单参数的构造函数支持隐式类型转换return iterator(_head->_next);}iterator end(){return iterator(_head);}

下面我们这里将迭代器的基本操作写的稍微完善一些

	template<class T>struct __list_iterator{typedef list_node<T> Node;Node* _node;__list_iterator(Node* node):_node(node){}T& operator*(){return _node->_val;}__list_iterator<T>& operator++(){_node = _node->_next;return *this;}__list_iterator<T> operator++(int){__list_iterator<T> tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const __list_iterator<T>& it){return _node != it._node;}bool operator==(const __list_iterator<T>& it){return _node == it._node;}};

7.const迭代器

我们可以拷贝一份原来的迭代器,然后改变一下类名和解引用这个成员函数的返回值即可

	template<class T>struct __list_const_iterator{typedef list_node<T> Node;Node* _node;__list_const_iterator(Node* node):_node(node){}const T& operator*() {return _node->_val;}__list_const_iterator<T>& operator++() {_node = _node->_next;return *this;}__list_const_iterator<T> operator++(int) {__list_const_iterator<T> tmp(*this);_node = _node->_next;return tmp;}bool operator!=(const __list_const_iterator<T>& it) {return _node != it._node;}bool operator==(const __list_const_iterator<T>& it) {return _node == it._node;}};

即如上代码所示,但是这样设计太过于冗余了。不过这个也是可以正常运行的,我们先补两个接口

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

其实我们可以通过一个类型去控制这个返回值,而要控制这个类型,就需要增加一个模板参数即可

在迭代器类中添加一个Ref参数,然后让*的运算符重载返回这个模板参数,最后代码如下:

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

当然现在还没完,还有时候,我们可能会写出这样的代码

	struct A{A(int a = 0, int b = 0):_a(a),_b(b){}int _a;int _b;};void test2(){list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));lt.push_back(A(5, 5));list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a << " ";it++;}cout << endl;}

我们显然发现是无法正常运行的。由于迭代器是类似于指针的操作,我们有时候就需要->操作符去解引用。所以,我们需要加上一个->的运算符重载。

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

如上所示,是写在迭代器里面的operator运算符重载,

然而当我们细心的话,我们会发现这个运算符重载是比较怪异的。哪里怪异呢?

首先我们这个运算符重载返回的是什么呢?是A*,也就是说它还需要一次->解引用才能找到真正的值。那么我们这里为什么可行呢?

严格来说,it->->_a,才是符合语法的。
因为运算符重载要求可读性,那么编译器特殊处理,省略了一个->

上面这个运算符重载仅仅只是针对于普通对象的,如果是const对象的话,那么我们只能使用跟*运算符重载一样的处理方法,多传一个参数,才可以解决这个问题。也就是我们需要三个模板参数才可以。
最终我们的迭代器代码如下所示

	template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){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){self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const self & it) const{return _node != it._node;}bool operator==(const self & it) const{return _node == it._node;}};

8.插入和删除接口

整体简单,这里直接展示源码:

		void push_back(const T& val){//insert(end(), val);Node* newnode = new Node(val);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;cur = nullptr;prev->_next = next;next->_prev = prev;return next;}

9.size

这个不难,我可以通过遍历解决得出size,但是这样子的时间复杂度可能有点高,

		size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){it++;sz++;}return sz;}

这里我们也可以用第二种方法,多加一个成员变量存储节点数量,每次插入节点时++,删除节点时--,这里我就不多做解释了

10.clear

这个就是遍历释放空间就行

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

11.析构函数

析构函数也是很简单的,析构和clear的区别就是析构会删除头节点,而clear不会删除头节点。

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

12.拷贝构造函数

这里设计空间的开辟,所以需要深拷贝,代码如下:

		list(const list<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;for (auto& e : lt){push_back(e);}}

当然在这里我们发现拷贝构造和默认构造有点重复了。我们可以对前面重复的部分在封装一个函数,从而简化代码:

		void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();}list(const list<T>& lt){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();for (auto& e : lt){push_back(e);}}

13.赋值运算符重载

如下所示,这个也是比较简单,我们直接使用现代写法吧:

		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;}

同时我们需要注意库里面的函数返回值和形参写的是类名,而不是类型,

这是因为在类模板里面写成员函数的时候,是允许用类名代替类型的。即我们的代码下面这些部分可以直接换为类名,但是呢,这里不建议使用,因为这种行为会降低可读性。

三、模拟list类的全部代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<list>
#include<assert.h>using namespace std;namespace Sim
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _node;__list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){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){self tmp(*this);_node = _node->_prev;return tmp;}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;//typedef __list_const_iterator<T> const_iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){//return _head->_next //单参数的构造函数支持隐式类型转换return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{//return _head->_next //单参数的构造函数支持隐式类型转换return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();}list(const list<T>& lt){//_head = new Node;//_head->_next = _head;//_head->_prev = _head;//_size = 0;empty_init();for (auto& e : lt){push_back(e);}}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;}void push_back(const T& val){insert(end(), val);//Node* newnode = new Node(val);//Node* tail = _head->_prev;//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;delete cur;cur = nullptr;prev->_next = next;next->_prev = prev;--_size;return next;}size_t size(){//size_t sz = 0;//iterator it = begin();//while (it != end())//{//	it++;//	sz++;//}//return sz;return _size;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}private:Node* _head;size_t _size;};}
}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

pyinstall 打包基于PyQt5和PaddleOCR的项目为.exe

简介&#xff1a; 最近做了一个小项目&#xff0c;是基于PyQt5和PaddleOCR的。需要将其打包为.exe&#xff0c;然后打包过程中遇到了很多问题&#xff0c;也看了很多教程&#xff0c;方法千奇百怪的&#xff0c;最后也是一步一步给试出来了。记录一下&#xff0c;防止以后忘记…

CSS基础学习之元素定位(6)

目录 1、定位类型 2、取值 2.1、static 2.2、relative 2.3、absolute 2.4、fixed 2.5、stickty 3、示例 3.1、相对定位(relative) 3.2、绝对定位&#xff08;absolute&#xff09; 3.3、固定定位&#xff08;fixed&#xff09; 3.4、粘性定位&#xff08;sticky&…

智慧互联新时代,Vatee万腾平台引领行业变革

在科技日新月异的今天&#xff0c;我们正步入一个前所未有的智慧互联新时代。这个时代&#xff0c;信息如潮水般涌来&#xff0c;数据成为新的石油&#xff0c;驱动着各行各业发生深刻变革。在这场变革的浪潮中&#xff0c;Vatee万腾平台以其卓越的智慧互联技术和前瞻性的战略布…

vue3前端开发-执行npm run dev提示报错怎么解决

vue3前端开发-执行npm run dev提示报错怎么解决&#xff01;今天在本地安装初始化了一个vue3的案例demo。但是当我执行npm run dev想启动它时报错了说&#xff0c;找不到dev。让我检查package.json文件是否包含dev。如下图所示&#xff1a; 实际上&#xff0c;不必惊慌&#xf…

2024全球和国内最常用的弱密码,有没有你的?

密码管理器NordPass分析了来自公开来源的超过4.3TB 的密码数据&#xff0c;找出了当前为止&#xff08;2024年&#xff09;最常用&#xff08;最脆弱&#xff09;的密码。 这些密码主要有下面这些特征&#xff1a; 简单且常用&#xff0c;万年弱密码&#xff0c;比如123456、a…

获利能力段部分特征值不更新,需要手动点派生才更新的问题

一、问题描述&#xff1a;销售订单修改某些特征值字段&#xff0c;保存后&#xff0c;获利能力段对应的字段值没更新。 比如&#xff1a;把销售订单销售组从Z09修改为Z04&#xff0c;保存后&#xff0c;获利能力段重的销售组还是旧值Z09。 1、修改销售组为Z04,然后保存 2、销售…

mac拆分pdf mac如何拆分pdf成多个文件

在数字化办公日益普及的今天&#xff0c;pdf文件因其良好的兼容性和便捷性&#xff0c;已经成为工作和学习中的重要文件格式。然而&#xff0c;有时候我们可能会遇到需要将一个大的pdf文件拆分成多个小文件的情况&#xff0c;以便于管一理和分享。本文将为您详细介绍两种简单易…

【BUG】已解决:java.lang.reflect.InvocationTargetException

已解决&#xff1a;java.lang.reflect.InvocationTargetException 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开发…

[word] word如何编写公式? #微信#知识分享

word如何编写公式&#xff1f; word如何编写公式&#xff1f;Word中数学公式是经常会使用到的&#xff0c;若是要在文档中录入一些复杂的公式&#xff0c;要怎么做呢&#xff1f;接下来小编就来给大家讲一讲具体操作&#xff0c;一起看过来吧&#xff01; 方法一&#xff1a;…

【机器学习】--过采样原理及代码详解

过采样&#xff08;Oversampling&#xff09;是一个在多个领域都有应用的技术&#xff0c;其具体含义和应用方法会根据领域的不同而有所差异。以下是对过采样技术的详细解析&#xff0c;主要从机器学习和信号处理两个领域进行阐述。 一、机器学习中的过采样 在机器学习中&…

完美的用户体验:如何设计一个直观和有效的网站导航?

APP的顶部导航栏对我们来说很熟悉。导航栏是UI设计中不可或缺的一部分&#xff0c;几乎每个页面都使用导航栏。虽然导航栏看起来很简单&#xff0c;不需要太多精力&#xff0c;但是设计一个与产品需求和客户目标高度匹配的导航栏并不是那么容易的。导航栏的设计标准有很多细节需…

JavaWeb服务器-Tomcat(Tomcat概述、Tomcat的下载、安装与卸载、启动与关闭、常见的问题)

Tomcat概述 Tomcat服务器软件是一个免费的开源的web应用服务器。是Apache软件基金会的一个核心项目。由Apache&#xff0c;Sun和其他一些公司及个人共同开发而成。 由于Tomcat只支持Servlet/JSP少量JavaEE规范&#xff0c;所以是一个开源免费的轻量级Web服务器。 JavaEE规范&…

JavaScript 中怎么看数据返回值

文章目录 前言console.log()1. 输出简单的文本2. 输出变量3. 输出表达式的结果4. 输出对象和数组5. 输出多个参数6. 使用模板字符串7. 输出错误信息 alert()基本用法使用场景注意事项 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 我只知道后端程序跑…

React学习笔记02-----React基本使用

一、React简介 想实现页面的局部刷新&#xff0c;而不是整个网页的刷新。AJAXDOM可以实现局部刷新 1.特点 &#xff08;1&#xff09;虚拟DOM 开发者通过React来操作原生DOM&#xff0c;从而构建页面。 React通过虚拟DOM来实现&#xff0c;可以解决DOM的兼容性问题&#x…

跳动的爱 - 动态全屏爱心【前端版本】

要使用HTML、CSS和JavaScript绘制一个全屏且较大的爱心&#xff0c;并且让它有动态效果&#xff0c;可以通过以下步骤实现&#xff1a; HTML: 定义基本的页面结构。 CSS: 定义爱心的样式和动画效果。 JavaScript: 动态调整爱心的位置和大小&#xff0c;使其在页面上移动。 下面…

软考2024下半年考试时间是多少?哪个科目容易考?

软考2024下半年考试时间为 11月9日-12日 2024下半年软考共安排了12个资格的考试&#xff0c;具体为软考高级&#xff1a;系统分析师、系统架构设计师、网络规划设计师、系统规划与管理师&#xff1b;软考中级&#xff1a;软件设计师、网络工程师、信息安全工程师、信息系统监…

【C语言】联合体(union)

文章目录 1.联合体的含义2. 联合体的声明3. 联合体大小的计算4. 联合体的特点 1.联合体的含义 联合体也叫做共用体&#xff0c;是指联合体的所有成员共用同一块内存空间。这也就说明了&#xff0c;联合体的大小至少是其成员所占空间的最大值。 2. 联合体的声明 #include<…

Codeforces Round 675 (Div. 2) --- B. Nice Matrix (数学,模拟)

很容易想到 a i , m − j 1 a n − i 1 , m − j 1 a i , j a n − i 1 , j a_{i,m-j1} a_{n-i1,m-j1} a_{i,j} a_{n-i1,j} ai,m−j1​an−i1,m−j1​ai,j​an−i1,j​ 在本题中应该被满足。 这道题主要的难点是我们怎么找到一个数&#xff0c;让这四个数与找到的数…

学习008-01-02 Define the Data Model and Set the Initial Data(定义数据模型并设置初始数据 )

Define the Data Model and Set the Initial Data&#xff08;定义数据模型并设置初始数据 &#xff09; This topic explains how to implement entity classes for your application. It also describes the basics of automatic user interface construction based on a da…

PGCCC|【PostgreSQL】PG考证对工作上有什么好处# PG证书

认证 PostgreSQL 考证&#xff08;PostgreSQL Certification&#xff09;在工作上有以下几个好处&#xff1a; 增强专业能力&#xff1a;通过考证&#xff0c;可以系统地学习和掌握 PostgreSQL 数据库的知识和技能&#xff0c;提高自己的专业水平。 提升职业竞争力&#xff1…